diff options
author | Robert Griebl <robert.griebl@qt.io> | 2023-07-17 16:24:08 +0200 |
---|---|---|
committer | Robert Griebl <robert.griebl@qt.io> | 2023-07-24 10:18:51 +0200 |
commit | e27cc856cbfc4fd2fc9dc90f0c24a55bf95209cf (patch) | |
tree | 6794c73d390b07ef8866fd6981fd112db0fdbd6b /tests/auto | |
parent | f48ef31446dc13c76838b744587108cc558e8464 (diff) |
Add a new auto-test for the appman-controller tool
Change-Id: I488838a14b1ecc92d27cd8995fa032e4bf047cb0
Pick-to: 6.6
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Bernd Weimer <bernd.weimer@qt.io>
Diffstat (limited to 'tests/auto')
-rw-r--r-- | tests/auto/CMakeLists.txt | 3 | ||||
-rw-r--r-- | tests/auto/controller-tool/BLACKLIST | 4 | ||||
-rw-r--r-- | tests/auto/controller-tool/CMakeLists.txt | 16 | ||||
-rw-r--r-- | tests/auto/controller-tool/am-config.yaml | 11 | ||||
-rw-r--r-- | tests/auto/controller-tool/builtin-apps/hello-world.green/icon.png | bin | 0 -> 1105 bytes | |||
-rw-r--r-- | tests/auto/controller-tool/builtin-apps/hello-world.green/info.yaml | 19 | ||||
-rw-r--r-- | tests/auto/controller-tool/builtin-apps/hello-world.green/main.qml | 14 | ||||
-rw-r--r-- | tests/auto/controller-tool/system-ui.qml | 31 | ||||
-rw-r--r-- | tests/auto/controller-tool/tst_controller-tool.cpp | 429 |
9 files changed, 527 insertions, 0 deletions
diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt index ca646a23..7719774e 100644 --- a/tests/auto/CMakeLists.txt +++ b/tests/auto/CMakeLists.txt @@ -22,4 +22,7 @@ if (LINUX) add_subdirectory(systemreader) add_subdirectory(processreader) add_subdirectory(sudo) + if (TARGET Qt::DBus) + add_subdirectory(controller-tool) + endif() endif() diff --git a/tests/auto/controller-tool/BLACKLIST b/tests/auto/controller-tool/BLACKLIST new file mode 100644 index 00000000..29d2495f --- /dev/null +++ b/tests/auto/controller-tool/BLACKLIST @@ -0,0 +1,4 @@ +# QDirIterator on /tmp doesn't list anything on qemu +# might be related to https://gitlab.com/qemu-project/qemu/-/issues/263 +[instances] +b2qt diff --git a/tests/auto/controller-tool/CMakeLists.txt b/tests/auto/controller-tool/CMakeLists.txt new file mode 100644 index 00000000..429553b4 --- /dev/null +++ b/tests/auto/controller-tool/CMakeLists.txt @@ -0,0 +1,16 @@ + +qt_internal_add_test(tst_controller-tool + SOURCES + ../error-checking.h + tst_controller-tool.cpp + DEFINES + AM_TESTDATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/../../data/" + LIBRARIES + Qt::DBus + Qt::Network + Qt::AppManApplicationPrivate + Qt::AppManCommonPrivate + Qt::AppManIntentServerPrivate + Qt::AppManMainPrivate + Qt::AppManManagerPrivate +) diff --git a/tests/auto/controller-tool/am-config.yaml b/tests/auto/controller-tool/am-config.yaml new file mode 100644 index 00000000..fe61a890 --- /dev/null +++ b/tests/auto/controller-tool/am-config.yaml @@ -0,0 +1,11 @@ +formatVersion: 1 +formatType: am-configuration +--- +applications: + builtinAppsManifestDir: "${CONFIG_PWD}/builtin-apps" + installationDir: "/tmp/am-test-controller-tool/apps" + documentDir: "/tmp/am-test-controller-tool/docs" + +ui: + mainQml: "${CONFIG_PWD}/system-ui.qml" +instanceId: controller-test-id diff --git a/tests/auto/controller-tool/builtin-apps/hello-world.green/icon.png b/tests/auto/controller-tool/builtin-apps/hello-world.green/icon.png Binary files differnew file mode 100644 index 00000000..b149340c --- /dev/null +++ b/tests/auto/controller-tool/builtin-apps/hello-world.green/icon.png diff --git a/tests/auto/controller-tool/builtin-apps/hello-world.green/info.yaml b/tests/auto/controller-tool/builtin-apps/hello-world.green/info.yaml new file mode 100644 index 00000000..a857f31c --- /dev/null +++ b/tests/auto/controller-tool/builtin-apps/hello-world.green/info.yaml @@ -0,0 +1,19 @@ +formatVersion: 1 +formatType: am-package +--- +id: 'hello-world.green' +icon: 'icon.png' +name: + en: 'Hello Green' +description: + en: "Green description" +version: '1.2.3' + +applications: +- id: green1 + runtime: 'qml' + code: 'main.qml' + +- id: green2 + runtime: 'qml' + code: 'main2.qml' diff --git a/tests/auto/controller-tool/builtin-apps/hello-world.green/main.qml b/tests/auto/controller-tool/builtin-apps/hello-world.green/main.qml new file mode 100644 index 00000000..29d598a0 --- /dev/null +++ b/tests/auto/controller-tool/builtin-apps/hello-world.green/main.qml @@ -0,0 +1,14 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtApplicationManager.Application + +ApplicationManagerWindow { + color: "green" + + Text { + anchors.centerIn: parent + text: "Hello World!" + } +} diff --git a/tests/auto/controller-tool/system-ui.qml b/tests/auto/controller-tool/system-ui.qml new file mode 100644 index 00000000..9c2328ed --- /dev/null +++ b/tests/auto/controller-tool/system-ui.qml @@ -0,0 +1,31 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtApplicationManager.SystemUI + +Item { + width: 800 + height: 600 + + IntentServerHandler { + intentIds: [ "inject-intent" ] + onRequestReceived: (request) => { + if (request.intentId === "inject-intent") + request.sendReply({ "status": "ok" }) + } + } + + Column { + anchors.fill: parent + Repeater { + model: WindowManager + WindowItem { + required property var model + width: 600 + height: 200 + window: model.window + } + } + } +} diff --git a/tests/auto/controller-tool/tst_controller-tool.cpp b/tests/auto/controller-tool/tst_controller-tool.cpp new file mode 100644 index 00000000..bd0f50eb --- /dev/null +++ b/tests/auto/controller-tool/tst_controller-tool.cpp @@ -0,0 +1,429 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <initializer_list> +#include <memory> + +#include <QtCore> +#include <QtTest> +#include <QDir> +#include <QUuid> +#include <QString> +#include <QJsonDocument> +#include <QJsonObject> + +#include "applicationmanager.h" +#include "packagemanager.h" +#include "logging.h" +#include "main.h" +#include "exception.h" +#include "utilities.h" +#include "qtyaml.h" +#include <QtAppManMain/configuration.h> + + +QT_USE_NAMESPACE_AM +using namespace QtYaml; + +class tst_ControllerTool : public QObject +{ + Q_OBJECT + +public: + tst_ControllerTool(); + +private slots: + void initTestCase(); + void cleanupTestCase(); + void usage(); + void instances(); + void applications(); + void packages(); + void installationLocations(); + void installCancel(); + void installRemove(); + void startStop(); + void injectIntent(); + +private: + int m_spyTimeout; + int m_argc = 0; + char **m_argv = nullptr; + Main *m_main = nullptr; + Configuration *m_config = nullptr; + bool m_mainSetupDone = false; + QDir m_tmpDir; +}; + + +class ControllerTool +{ +public: + ControllerTool(const std::initializer_list<QString> &list) + : m_arguments(list) + { } + ControllerTool(const QStringList &arguments) + : m_arguments(arguments) + { } + + static void setControllerPath(const QString &path) + { + s_command = path; + } + + int exitCode = 0; + QProcess::ExitStatus exitStatus = QProcess::NormalExit; + QByteArray stdOut; + QStringList stdOutList; + QByteArray stdErr; + QStringList stdErrList; + QByteArray failure; + + bool call() + { + return start() && waitForFinished(); + } + + bool start() + { + if (m_started) + return false; + + m_ctrl.reset(new QProcess); + m_spy.reset(new QSignalSpy(m_ctrl.get(), &QProcess::finished)); + m_ctrl->setProgram(s_command); + QStringList args = { qSL("--instance-id"), qSL("controller-test-id") }; + args.append(m_arguments); + m_ctrl->setArguments(args); + m_ctrl->start(); + + if (!m_ctrl->waitForStarted()) { + failure = "could not start appman-controller"; + return false; + } + return m_started = true; + } + + bool waitForFinished() + { + if (!m_started) + return false; + + if (m_ctrl->state() == QProcess::Running) { + m_spy->wait(5000 * timeoutFactor()); + if (m_ctrl->state() != QProcess::NotRunning) { + failure = "appman-controller did not exit"; + return false; + } + } + + exitCode = m_ctrl->exitCode(); + exitStatus = m_ctrl->exitStatus(); + stdOut = m_ctrl->readAllStandardOutput(); + stdOutList = QString::fromLocal8Bit(stdOut).split(u'\n', Qt::SkipEmptyParts); + stdErr = m_ctrl->readAllStandardError(); + stdErrList = QString::fromLocal8Bit(stdErr).split(u'\n', Qt::SkipEmptyParts); + + if (exitStatus == QProcess::CrashExit) + failure = "appman-controller crashed, signal: " + QByteArray::number(exitCode); + else if (exitCode != 0) + failure = "appman-controller returned an error code: " + QByteArray::number(exitCode); + + // enable for debugging +// if (!failure.isEmpty()) { +// qWarning() << "STDOUT" << stdOut; +// qWarning() << "STDERR" << stdErr; +// } + m_started = false; + return failure.isEmpty(); + } +private: + QStringList m_arguments; + bool m_started = false; + std::unique_ptr<QSignalSpy> m_spy; + std::unique_ptr<QProcess> m_ctrl; + static QString s_command; +}; + +QString ControllerTool::s_command; + + +tst_ControllerTool::tst_ControllerTool() + : m_spyTimeout(5000 * timeoutFactor()) + , m_tmpDir(qSL("/tmp/am-test-controller-tool")) +{ } + +void tst_ControllerTool::initTestCase() +{ +#if !defined(Q_OS_LINUX) + QSKIP("This test is only supported on Linux"); +#endif + + if (!QDir(qL1S(AM_TESTDATA_DIR "/packages")).exists()) + QSKIP("No test packages available in the data/ directory"); + + auto verbose = qEnvironmentVariableIsSet("AM_VERBOSE_TEST"); + qInfo() << "Verbose mode is" << (verbose ? "on" : "off") << "(change by (un)setting $AM_VERBOSE_TEST)"; + + m_argc = 2; + m_argv = new char * [m_argc + 1]; + m_argv[0] = qstrdup("tst_controller-tool"); + m_argv[1] = qstrdup("--no-cache"); + m_argv[m_argc] = nullptr; + + m_main = new Main(m_argc, m_argv); // QCoreApplication saves a reference to argc! + + QStringList possibleLocations; + possibleLocations.append(QLibraryInfo::path(QLibraryInfo::BinariesPath)); + possibleLocations.append(QCoreApplication::applicationDirPath() + qSL("/../../../bin")); + + QString controllerPath; + const QString controllerName = qSL("/appman-controller"); + for (const QString &possibleLocation : possibleLocations) { + QFileInfo fi(possibleLocation + controllerName); + + if (fi.exists() && fi.isExecutable()) { + controllerPath = fi.absoluteFilePath(); + break; + } + } + QVERIFY(!controllerPath.isEmpty()); + ControllerTool::setControllerPath(controllerPath); + + m_config = new Configuration({ QFINDTESTDATA("am-config.yaml") }, QString()); + m_config->parseWithArguments(QCoreApplication::arguments()); + if (verbose) + m_config->setForceVerbose(true); + + try { + m_main->setup(m_config); + m_mainSetupDone = true; + m_main->loadQml(false); + m_main->showWindow(false); + } catch (const Exception &e) { + QVERIFY2(false, e.what()); + } + PackageManager::instance()->setAllowInstallationOfUnsignedPackages(true); + + if (m_tmpDir.exists()) + m_tmpDir.removeRecursively(); + QVERIFY(m_tmpDir.mkpath(qSL("."))); +} + +void tst_ControllerTool::cleanupTestCase() +{ + if (m_main) { + if (m_mainSetupDone) { + m_main->shutDown(); + m_main->exec(); + } + delete m_main; + } + if (m_config) + delete m_config; + + if (m_argc && m_argv) { + for (int i = 0; i < m_argc; ++i) + delete [] m_argv[i]; + delete [] m_argv; + } + + if (m_tmpDir.exists()) + m_tmpDir.removeRecursively(); +} + +void tst_ControllerTool::usage() +{ + ControllerTool ctrl({ }); + QVERIFY(!ctrl.call()); + QVERIFY(ctrl.stdOut.trimmed().startsWith("Usage:")); +} + +void tst_ControllerTool::instances() +{ + ControllerTool ctrl({ qSL("list-instances") }); + QVERIFY2(ctrl.call(), ctrl.failure); + QCOMPARE(ctrl.stdOutList, QStringList({ qSL("\"controller-test-id\"") })); +} + +void tst_ControllerTool::applications() +{ + { + ControllerTool ctrl({ qSL("list-applications") }); + QVERIFY2(ctrl.call(), ctrl.failure); + QCOMPARE(ctrl.stdOutList, QStringList({ qSL("green1"), qSL("green2") })); + } + { + ControllerTool ctrl({ qSL("show-application"), qSL("green1") }); + QVERIFY2(ctrl.call(), ctrl.failure); + const auto docs = YamlParser::parseAllDocuments(ctrl.stdOut); + QCOMPARE(docs.size(), 1); + const auto vm = docs[0].toMap(); + + QVariantMap expected({ + { qSL("applicationId"), qSL("green1") }, + { qSL("capabilities"), QVariantList { } }, + { qSL("categories"), QVariantList { } }, + { qSL("codeFilePath"), QFINDTESTDATA("builtin-apps/hello-world.green/main.qml") }, + { qSL("icon"), QUrl::fromLocalFile(QFINDTESTDATA("builtin-apps/hello-world.green/icon.png")).toString() }, + { qSL("isBlocked"), false }, + { qSL("isRemovable"), false }, + { qSL("isRunning"), false }, + { qSL("isShuttingDown"), false }, + { qSL("isStartingUp"), false }, + { qSL("isUpdating"), false }, + { qSL("lastExitCode"), 0 }, + { qSL("lastExitStatus"), 0 }, + { qSL("name"), qSL("Hello Green") }, + { qSL("runtimeName"), qSL("qml") }, + { qSL("runtimeParameters"), QVariantMap { } }, + { qSL("updateProgress"), 0 }, + { qSL("version"), qSL("1.2.3") }, + }); + QCOMPARE(vm, expected); + } +} + +void tst_ControllerTool::packages() +{ + { + ControllerTool ctrl({ qSL("list-packages") }); + QVERIFY2(ctrl.call(), ctrl.failure); + QCOMPARE(ctrl.stdOutList, QStringList({ qSL("hello-world.green") })); + } + { + ControllerTool ctrl({ qSL("show-package"), qSL("hello-world.green") }); + QVERIFY2(ctrl.call(), ctrl.failure); + const auto docs = YamlParser::parseAllDocuments(ctrl.stdOut); + QCOMPARE(docs.size(), 1); + const auto vm = docs[0].toMap(); + + QVariantMap expected({ + { qSL("description"), qSL("Green description") }, + { qSL("icon"), QUrl::fromLocalFile(QFINDTESTDATA("builtin-apps/hello-world.green/icon.png")).toString() }, + { qSL("isBlocked"), false }, + { qSL("isRemovable"), false }, + { qSL("isUpdating"), false }, + { qSL("name"), qSL("Hello Green") }, + { qSL("packageId"), qSL("hello-world.green") }, + { qSL("updateProgress"), 0 }, + { qSL("version"), qSL("1.2.3") }, + }); + QCOMPARE(vm, expected); + } +} + +void tst_ControllerTool::installationLocations() +{ + { + ControllerTool ctrl({ qSL("list-installation-locations") }); + QVERIFY2(ctrl.call(), ctrl.failure); + QCOMPARE(ctrl.stdOutList, QStringList({ qSL("internal-0") })); + } + { + ControllerTool ctrl({ qSL("show-installation-location"), qSL("internal-0") }); + QVERIFY2(ctrl.call(), ctrl.failure); + const auto docs = YamlParser::parseAllDocuments(ctrl.stdOut); + QCOMPARE(docs.size(), 1); + const auto vm = docs[0].toMap(); + + QCOMPARE(vm.value(qSL("path")), qSL("/tmp/am-test-controller-tool/apps")); + QVERIFY(vm.value(qSL("deviceSize")).toULongLong() > 0); + QVERIFY(vm.value(qSL("deviceFree")).toULongLong() > 0); + } +} + +void tst_ControllerTool::installCancel() +{ + ControllerTool install({ qSL("install-package"), + qL1S(AM_TESTDATA_DIR "packages/hello-world.red.appkg") }); + QVERIFY2(install.start(), install.failure); + + QTRY_VERIFY(PackageManager::instance()->isPackageInstallationActive(qSL("hello-world.red"))); + QString taskId; + + { + ControllerTool ctrl({ qSL("list-installation-tasks") }); + QVERIFY2(ctrl.call(), ctrl.failure); + QCOMPARE(ctrl.stdOutList.size(), 1); + taskId = ctrl.stdOutList.at(0); + QVERIFY(!QUuid::fromString(taskId).isNull()); + } + { + ControllerTool ctrl({ qSL("cancel-installation-task"), taskId }); + QVERIFY2(ctrl.call(), ctrl.failure); + } + { + ControllerTool ctrl({ qSL("list-installation-tasks") }); + QVERIFY2(ctrl.call(), ctrl.failure); + QCOMPARE(ctrl.stdOutList.size(), 0); + } + + QVERIFY(!install.waitForFinished()); + QVERIFY(install.exitCode != 0); + QVERIFY(install.stdErr.contains("canceled")); +} + +void tst_ControllerTool::installRemove() +{ + { + ControllerTool ctrl({ qSL("install-package"), qSL("-a"), + qL1S(AM_TESTDATA_DIR "packages/hello-world.red.appkg") }); + QVERIFY2(ctrl.call(), ctrl.failure); + } + { + ControllerTool ctrl({ qSL("remove-package"), qSL("hello-world.red") }); + QVERIFY2(ctrl.call(), ctrl.failure); + } +} + +void tst_ControllerTool::startStop() +{ + const auto app = ApplicationManager::instance()->application(qSL("green1")); + QVERIFY(app); + { + ControllerTool ctrl({ qSL("start-application"), app->id() }); + QVERIFY2(ctrl.call(), ctrl.failure); + } + QTRY_VERIFY(app->runState() == Am::Running); + { + ControllerTool ctrl({ qSL("stop-application"), app->id() }); + QVERIFY2(ctrl.call(), ctrl.failure); + } + QTRY_VERIFY(app->runState() == Am::NotRunning); + { + ControllerTool ctrl({ qSL("debug-application"), qSL("FOO=BAR"), app->id() }); + QVERIFY2(ctrl.call(), ctrl.failure); + } + QTRY_VERIFY(app->runState() == Am::Running); + { + ControllerTool ctrl({ qSL("stop-all-applications") }); + QVERIFY2(ctrl.call(), ctrl.failure); + } + QTRY_VERIFY(app->runState() == Am::NotRunning); +} + +void tst_ControllerTool::injectIntent() +{ + auto oldDevMode = PackageManager::instance()->developmentMode(); + PackageManager::instance()->setDevelopmentMode(true); + + ControllerTool ctrl({ qSL("inject-intent-request"), qSL("--requesting-application-id"), + qSL(":sysui:"), qSL("--application-id"), qSL(":sysui:"), qSL("inject-intent"), qSL("{ }"), }); + QVERIFY2(ctrl.call(), ctrl.failure); + + const auto json = QJsonDocument::fromJson(ctrl.stdOut); + const auto vm = json.toVariant().toMap(); + + QVariantMap expected({ + { qSL("status"), qSL("ok") }, + }); + QCOMPARE(vm, expected); + + PackageManager::instance()->setDevelopmentMode(oldDevMode); +} + + +QTEST_APPLESS_MAIN(tst_ControllerTool) + +#include "tst_controller-tool.moc" |