summaryrefslogtreecommitdiffstats
path: root/tests/auto
diff options
context:
space:
mode:
authorRobert Griebl <robert.griebl@qt.io>2023-07-17 16:24:08 +0200
committerRobert Griebl <robert.griebl@qt.io>2023-07-24 10:18:51 +0200
commite27cc856cbfc4fd2fc9dc90f0c24a55bf95209cf (patch)
tree6794c73d390b07ef8866fd6981fd112db0fdbd6b /tests/auto
parentf48ef31446dc13c76838b744587108cc558e8464 (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.txt3
-rw-r--r--tests/auto/controller-tool/BLACKLIST4
-rw-r--r--tests/auto/controller-tool/CMakeLists.txt16
-rw-r--r--tests/auto/controller-tool/am-config.yaml11
-rw-r--r--tests/auto/controller-tool/builtin-apps/hello-world.green/icon.pngbin0 -> 1105 bytes
-rw-r--r--tests/auto/controller-tool/builtin-apps/hello-world.green/info.yaml19
-rw-r--r--tests/auto/controller-tool/builtin-apps/hello-world.green/main.qml14
-rw-r--r--tests/auto/controller-tool/system-ui.qml31
-rw-r--r--tests/auto/controller-tool/tst_controller-tool.cpp429
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
new file mode 100644
index 00000000..b149340c
--- /dev/null
+++ b/tests/auto/controller-tool/builtin-apps/hello-world.green/icon.png
Binary files differ
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"