summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mkspecs/features/qmltestcase.prf10
-rw-r--r--mkspecs/features/testcase.prf14
-rw-r--r--src/src.pro10
-rw-r--r--src/tools/androidtestrunner/androidtestrunner.pro13
-rw-r--r--src/tools/androidtestrunner/main.cpp464
-rwxr-xr-xtests/auto/android/runtests_androiddeployqt.pl550
6 files changed, 505 insertions, 556 deletions
diff --git a/mkspecs/features/qmltestcase.prf b/mkspecs/features/qmltestcase.prf
index b4b1224781..ae4ebef513 100644
--- a/mkspecs/features/qmltestcase.prf
+++ b/mkspecs/features/qmltestcase.prf
@@ -1,8 +1,14 @@
!isEmpty(SOURCES) {
QT += qml qmltest
load(testcase)
- contains(TEMPLATE, vc.*): DEFINES += QUICK_TEST_SOURCE_DIR=\"$$_PRO_FILE_PWD_\"
- else: DEFINES += QUICK_TEST_SOURCE_DIR=$$shell_quote(\"$$_PRO_FILE_PWD_\")
+ !android {
+ contains(TEMPLATE, vc.*): DEFINES += QUICK_TEST_SOURCE_DIR=\"$$_PRO_FILE_PWD_\"
+ else: DEFINES += QUICK_TEST_SOURCE_DIR=$$shell_quote(\"$$_PRO_FILE_PWD_\")
+ } else {
+ !isEmpty(RESOURCES): warning("The RESOURCES qmake variable is empty, the test will probably fail to run")
+ DEFINES += QUICK_TEST_SOURCE_DIR=\":/\"
+ }
+
} else {
# Allow a project to run tests without a CPP stub
TEMPLATE = aux
diff --git a/mkspecs/features/testcase.prf b/mkspecs/features/testcase.prf
index b8102c26b5..bc1ee22701 100644
--- a/mkspecs/features/testcase.prf
+++ b/mkspecs/features/testcase.prf
@@ -52,14 +52,24 @@ debug_and_release:debug_and_release_target {
}
# Allow for a custom test runner script
-$${type}.commands += $(TESTRUNNER)
+
+android: isEmpty($(TESTRUNNER)) {
+ qtPrepareTool(ANDROIDTESTRUNNER, androidtestrunner)
+ qtPrepareTool(ANDROIDDEPLOYQT, androiddeployqt)
+ isEmpty(ANDROID_DEPLOYMENT_SETTINGS_FILE): ANDROID_DEPLOYMENT_SETTINGS_FILE = $$OUT_PWD/android-$$TARGET-deployment-settings.json
+ contains(QMAKE_HOST.os, Windows): extension = .exe
+ $${type}.commands = $$ANDROIDTESTRUNNER --androiddeployqt \"$$ANDROIDDEPLOYQT --input $$ANDROID_DEPLOYMENT_SETTINGS_FILE\"
+ $${type}.commands += --path \"$$OUT_PWD/android-build\"
+ $${type}.commands += --adb \"$$shell_path($${ANDROID_SDK_ROOT}$${QMAKE_DIR_SEP}platform-tools$${QMAKE_DIR_SEP}adb$${extension})\"
+ $${type}.commands += --make \"$(MAKE) -f $(MAKEFILE)\"
+} else: $${type}.commands += $(TESTRUNNER)
unix {
isEmpty(TEST_TARGET_DIR): TEST_TARGET_DIR = .
app_bundle: \
$${type}.commands += $${TEST_TARGET_DIR}/$(QMAKE_TARGET).app/Contents/MacOS/$(QMAKE_TARGET)
- else: \
+ else: !android: \
$${type}.commands += $${TEST_TARGET_DIR}/$(QMAKE_TARGET)
} else {
# Windows
diff --git a/src/src.pro b/src/src.pro
index 1c76a2e46f..a39b718e10 100644
--- a/src/src.pro
+++ b/src/src.pro
@@ -57,6 +57,10 @@ src_tools_androiddeployqt.subdir = tools/androiddeployqt
src_tools_androiddeployqt.target = sub-androiddeployqt
src_tools_androiddeployqt.depends = src_corelib
+src_tools_androidtestrunner.subdir = tools/androidtestrunner
+src_tools_androidtestrunner.target = sub-androidtestrunner
+src_tools_androidtestrunner.depends = src_corelib
+
src_tools_qvkgen.subdir = tools/qvkgen
src_tools_qvkgen.target = sub-qvkgen
force_bootstrap: src_tools_qvkgen.depends = src_tools_bootstrap
@@ -189,8 +193,10 @@ qtConfig(dbus) {
}
android {
- SUBDIRS += src_tools_androiddeployqt
- TOOLS += src_tools_androiddeployqt
+ SUBDIRS += src_tools_androiddeployqt \
+ src_tools_androidtestrunner
+ TOOLS += src_tools_androiddeployqt \
+ src_tools_androidtestrunner
}
qtConfig(concurrent): SUBDIRS += src_concurrent
diff --git a/src/tools/androidtestrunner/androidtestrunner.pro b/src/tools/androidtestrunner/androidtestrunner.pro
new file mode 100644
index 0000000000..641d3e0003
--- /dev/null
+++ b/src/tools/androidtestrunner/androidtestrunner.pro
@@ -0,0 +1,13 @@
+option(host_build)
+CONFIG += console
+
+SOURCES += \
+ main.cpp
+
+# Required for declarations of popen/pclose on Windows
+windows: QMAKE_CXXFLAGS += -U__STRICT_ANSI__
+
+DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII
+DEFINES += QT_NO_FOREACH
+
+load(qt_app)
diff --git a/src/tools/androidtestrunner/main.cpp b/src/tools/androidtestrunner/main.cpp
new file mode 100644
index 0000000000..c7c27c97df
--- /dev/null
+++ b/src/tools/androidtestrunner/main.cpp
@@ -0,0 +1,464 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 BogDan Vatra <bogdan@kde.org>
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QCoreApplication>
+#include <QDir>
+#include <QHash>
+#include <QRegExp>
+#include <QXmlStreamReader>
+
+#include <algorithm>
+#include <chrono>
+#include <functional>
+#include <thread>
+
+#ifdef Q_CC_MSVC
+#define popen _popen
+#define QT_POPEN_READ "rb"
+#define pclose _pclose
+#else
+#define QT_POPEN_READ "r"
+#endif
+
+struct Options
+{
+ bool helpRequested = false;
+ bool verbose = false;
+ std::chrono::seconds timeout{300}; // 5minutes
+ QString androidDeployQtCommand;
+ QString buildPath;
+ QString adbCommand{QStringLiteral("adb")};
+ QString makeCommand;
+ QString package;
+ QString activity;
+ QStringList testArgsList;
+ QHash<QString, QString> outFiles;
+ QString testArgs;
+ QHash<QString, std::function<bool(const QByteArray &)>> checkFiles = {
+ {QStringLiteral("txt"), [](const QByteArray &data) -> bool {
+ return data.indexOf("\nFAIL! : ") < 0;
+ }},
+ {QStringLiteral("csv"), [](const QByteArray &/*data*/) -> bool {
+ // It seems csv is broken
+ return true;
+ }},
+ {QStringLiteral("xml"), [](const QByteArray &data) -> bool {
+ QXmlStreamReader reader{data};
+ while (!reader.atEnd()) {
+ reader.readNext();
+ if (reader.isStartElement() && reader.name() == QStringLiteral("Incident") &&
+ reader.attributes().value(QStringLiteral("type")).toString() == QStringLiteral("fail")) {
+ return false;
+ }
+ }
+ return true;
+ }},
+ {QStringLiteral("lightxml"), [](const QByteArray &data) -> bool {
+ return data.indexOf("\n<Incident type=\"fail\" ") < 0;
+ }},
+ {QStringLiteral("xunitxml"), [](const QByteArray &data) -> bool {
+ QXmlStreamReader reader{data};
+ while (!reader.atEnd()) {
+ reader.readNext();
+ if (reader.isStartElement() && reader.name() == QStringLiteral("testcase") &&
+ reader.attributes().value(QStringLiteral("result")).toString() == QStringLiteral("fail")) {
+ return false;
+ }
+ }
+ return true;
+ }},
+ {QStringLiteral("teamcity"), [](const QByteArray &data) -> bool {
+ return data.indexOf("' message='Failure! |[Loc: ") < 0;
+ }},
+ {QStringLiteral("tap"), [](const QByteArray &data) -> bool {
+ return data.indexOf("\nnot ok ") < 0;
+ }},
+ };
+};
+
+static Options g_options;
+
+static bool execCommand(const QString &command, QByteArray *output = nullptr, bool verbose = false)
+{
+#if defined(Q_OS_WIN32)
+ QString processedCommand = QLatin1Char('\"') + command + QLatin1Char('\"');
+#else
+ const QString& processedCommand = command;
+#endif
+
+ if (verbose)
+ fprintf(stdout, "Execute %s\n", processedCommand.toUtf8().constData());
+ FILE *process = popen(processedCommand.toUtf8().constData(), QT_POPEN_READ);
+
+ if (!process) {
+ fprintf(stderr, "Cannot execute command %s", qPrintable(processedCommand));
+ return false;
+ }
+ char buffer[512];
+ while (fgets(buffer, sizeof(buffer), process)) {
+ if (output)
+ output->append(buffer);
+ if (verbose)
+ fprintf(stdout, "%s", buffer);
+ }
+ return pclose(process) == 0;
+}
+
+// Copy-pasted from qmake/library/ioutil.cpp
+inline static bool hasSpecialChars(const QString &arg, const uchar (&iqm)[16])
+{
+ for (int x = arg.length() - 1; x >= 0; --x) {
+ ushort c = arg.unicode()[x].unicode();
+ if ((c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))))
+ return true;
+ }
+ return false;
+}
+
+static QString shellQuoteUnix(const QString &arg)
+{
+ // Chars that should be quoted (TM). This includes:
+ static const uchar iqm[] = {
+ 0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8,
+ 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78
+ }; // 0-32 \'"$`<>|;&(){}*?#!~[]
+
+ if (!arg.length())
+ return QStringLiteral("\"\"");
+
+ QString ret(arg);
+ if (hasSpecialChars(ret, iqm)) {
+ ret.replace(QLatin1Char('\''), QStringLiteral("'\\''"));
+ ret.prepend(QLatin1Char('\''));
+ ret.append(QLatin1Char('\''));
+ }
+ return ret;
+}
+
+static QString shellQuoteWin(const QString &arg)
+{
+ // Chars that should be quoted (TM). This includes:
+ // - control chars & space
+ // - the shell meta chars "&()<>^|
+ // - the potential separators ,;=
+ static const uchar iqm[] = {
+ 0xff, 0xff, 0xff, 0xff, 0x45, 0x13, 0x00, 0x78,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10
+ };
+
+ if (!arg.length())
+ return QStringLiteral("\"\"");
+
+ QString ret(arg);
+ if (hasSpecialChars(ret, iqm)) {
+ // Quotes are escaped and their preceding backslashes are doubled.
+ // It's impossible to escape anything inside a quoted string on cmd
+ // level, so the outer quoting must be "suspended".
+ ret.replace(QRegExp(QStringLiteral("(\\\\*)\"")), QStringLiteral("\"\\1\\1\\^\"\""));
+ // The argument must not end with a \ since this would be interpreted
+ // as escaping the quote -- rather put the \ behind the quote: e.g.
+ // rather use "foo"\ than "foo\"
+ int i = ret.length();
+ while (i > 0 && ret.at(i - 1) == QLatin1Char('\\'))
+ --i;
+ ret.insert(i, QLatin1Char('"'));
+ ret.prepend(QLatin1Char('"'));
+ }
+ return ret;
+}
+
+static QString shellQuote(const QString &arg)
+{
+ if (QDir::separator() == QLatin1Char('\\'))
+ return shellQuoteWin(arg);
+ else
+ return shellQuoteUnix(arg);
+}
+
+static bool parseOptions()
+{
+ QStringList arguments = QCoreApplication::arguments();
+ int i = 1;
+ for (; i < arguments.size(); ++i) {
+ const QString &argument = arguments.at(i);
+ if (argument.compare(QStringLiteral("--androiddeployqt"), Qt::CaseInsensitive) == 0) {
+ if (i + 1 == arguments.size())
+ g_options.helpRequested = true;
+ else
+ g_options.androidDeployQtCommand = arguments.at(++i).trimmed();
+ } else if (argument.compare(QStringLiteral("--adb"), Qt::CaseInsensitive) == 0) {
+ if (i + 1 == arguments.size())
+ g_options.helpRequested = true;
+ else
+ g_options.adbCommand = arguments.at(++i);
+ } else if (argument.compare(QStringLiteral("--path"), Qt::CaseInsensitive) == 0) {
+ if (i + 1 == arguments.size())
+ g_options.helpRequested = true;
+ else
+ g_options.buildPath = arguments.at(++i);
+ } else if (argument.compare(QStringLiteral("--make"), Qt::CaseInsensitive) == 0) {
+ if (i + 1 == arguments.size())
+ g_options.helpRequested = true;
+ else
+ g_options.makeCommand = arguments.at(++i);
+ } else if (argument.compare(QStringLiteral("--activity"), Qt::CaseInsensitive) == 0) {
+ if (i + 1 == arguments.size())
+ g_options.helpRequested = true;
+ else
+ g_options.activity = arguments.at(++i);
+ } else if (argument.compare(QStringLiteral("--timeout"), Qt::CaseInsensitive) == 0) {
+ if (i + 1 == arguments.size())
+ g_options.helpRequested = true;
+ else
+ g_options.timeout = std::chrono::seconds{arguments.at(++i).toInt()};
+ } else if (argument.compare(QStringLiteral("--help"), Qt::CaseInsensitive) == 0) {
+ g_options.helpRequested = true;
+ } else if (argument.compare(QStringLiteral("--verbose"), Qt::CaseInsensitive) == 0) {
+ g_options.verbose = true;
+ } else if (argument.compare(QStringLiteral("--"), Qt::CaseInsensitive) == 0) {
+ ++i;
+ break;
+ } else {
+ g_options.testArgsList << arguments.at(i);
+ }
+ }
+ for (;i < arguments.size(); ++i)
+ g_options.testArgsList << arguments.at(i);
+
+ if (g_options.helpRequested || g_options.androidDeployQtCommand.isEmpty() || g_options.buildPath.isEmpty())
+ return false;
+
+ QString serial = qEnvironmentVariable("ANDROID_DEVICE_SERIAL");
+ if (!serial.isEmpty())
+ g_options.adbCommand += QStringLiteral(" -s %1").arg(serial);
+ return true;
+}
+
+static void printHelp()
+{// "012345678901234567890123456789012345678901234567890123456789012345678901"
+ fprintf(stderr, "Syntax: %s <options> -- [TESTARGS] \n"
+ "\n"
+ " Creates an Android package in a temp directory <destination> and\n"
+ " runs it on the default emulator/device or on the one specified by\n"
+ " \"ANDROID_DEVICE_SERIAL\" environment variable.\n\n"
+ " Mandatory arguments:\n"
+ " --androiddeployqt <androiddeployqt cmd>: The androiddeployqt:\n"
+ " path including its additional arguments.\n"
+ " --path <path>: The path where androiddeployqt will build the .apk.\n"
+ " Optional arguments:\n"
+ " --adb <adb cmd>: The Android ADB command. If missing the one from\n"
+ " $PATH will be used.\n"
+ " --activity <acitvity>: The Activity to run. If missing the first\n"
+ " activity from AndroidManifest.qml file will be used.\n"
+ " --timout <seconds>: Timeout to run the test.\n"
+ " Default is 5 minutes.\n"
+ " --make <make cmd>: make command, needed to install the qt library.\n"
+ " If make is missing make sure the --path is set.\n"
+ " -- arguments that will be passed to the test application.\n"
+ " --verbose: Prints out information during processing.\n"
+ " --help: Displays this information.\n\n",
+ qPrintable(QCoreApplication::arguments().at(0))
+ );
+}
+
+static QString packageNameFromAndroidManifest(const QString &androidManifestPath)
+{
+ QFile androidManifestXml(androidManifestPath);
+ if (androidManifestXml.open(QIODevice::ReadOnly)) {
+ QXmlStreamReader reader(&androidManifestXml);
+ while (!reader.atEnd()) {
+ reader.readNext();
+ if (reader.isStartElement() && reader.name() == QStringLiteral("manifest"))
+ return reader.attributes().value(QStringLiteral("package")).toString();
+ }
+ }
+ return {};
+}
+
+static QString activityFromAndroidManifest(const QString &androidManifestPath)
+{
+ QFile androidManifestXml(androidManifestPath);
+ if (androidManifestXml.open(QIODevice::ReadOnly)) {
+ QXmlStreamReader reader(&androidManifestXml);
+ while (!reader.atEnd()) {
+ reader.readNext();
+ if (reader.isStartElement() && reader.name() == QStringLiteral("activity"))
+ return reader.attributes().value(QStringLiteral("android:name")).toString();
+ }
+ }
+ return {};
+}
+
+static void setOutputFile(QString file, QString format)
+{
+ if (file.isEmpty())
+ file = QStringLiteral("-");
+ if (format.isEmpty())
+ format = QStringLiteral("txt");
+
+ g_options.outFiles[format] = file;
+}
+
+static bool parseTestArgs()
+{
+ QRegExp newLoggingFormat{QStringLiteral("(.*),(txt|csv|xunitxml|xml|lightxml|teamcity|tap)")};
+ QRegExp oldFormats{QStringLiteral("-(txt|csv|xunitxml|xml|lightxml|teamcity|tap)")};
+
+ QString file;
+ QString logType;
+ QString unhandledArgs;
+ for (int i = 0; i < g_options.testArgsList.size(); ++i) {
+ const QString &arg = g_options.testArgsList[i].trimmed();
+ if (arg == QStringLiteral("-o")) {
+ if (i >= g_options.testArgsList.size() - 1)
+ return false; // missing file argument
+
+ const auto &filePath = g_options.testArgsList[++i];
+ if (!newLoggingFormat.exactMatch(filePath)) {
+ file = filePath;
+ } else {
+ const auto capturedTexts = newLoggingFormat.capturedTexts();
+ setOutputFile(capturedTexts.at(1), capturedTexts.at(2));
+ }
+ } else if (oldFormats.exactMatch(arg)) {
+ logType = oldFormats.capturedTexts().at(1);
+ } else {
+ unhandledArgs += QStringLiteral(" %1").arg(arg);
+ }
+ }
+ if (g_options.outFiles.isEmpty() || !file.isEmpty() || !logType.isEmpty())
+ setOutputFile(file, logType);
+
+ for (const auto &format : g_options.outFiles.keys())
+ g_options.testArgs += QStringLiteral(" -o output.%1,%1").arg(format);
+
+ g_options.testArgs += unhandledArgs;
+ g_options.testArgs = QStringLiteral("shell am start -e applicationArguments \"%1\" -n %2/%3").arg(shellQuote(g_options.testArgs.trimmed()),
+ g_options.package,
+ g_options.activity);
+ return true;
+}
+
+static bool isRunning() {
+ QByteArray output;
+ if (!execCommand(QStringLiteral("%1 shell 'ps | grep \" %2\"'").arg(g_options.adbCommand,
+ shellQuoteUnix(g_options.package)), &output)) {
+
+ return false;
+ }
+ return output.indexOf(" " + g_options.package.toUtf8()) > -1;
+}
+
+static bool waitToFinish()
+{
+ using clock = std::chrono::system_clock;
+ auto start = clock::now();
+ // wait to start
+ while (!isRunning()) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ if ((clock::now() - start) > std::chrono::seconds{10})
+ return false;
+ }
+
+ // Wait to finish
+ while (isRunning()) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(250));
+ if ((clock::now() - start) > g_options.timeout)
+ return false;
+ }
+ return true;
+}
+
+
+static bool pullFiles()
+{
+ bool ret = true;
+ for (auto it = g_options.outFiles.constBegin(); it != g_options.outFiles.end(); ++it) {
+ QByteArray output;
+ if (!execCommand(QStringLiteral("%1 shell run-as %2 cat files/output.%3")
+ .arg(g_options.adbCommand, g_options.package, it.key()), &output)) {
+ return false;
+ }
+ auto checkerIt = g_options.checkFiles.find(it.key());
+ ret &= checkerIt != g_options.checkFiles.end() && checkerIt.value()(output);
+ if (it.value() == QStringLiteral("-")){
+ fprintf(stdout, "%s", output.constData());
+ fflush(stdout);
+ } else {
+ QFile out{it.value()};
+ if (!out.open(QIODevice::WriteOnly))
+ return false;
+ out.write(output);
+ }
+ }
+ return ret;
+}
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication a(argc, argv);
+ if (!parseOptions()) {
+ printHelp();
+ return 1;
+ }
+
+ if (!g_options.makeCommand.isEmpty()) {
+ // we need to run make INSTALL_ROOT=path install to install the application file(s) first
+ if (!execCommand(QStringLiteral("%1 INSTALL_ROOT=%2 install")
+ .arg(g_options.makeCommand, g_options.buildPath), nullptr, g_options.verbose)) {
+ return 1;
+ }
+ }
+ // Run androiddeployqt
+ static auto verbose = g_options.verbose ? QStringLiteral("--verbose") : QStringLiteral();
+ if (!execCommand(QStringLiteral("%1 %3 --gradle --reinstall --output %2").arg(g_options.androidDeployQtCommand,
+ g_options.buildPath,
+ verbose), nullptr, g_options.verbose)) {
+ return 1;
+ }
+
+ QString manifest = g_options.buildPath + QStringLiteral("/AndroidManifest.xml");
+ g_options.package = packageNameFromAndroidManifest(manifest);
+ if (g_options.activity.isEmpty())
+ g_options.activity = activityFromAndroidManifest(manifest);
+
+ // parseTestArgs depends on g_options.package
+ if (!parseTestArgs())
+ return 1;
+
+ // start the tests
+ bool res = execCommand(QStringLiteral("%1 %2").arg(g_options.adbCommand, g_options.testArgs),
+ nullptr, g_options.verbose) && waitToFinish();
+ if (res)
+ res &= pullFiles();
+ res &= execCommand(QStringLiteral("%1 uninstall %2").arg(g_options.adbCommand, g_options.package),
+ nullptr, g_options.verbose);
+ fflush(stdout);
+ return res ? 0 : 1;
+}
diff --git a/tests/auto/android/runtests_androiddeployqt.pl b/tests/auto/android/runtests_androiddeployqt.pl
deleted file mode 100755
index 24b44cf9b2..0000000000
--- a/tests/auto/android/runtests_androiddeployqt.pl
+++ /dev/null
@@ -1,550 +0,0 @@
-#!/usr/bin/perl -w
-#############################################################################
-##
-## Copyright (C) 2012-2013 BogDan Vatra <bogdan@kde.org>
-## Copyright (C) 2016 The Qt Company Ltd.
-## Contact: https://www.qt.io/licensing/
-##
-## This file is part of the test suite of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:GPL-EXCEPT$
-## Commercial License Usage
-## Licensees holding valid commercial Qt licenses may use this file in
-## accordance with the commercial license agreement provided with the
-## Software or, alternatively, in accordance with the terms contained in
-## a written agreement between you and The Qt Company. For licensing terms
-## and conditions see https://www.qt.io/terms-conditions. For further
-## information use the contact form at https://www.qt.io/contact-us.
-##
-## GNU General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU
-## General Public License version 3 as published by the Free Software
-## Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-## included in the packaging of this file. Please review the following
-## information to ensure the GNU General Public License requirements will
-## be met: https://www.gnu.org/licenses/gpl-3.0.html.
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-use Cwd;
-use Cwd 'abs_path';
-use File::Basename;
-use File::Temp 'tempdir';
-use File::Path 'remove_tree';
-use Getopt::Long;
-use Pod::Usage;
-use XML::Simple;
-use Term::ANSIColor;
-
-### default options
-my @stack = cwd;
-my $device_serial=""; # "-s device_serial";
-my $deployqt_device_serial=""; # "-device device_serial";
-my $log_out="xml";
-my $max_runtime = 5;
-my $className="org.qtproject.qt5.android.bindings.QtActivity";
-my $jobs = 4;
-my $testsubset = "";
-my $man = 0;
-my $help = 0;
-my $make_clean = 0;
-my $stop_on_fail = 0;
-my $time_out=400;
-my $android_toolchain_version = "4.8";
-my $host_arch = "linux-x86";
-my $android_sdk_dir = "$ENV{'ANDROID_SDK_ROOT'}";
-my $android_ndk_dir = "$ENV{'ANDROID_NDK_ROOT'}";
-my $android_to_connect = "$ENV{'ANDROID_DEVICE'}";
-my $ant_tool = `which ant`;
-my $silent = 0;
-chomp $ant_tool;
-my $strip_tool="";
-my $readelf_tool="";
-# for ci usage
-my @failures = '';
-my $total_tests = 0;
-my $total_failed = 0;
-my $failed_insignificants = 0;
-my $ci_use = 0;
-my $start = time();
-my $uninstall = 0;
-
-GetOptions('h|help' => \$help
- , man => \$man
- , 's|serial=s' => \$device_serial
- , 't|test=s' => \$testsubset
- , 'c|clean' => \$make_clean
- , 'j|jobs=i' => \$jobs
- , 'logtype=s' => \$log_out
- , 'runtime=i' => \$max_runtime
- , 'sdk=s' => \$android_sdk_dir
- , 'ndk=s' => \$android_ndk_dir
- , 'toolchain=s' => \$android_toolchain_version
- , 'host=s' => \$host_arch
- , 'ant=s' => \$ant_tool
- , 'strip=s' => \$strip_tool
- , 'readelf=s' => \$readelf_tool
- , 'testcase=s' => \$testcase
- , 'f|fail' => sub { $stop_on_fail = 1 }
- , 'silent' => sub { $silent = 1 }
- , 'ci' => sub { $ci_use = 1 }
- , 'uninstall' => sub { $uninstall = 1 }
- ) or pod2usage(2);
-pod2usage(1) if $help;
-pod2usage(-verbose => 2) if $man;
-
-if ($ci_use){
- use QMake::Project;
-}
-my $adb_tool="$android_sdk_dir/platform-tools/adb";
-
-# For CI. Nodes are connecting to test devices over IP, which is stored to env variable
-if ($android_to_connect ne ""){
- print " Found device to be connected from env: $android_to_connect \n";
- system("$adb_tool disconnect $android_to_connect");
- system("$adb_tool connect $android_to_connect");
- sleep(2);# let it connect
- system("$adb_tool -s $android_to_connect reboot &");# adb bug, it blocks forever
- sleep(15); # wait for the device to come up again
- system("$adb_tool disconnect $android_to_connect");# cleans up the left adb reboot process
- system("$adb_tool connect $android_to_connect");
- $device_serial =$android_to_connect;
-}
-
-system("$adb_tool devices") == 0 or die "No device found, please plug/start at least one device/emulator\n"; # make sure we have at least on device attached
-
-$deployqt_device_serial = "--device $device_serial" if ($device_serial);
-$device_serial = "-s $device_serial" if ($device_serial);
-$testsubset="/$testsubset" if ($testsubset);
-
-$strip_tool="$android_ndk_dir/toolchains/arm-linux-androideabi-$android_toolchain_version/prebuilt/$host_arch/bin/arm-linux-androideabi-strip" unless($strip_tool);
-$readelf_tool="$android_ndk_dir/toolchains/arm-linux-androideabi-$android_toolchain_version/prebuilt/$host_arch/bin/arm-linux-androideabi-readelf" unless($readelf_tool);
-$readelf_tool="$readelf_tool -d -w ";
-
-sub dir
-{
-# print "@stack\n";
-}
-
-sub pushd ($)
-{
- unless ( chdir $_[0] )
- {
- warn "Error: $!\n";
- return;
- }
- unshift @stack, cwd;
- dir;
-}
-
-sub popd ()
-{
- @stack > 1 and shift @stack;
- chdir $stack[0];
- dir;
-}
-
-##############################
-# Read possible insignificance
-# from pro file
-##############################
-sub check_if_insignificant
-{
- return 0 if ( !$ci_use );
- my $case = shift;
- my $insignificant = 0;
- my $prj = QMake::Project->new( 'Makefile' );
- $insignificant = $prj->test( 'insignificant_test' );
- return $insignificant;
-}
-
-##############################
-# Print output from given
-# $testresult.txt file
-##############################
-sub print_output
-{
- my $res_file = shift;
- my $case = shift;
- my $insignificant = shift;
- my $print_all = 0;
- $total_tests++;
- if (-e $res_file) {
- open my $file, $res_file or die "Could not open $res_file: $!";
- while (my $line = <$file>) {
- if ($line =~ m/^FAIL/) {
- print "$line";
- # Pretend to be like the "real" testrunner and print out
- # all steps
- $print_all = 1;
- }
- }
- close $file;
- if ($print_all) {
- # In case we are printing all, the test failed
- system("cat $res_file");
- if ($insignificant) {
- print " Testrunner: $case failed, but it is marked with insignificant_test\n";
- push (@failures ,(basename($case)." [insignificant]"));
- $failed_insignificants++;
- } else {
- $total_failed++;
- push (@failures ,(basename($case)));
- }
- } else {
- my $cmd = "sed -n 'x;\$p' ${res_file}";
- my $summary = qx(${cmd});
- if ($summary =~ m/^Totals/) {
- print "$summary";
- } else {
- print "Error: The log is incomplete. Looks like you have to increase the timeout.";
- # The test log seems inclomplete, considering the test as failed.
- if ($insignificant) {
- print " Testrunner: $case failed, but it is marked with insignificant_test\n";
- push (@failures ,(basename($case)." [insignificant]"));
- $failed_insignificants++;
- } else {
- $total_failed++;
- push (@failures ,(basename($case)));
- }
- }
- }
- } else {
- if ($insignificant) {
- print " Failed to execute $case, but it is marked with insignificant_test\n";
- push (@failures ,(basename($case)." [insignificant]"));
- $failed_insignificants++;
- } else {
- print "Failed to execute $case \n";
- $total_failed++;
- push (@failures ,(basename($case)));
- }
- }
-}
-
-##############################
-# Print summary of test run
-##############################
-
-sub print_summary
-{
- my $total = time()-$start;
- my $h = 0;
- my $m = 0;
- my $s = 0;
- my $exit = 0;
- print "=== Timing: =================== TEST RUN COMPLETED! ============================\n";
- if ($total > 60*60) {
- $h = int($total/60/60);
- $s = int($total - $h*60*60);
-
- $m = int($s/60);
- $s = 0;
- print "Total: $h hours $m minutes\n";
- } elsif ($total > 60) {
- $m = int($total/60);
- $s = int($total - $m*60);
- print "Total: $m minutes $s seconds\n";
- } else {
- $s = int($total);
- print "Total: $s seconds\n";
- }
-
- print "=== Failures: ==================================================================";
- foreach my $failed (@failures) {
- print $failed."\n";
- $exit = 1;
- }
- print "=== Totals: ".$total_tests." tests, ".($total_tests-$total_failed).
- " passes, ".$failed_insignificants.
- " insignificant fails ======================\n";
- return $exit;
-}
-
-
-sub waitForProcess
-{
- my $process=shift;
- my $action=shift;
- my $timeout=shift;
- my $sleepPeriod=shift;
- $sleepPeriod=1 if !defined($sleepPeriod);
- print "Waiting for $process ".$timeout*$sleepPeriod." seconds to" if (!$silent);
- print $action?" start...\n":" die...\n" if (!$silent);
- while ($timeout--)
- {
- my $output = `$adb_tool $device_serial shell ps 2>&1`; # get current processes
- #FIXME check why $output is not matching m/.*S $process\n/ or m/.*S $process$/ (eol)
- my $res=($output =~ m/.*S $process/)?1:0; # check the procress
- if ($action == $res)
- {
- print "... succeed\n" if (!$silent);
- return 1;
- }
- sleep($sleepPeriod);
- print "timeount in ".$timeout*$sleepPeriod." seconds\n" if (!$silent);
- }
- print "... failed\n" if (!$silent);
- return 0;
-}
-
-my $src_dir_qt=abs_path(dirname($0)."/../../..");
-my $quadruplor_dir="$src_dir_qt/tests/auto/android";
-my $qmake_path="$src_dir_qt/bin/qmake";
-my $androiddeployqt_path="$src_dir_qt/bin/androiddeployqt";
-my $tests_dir="$src_dir_qt/tests$testsubset";
-my $temp_dir=tempdir(CLEANUP => 1);
-my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
-my $output_dir=$stack[0]."/".(1900+$year)."-$mon-$mday-$hour:$min";
-mkdir($output_dir);
-unlink("latest");
-system(" ln -s $output_dir latest");
-my $sdk_api=0;
-my $output = `$adb_tool $device_serial shell getprop`; # get device properties
-if ($output =~ m/.*\[ro.build.version.sdk\]: \[(\d+)\]/)
-{
- $sdk_api=int($1);
- $sdk_api=5 if ($sdk_api>5 && $sdk_api<8);
- $sdk_api=9 if ($sdk_api>9);
-}
-
-sub checkXMLOutput
-{
- print color 'bold red';
- my $fileName = shift;
- my $XMLOutput = eval { XMLin($fileName, ForceArray => 1) };
- if (!defined($XMLOutput)) {
- print "Can't parse the $fileName file, probably the test crased.\n";
- print color 'reset';
- die "Stopping\n" if $stop_on_fail;
- return;
- }
- my $testName = $XMLOutput->{name};
- my $fail = 0;
- while (my($node_key, $node_valule) = each (%{$XMLOutput})) {
- next if $node_key ne "TestFunction";
- while (my($function_key, $function_valule) = each (%{$node_valule})) {
- while (my($test_key, $test_valule) = each (%{$function_valule})) {
- next if $test_key ne "Incident";
- for my $incident (@{$test_valule}) {
- if (($incident->{type} ne "pass") && ($incident->{type} ne "xfail")) {
- print "test $testName::$function_key failed $incident->{file}:$incident->{line}\n";
- $fail = 1;
- }
- }
- }
- }
- }
- print color 'reset';
- die "Stopping\n" if $stop_on_fail and $fail;
-}
-
-sub startTest
-{
- my $testName = shift;
- my $packageName = "org.qtproject.example.tst_$testName";
- my $intentName = "$packageName/org.qtproject.qt5.android.bindings.QtActivity";
- my $output_file = shift;
- my $insignificance = shift;
- my $get_xml= 0;
- my $get_txt= 0;
- my $testLib ="";
- if ($log_out eq "xml") {
- $testLib="-o /data/data/$packageName/output.xml,xml";
- $get_xml = 1;
- } elsif ($log_out eq "txt") {
- $testLib="-o /data/data/$packageName/output.txt,txt";
- $get_txt = 1;
- } else {
- $testLib="-o /data/data/$packageName/output.xml,xml -o /data/data/$packageName/output.txt,txt";
- $get_xml = 1;
- $get_txt = 1;
- }
-
- my $cmd="${adb_tool} ${device_serial} shell am start -e applicationArguments \"${testLib}\" -n ${intentName}";
- my $res = qx(${cmd});
- print $res if (!$silent);
- #wait to start (if it has not started and quit already)
- waitForProcess($packageName,1,10);
-
- #wait to stop
- unless(waitForProcess($packageName,0,$time_out,5))
- {
- #killProcess($packageName);
- print "Someone should kill $packageName\n";
- system("$adb_tool $device_serial uninstall $packageName") if ($uninstall);
- return 1;
- }
-
- # Wait for three seconds to allow process to write all data
- sleep(3);
-
- system("$adb_tool $device_serial pull /data/data/$packageName/output.xml $output_dir/$output_file.xml") if ($get_xml);
-
- system("$adb_tool $device_serial pull /data/data/$packageName/output.txt $output_dir/$output_file.txt") if ($get_txt);
- if ($get_txt){
- print "Test results for $packageName:\n";
- my $insig =
- print_output("$output_dir/$output_file.txt", $packageName, $insignificance);
- }
- system("$adb_tool $device_serial uninstall $packageName") if ($uninstall);
-
- checkXMLOutput("$output_dir/$output_file.xml") if ($get_xml);
- return 1;
-}
-
-########### build qt tests and benchmarks ###########
-pushd($tests_dir);
-print "Building $tests_dir \n";
-system("make distclean") if ($make_clean);
-system("$qmake_path -r") == 0 or die "Can't run qmake\n"; #exec qmake
-system("make -j$jobs") == 0 or warn "Can't build all tests\n"; #exec make
-
-my $testsFiles = "";
-if ($testcase) {
- $testsFiles=`find . -name libtst_$testcase.so`; # only tests
-} else {
- $testsFiles=`find . -name libtst_*.so`; # only tests
-}
-
-foreach (split("\n",$testsFiles))
-{
- chomp; #remove white spaces
- pushd(abs_path(dirname($_))); # cd to application dir
- my $insig = check_if_insignificant();
- my $cmd="make INSTALL_ROOT=${temp_dir} install";
- my $res = qx(${cmd});
- print $res if (!$silent);
- my $application=basename(cwd);
- if ($silent) {
- $cmd="$androiddeployqt_path --install ${deployqt_device_serial} --output ${temp_dir} --deployment debug --verbose --input android-libtst_${application}.so-deployment-settings.json >/dev/null 2>&1";
- } else {
- $cmd="$androiddeployqt_path --install ${deployqt_device_serial} --output ${temp_dir} --deployment debug --verbose --input android-libtst_${application}.so-deployment-settings.json";
- }
- $res = qx(${cmd});
- print $res if (!$silent);
- my $output_name=dirname($_);
- $output_name =~ s/\.//; # remove first "." character
- $output_name =~ s/\///; # remove first "/" character
- $output_name =~ s/\//_/g; # replace all "/" with "_"
- $output_name=$application unless($output_name);
- $time_out=$max_runtime*60/5; # 5 minutes time out for a normal test
-
- $applicationLibrary = `find $temp_dir -name libtst_bench_$application.so`;
-
- if ($applicationLibrary)
- {
- $time_out=5*60/5; # 10 minutes for a benchmark
- $application = "bench_$application";
- }
- else
- {
- $applicationLibrary = `find $temp_dir -name libtst_$application.so`;
- }
-
- if (!$applicationLibrary)
- {
- print "Can't find application binary libtst_$application.so in $temp_dir!\n";
- }
- else
- {
- startTest($application, "$output_name", $insig) or warn "Can't run $application ...\n";
- }
-
- popd();
- remove_tree( $temp_dir, {keep_root => 1} );
-}
-print_summary() if ($ci_use);
-popd();
-
-__END__
-
-=head1 NAME
-
-Script to run all qt tests/benchmarks to an android device/emulator
-
-=head1 SYNOPSIS
-
-runtests.pl [options]
-
-=head1 OPTIONS
-
-=over 8
-=item B<-f --fail>
-
-Stop the script when test fails. Default 0
-
-=item B<-s --serial = serial>
-
-Device serial number. May be empty if only one device is attached.
-
-=item B<-t --test = test_subset>
-
-Tests subset (e.g. benchmarks, auto, auto/qbuffer, etc.).
-
-=item B<-c --clean>
-
-Clean tests before building them.
-
-=item B<-j --jobs = number>
-
-Make jobs when building tests.
-
-=item B<--sdk = sdk_path>
-
-Android SDK path.
-
-=item B<--ndk = ndk_path>
-
-Android NDK path.
-
-=item B<--ant = ant_tool_path>
-
-Ant tool path.
-
-=item B<--strip = strip_tool_path>
-
-Android strip tool path, used to deploy qt libs.
-
-=item B<--readelf = readelf_tool_path>
-
-Android readelf tool path, used to check if a test application uses qt OpenGL.
-
-=item B<--logtype = xml|txt|both>
-
-The format of log file, default is xml.
-
-=item B<--runtime = minutes>
-
-The timeout period before stopping individual tests from running.
-
-=item B<-silent>
-
-Suppress output of system commands.
-
-=item B<-ci>
-
-Enables checking if test is insignificant or not. Also prints test
-summary after all tests has been executed.
-
-=item B<-uninstall>
-
-Uninstalls the test after has been executed.
-
-=item B<-h --help>
-
-Print a brief help message and exits.
-
-=item B<--man>
-
-Prints the manual page and exits.
-
-=back
-
-=head1 DESCRIPTION
-
-B<This program> will run all qt tests/benchmarks to an android device/emulator.
-
-=cut