summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorMarcus Tillmanns <marcus.tillmanns@qt.io>2022-05-13 15:13:35 +0200
committerMarcus Tillmanns <marcus.tillmanns@qt.io>2022-06-07 09:22:14 +0000
commit0135c47849bb1962fd379de210a01a918c6e8b4e (patch)
tree97150f459e754a3e417d07cb8b6f2e652b436e44 /tests
parent13146fb0bd122cd2031f3a7b8d2193c362046762 (diff)
downloadqt-creator-0135c47849bb1962fd379de210a01a918c6e8b4e.tar.gz
device: Use multiplex script to allow multithread support
Previously the runInShell and outputForRunInShell methods were exclusively processed single threaded, meaning all calls were processed sequentially. With the multiplexed helper script we can now run multiple processes simultaneously. ( see tst_manual_deviceshell ) Additionally the new script allows us to capture both stdout and stderr from commands which was not possible previously. Change-Id: I52f4fb46d872dc274edb9c11872d2f6543741b34 Reviewed-by: David Schulz <david.schulz@qt.io> Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io> Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Diffstat (limited to 'tests')
-rw-r--r--tests/auto/utils/CMakeLists.txt1
-rw-r--r--tests/auto/utils/deviceshell/CMakeLists.txt8
-rw-r--r--tests/auto/utils/deviceshell/deviceshell.qbs12
-rw-r--r--tests/auto/utils/deviceshell/tst_deviceshell.cpp284
-rw-r--r--tests/auto/utils/utils.qbs1
-rw-r--r--tests/manual/CMakeLists.txt1
-rw-r--r--tests/manual/deviceshell/CMakeLists.txt10
-rw-r--r--tests/manual/deviceshell/deviceshell.qbs22
-rw-r--r--tests/manual/deviceshell/tst_deviceshell.cpp228
-rw-r--r--tests/manual/manual.qbs1
10 files changed, 568 insertions, 0 deletions
diff --git a/tests/auto/utils/CMakeLists.txt b/tests/auto/utils/CMakeLists.txt
index 5680cde7aa..60dd1cfbcc 100644
--- a/tests/auto/utils/CMakeLists.txt
+++ b/tests/auto/utils/CMakeLists.txt
@@ -9,3 +9,4 @@ add_subdirectory(stringutils)
add_subdirectory(templateengine)
add_subdirectory(treemodel)
add_subdirectory(multicursor)
+add_subdirectory(deviceshell)
diff --git a/tests/auto/utils/deviceshell/CMakeLists.txt b/tests/auto/utils/deviceshell/CMakeLists.txt
new file mode 100644
index 0000000000..61492bef32
--- /dev/null
+++ b/tests/auto/utils/deviceshell/CMakeLists.txt
@@ -0,0 +1,8 @@
+file(RELATIVE_PATH RELATIVE_TEST_PATH "${PROJECT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}")
+file(RELATIVE_PATH TEST_RELATIVE_LIBEXEC_PATH "/${RELATIVE_TEST_PATH}" "/${IDE_LIBEXEC_PATH}")
+
+add_qtc_test(tst_utils_deviceshell
+ DEFINES "TEST_RELATIVE_LIBEXEC_PATH=\"${TEST_RELATIVE_LIBEXEC_PATH}\""
+ DEPENDS Utils
+ SOURCES tst_deviceshell.cpp
+)
diff --git a/tests/auto/utils/deviceshell/deviceshell.qbs b/tests/auto/utils/deviceshell/deviceshell.qbs
new file mode 100644
index 0000000000..207848f00d
--- /dev/null
+++ b/tests/auto/utils/deviceshell/deviceshell.qbs
@@ -0,0 +1,12 @@
+Project {
+ QtcAutotest {
+ name: "DeviceShell autotest"
+
+ Depends { name: "Utils" }
+ Depends { name: "app_version_header" }
+
+ files: [
+ "tst_deviceshell.cpp",
+ ]
+ }
+}
diff --git a/tests/auto/utils/deviceshell/tst_deviceshell.cpp b/tests/auto/utils/deviceshell/tst_deviceshell.cpp
new file mode 100644
index 0000000000..40181b7d25
--- /dev/null
+++ b/tests/auto/utils/deviceshell/tst_deviceshell.cpp
@@ -0,0 +1,284 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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.
+**
+****************************************************************************/
+
+#include <app/app_version.h>
+
+#include <utils/deviceshell.h>
+#include <utils/environment.h>
+#include <utils/hostosinfo.h>
+#include <utils/launcherinterface.h>
+#include <utils/qtcprocess.h>
+#include <utils/runextensions.h>
+#include <utils/temporarydirectory.h>
+
+#include <QObject>
+#include <QtTest>
+
+using namespace Utils;
+
+class TestShell : public DeviceShell
+{
+public:
+ TestShell(CommandLine cmdLine)
+ : m_cmdLine(std::move(cmdLine))
+ {
+ start();
+ }
+
+private:
+ void setupShellProcess(QtcProcess *shellProcess) override
+ {
+ shellProcess->setCommand(m_cmdLine);
+ }
+
+ CommandLine m_cmdLine;
+};
+
+bool testDocker(const FilePath &executable)
+{
+ QtcProcess p;
+ p.setCommand({executable, {"info"}});
+ p.runBlocking();
+ return p.result() == ProcessResult::FinishedWithSuccess;
+}
+
+class tst_DeviceShell : public QObject
+{
+ Q_OBJECT
+private:
+ QByteArray m_asciiTestData{256, Qt::Uninitialized};
+
+ QList<CommandLine> m_availableShells;
+ bool m_dockerSetupCheckOk{false};
+
+private:
+ QString testString(int length)
+ {
+ QRandomGenerator generator;
+ QString result;
+ for (int i = 0; i < length; ++i)
+ result.append(QChar{generator.bounded('a', 'z')});
+
+ return result;
+ }
+
+private slots:
+ void initTestCase()
+ {
+ TemporaryDirectory::setMasterTemporaryDirectory(
+ QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX");
+
+ const QString libExecPath(qApp->applicationDirPath() + '/'
+ + QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
+ LauncherInterface::setPathToLauncher(libExecPath);
+
+ std::iota(m_asciiTestData.begin(), m_asciiTestData.end(), 0);
+
+ const FilePath dockerExecutable = Environment::systemEnvironment()
+ .searchInPath("docker", {"/usr/local/bin"});
+
+ if (dockerExecutable.exists()) {
+ m_availableShells.append({dockerExecutable, {"run", "-i", "--rm", "alpine"}});
+ if (testDocker(dockerExecutable)) {
+ m_dockerSetupCheckOk = true;
+ } else {
+ // On linux, docker needs some post-install steps: https://docs.docker.com/engine/install/linux-postinstall/
+ // Also check if you can start a simple container from the command line: "docker run -it alpine".
+ qWarning() << "Checking docker failed, tests will be skipped.";
+ }
+ }
+
+ if (!Utils::HostOsInfo::isWindowsHost()) {
+ // Windows by default has bash.exe, which does not work unless a working wsl is installed.
+ // Therefore we only test shells on linux / mac hosts.
+ const auto shells = {"dash", "bash", "sh", "zsh"};
+
+ for (const auto &shell : shells) {
+ const FilePath executable = Environment::systemEnvironment()
+ .searchInPath(shell, {"/usr/local/bin"});
+ if (executable.exists())
+ m_availableShells.append({executable, {}});
+ }
+ }
+
+ if (m_availableShells.isEmpty()) {
+ QSKIP("Skipping deviceshell tests, as no compatible shell could be found");
+ }
+ }
+
+ void cleanupTestCase() { Singleton::deleteAll(); }
+
+ void testArguments_data()
+ {
+ QTest::addColumn<CommandLine>("cmdLine");
+ QTest::addColumn<QString>("testData");
+
+ for (const auto &cmdLine : qAsConst(m_availableShells)) {
+ QTest::newRow((cmdLine.executable().baseName() + " : simple").toUtf8())
+ << cmdLine << "Hallo Welt!";
+ QTest::newRow((cmdLine.executable().baseName() + " : japanese").toUtf8())
+ << cmdLine
+ << QString::fromUtf8(u8"\xe8\xac\x9d\xe3\x81\x8d\xe3\x82\x81\xe9\x80\x80\x31\x30"
+ u8"\xe8\x89\xaf\xe3\x81\x9a\xe3"
+ u8"\x82\xa4\xe3\x81\xb5\xe3\x81\x8b\xe7\x89\x88\xe8\x84\xb3"
+ u8"\xe3\x83\xa9\xe3\x83\xaf\xe6"
+ u8"\xad\xa2\xe9\x80\x9a\xe3\x83\xa8\xe3\x83\xb2\xe3\x82\xad");
+ QTest::newRow((cmdLine.executable().baseName() + " : german").toUtf8())
+ << cmdLine
+ << QString::fromUtf8(u8"\x48\x61\x6c\x6c\xc3\xb6\x2c\x20\x77\x69\x65\x20\x67\xc3"
+ u8"\xa4\x68\x74\x20\x65\x73\x20"
+ u8"\x64\xc3\xbc\x72");
+
+ QTest::newRow((cmdLine.executable().baseName() + " : long").toUtf8())
+ << cmdLine << testString(4096 * 16);
+ }
+ }
+
+ void testArguments()
+ {
+ QFETCH(CommandLine, cmdLine);
+ QFETCH(QString, testData);
+
+ if (cmdLine.executable().toString().contains("docker") && !m_dockerSetupCheckOk) {
+ QSKIP("Docker was found, but does not seem to be set up correctly, skipping.");
+ }
+
+ TestShell shell(cmdLine);
+ QCOMPARE(shell.state(), DeviceShell::State::Succeeded);
+
+ QRandomGenerator generator;
+
+ const DeviceShell::RunResult result = shell.outputForRunInShell({"echo", {testData}});
+ QCOMPARE(result.exitCode, 0);
+ const QString expected = testData + "\n";
+ const QString resultAsUtf8 = QString::fromUtf8(result.stdOut);
+ QCOMPARE(resultAsUtf8.size(), expected.size());
+ QCOMPARE(resultAsUtf8, expected);
+ }
+
+ void testStdin_data()
+ {
+ QTest::addColumn<CommandLine>("cmdLine");
+ QTest::addColumn<QString>("testData");
+
+ for (const auto &cmdLine : qAsConst(m_availableShells)) {
+ QTest::newRow((cmdLine.executable().baseName() + " : simple").toUtf8())
+ << cmdLine << "Hallo Welt!";
+ QTest::newRow((cmdLine.executable().baseName() + " : japanese").toUtf8())
+ << cmdLine
+ << QString::fromUtf8(u8"\xe8\xac\x9d\xe3\x81\x8d\xe3\x82\x81\xe9\x80\x80\x31\x30"
+ u8"\xe8\x89\xaf\xe3\x81\x9a\xe3"
+ u8"\x82\xa4\xe3\x81\xb5\xe3\x81\x8b\xe7\x89\x88\xe8\x84\xb3"
+ u8"\xe3\x83\xa9\xe3\x83\xaf\xe6"
+ u8"\xad\xa2\xe9\x80\x9a\xe3\x83\xa8\xe3\x83\xb2\xe3\x82\xad");
+ QTest::newRow((cmdLine.executable().baseName() + " : german").toUtf8())
+ << cmdLine
+ << QString::fromUtf8(u8"\x48\x61\x6c\x6c\xc3\xb6\x2c\x20\x77\x69\x65\x20\x67\xc3"
+ u8"\xa4\x68\x74\x20\x65\x73\x20"
+ u8"\x64\xc3\xbc\x72");
+
+ QTest::newRow((cmdLine.executable().baseName() + " : long").toUtf8())
+ << cmdLine << testString(4096 * 16);
+ }
+ }
+
+ void testStdin()
+ {
+ QFETCH(CommandLine, cmdLine);
+ QFETCH(QString, testData);
+
+ if (cmdLine.executable().toString().contains("docker") && !m_dockerSetupCheckOk) {
+ QSKIP("Docker was found, but does not seem to be set up correctly, skipping.");
+ }
+
+ TestShell shell(cmdLine);
+ QCOMPARE(shell.state(), DeviceShell::State::Succeeded);
+
+ QRandomGenerator generator;
+
+ const DeviceShell::RunResult result = shell.outputForRunInShell({"cat", {}}, testData.toUtf8());
+ QCOMPARE(result.exitCode, 0);
+ const QString resultAsUtf8 = QString::fromUtf8(result.stdOut);
+ QCOMPARE(resultAsUtf8.size(), testData.size());
+ QCOMPARE(resultAsUtf8, testData);
+ }
+
+ void testAscii_data()
+ {
+ QTest::addColumn<CommandLine>("cmdLine");
+ for (const auto &cmdLine : qAsConst(m_availableShells)) {
+ QTest::newRow(cmdLine.executable().baseName().toUtf8()) << cmdLine;
+ }
+ }
+
+ void testAscii()
+ {
+ QFETCH(CommandLine, cmdLine);
+
+ if (cmdLine.executable().toString().contains("docker") && !m_dockerSetupCheckOk) {
+ QSKIP("Docker was found, but does not seem to be set up correctly, skipping.");
+ }
+
+ TestShell shell(cmdLine);
+ QCOMPARE(shell.state(), DeviceShell::State::Succeeded);
+
+ const DeviceShell::RunResult result = shell.outputForRunInShell({"cat", {}},
+ m_asciiTestData);
+ QCOMPARE(result.stdOut, m_asciiTestData);
+ }
+
+ void testStdErr_data()
+ {
+ QTest::addColumn<CommandLine>("cmdLine");
+ for (const auto &cmdLine : m_availableShells) {
+ QTest::newRow(cmdLine.executable().baseName().toUtf8()) << cmdLine;
+ }
+ }
+
+ void testStdErr()
+ {
+ QFETCH(CommandLine, cmdLine);
+
+ if (cmdLine.executable().toString().contains("docker") && !m_dockerSetupCheckOk) {
+ QSKIP("Docker was found, but does not seem to be set up correctly, skipping.");
+ }
+
+ TestShell shell(cmdLine);
+ QCOMPARE(shell.state(), DeviceShell::State::Succeeded);
+
+ const DeviceShell::RunResult result = shell.outputForRunInShell({"cat", {}},
+ m_asciiTestData);
+ QCOMPARE(result.stdOut, m_asciiTestData);
+ QVERIFY(result.stdErr.isEmpty());
+
+ const DeviceShell::RunResult result2 = shell.outputForRunInShell(
+ {"cat", {"/tmp/i-do-not-exist.none"}});
+ QVERIFY(!result2.stdErr.isEmpty());
+ }
+};
+
+QTEST_MAIN(tst_DeviceShell)
+
+#include "tst_deviceshell.moc"
diff --git a/tests/auto/utils/utils.qbs b/tests/auto/utils/utils.qbs
index 8960168fc7..5237f24a19 100644
--- a/tests/auto/utils/utils.qbs
+++ b/tests/auto/utils/utils.qbs
@@ -14,5 +14,6 @@ Project {
"templateengine/templateengine.qbs",
"treemodel/treemodel.qbs",
"multicursor/multicursor.qbs",
+ "deviceshell/deviceshell.qbs",
]
}
diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt
index 48bd9648fa..eecadda01f 100644
--- a/tests/manual/CMakeLists.txt
+++ b/tests/manual/CMakeLists.txt
@@ -17,3 +17,4 @@ add_subdirectory(proparser)
# add_subdirectory(search)
add_subdirectory(shootout)
add_subdirectory(widgets)
+add_subdirectory(deviceshell)
diff --git a/tests/manual/deviceshell/CMakeLists.txt b/tests/manual/deviceshell/CMakeLists.txt
new file mode 100644
index 0000000000..3961622348
--- /dev/null
+++ b/tests/manual/deviceshell/CMakeLists.txt
@@ -0,0 +1,10 @@
+file(RELATIVE_PATH RELATIVE_TEST_PATH "${PROJECT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}")
+file(RELATIVE_PATH TEST_RELATIVE_LIBEXEC_PATH "/${RELATIVE_TEST_PATH}" "/${IDE_LIBEXEC_PATH}")
+
+add_qtc_test(tst_manual_deviceshell
+ MANUALTEST
+ DEFINES "TEST_RELATIVE_LIBEXEC_PATH=\"${TEST_RELATIVE_LIBEXEC_PATH}\""
+ DEPENDS Utils
+ SOURCES
+ tst_deviceshell.cpp
+)
diff --git a/tests/manual/deviceshell/deviceshell.qbs b/tests/manual/deviceshell/deviceshell.qbs
new file mode 100644
index 0000000000..9a4409793f
--- /dev/null
+++ b/tests/manual/deviceshell/deviceshell.qbs
@@ -0,0 +1,22 @@
+import qbs.FileInfo
+
+Project {
+ QtcManualtest {
+ name: "DeviceShell manualtest"
+
+ Depends { name: "Utils" }
+ Depends { name: "app_version_header" }
+
+ files: [
+ "tst_deviceshell.cpp",
+ ]
+ cpp.defines: {
+ var defines = base;
+ var absLibExecPath = FileInfo.joinPaths(qbs.installRoot, qbs.installPrefix,
+ qtc.ide_libexec_path);
+ var relLibExecPath = FileInfo.relativePath(destinationDirectory, absLibExecPath);
+ defines.push('TEST_RELATIVE_LIBEXEC_PATH="' + relLibExecPath + '"');
+ return defines;
+ }
+ }
+}
diff --git a/tests/manual/deviceshell/tst_deviceshell.cpp b/tests/manual/deviceshell/tst_deviceshell.cpp
new file mode 100644
index 0000000000..f9ab9b4e8a
--- /dev/null
+++ b/tests/manual/deviceshell/tst_deviceshell.cpp
@@ -0,0 +1,228 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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.
+**
+****************************************************************************/
+
+#include <app/app_version.h>
+
+#include <utils/deviceshell.h>
+#include <utils/environment.h>
+#include <utils/hostosinfo.h>
+#include <utils/launcherinterface.h>
+#include <utils/qtcprocess.h>
+#include <utils/runextensions.h>
+#include <utils/temporarydirectory.h>
+#include <utils/mapreduce.h>
+
+#include <QObject>
+#include <QtTest>
+
+using namespace Utils;
+
+class TestShell : public DeviceShell
+{
+public:
+ TestShell() { start(); }
+
+ static CommandLine cmdLine() {
+ static CommandLine cmd;
+
+ if (cmd.isEmpty()) {
+ const FilePath dockerExecutable = Environment::systemEnvironment()
+ .searchInPath("docker", {"/usr/local/bin"});
+ const FilePath dashExecutable = Environment::systemEnvironment()
+ .searchInPath("dash", {"/usr/local/bin"});
+ const FilePath bashExecutable = Environment::systemEnvironment()
+ .searchInPath("bash", {"/usr/local/bin"});
+ const FilePath shExecutable = Environment::systemEnvironment()
+ .searchInPath("sh", {"/usr/local/bin"});
+
+ if (dockerExecutable.exists()) {
+ cmd = {dockerExecutable, {"run", "-i", "--rm","alpine"}};
+ } else if (dashExecutable.exists()) {
+ cmd = {dashExecutable, {}};
+ } else if (bashExecutable.exists()) {
+ cmd = {bashExecutable, {}};
+ } else if (shExecutable.exists()) {
+ cmd = {shExecutable, {}};
+ }
+
+ if (cmd.isEmpty()) {
+ return cmd;
+ }
+
+ qDebug() << "Using shell cmd:" << cmd;
+ }
+
+ return cmd;
+ }
+
+private:
+ void setupShellProcess(QtcProcess *shellProcess) override
+ {
+ shellProcess->setCommand(cmdLine());
+ }
+};
+
+class tst_DeviceShell : public QObject
+{
+ Q_OBJECT
+
+ QList<QByteArray> testArrays(const int numArrays)
+ {
+ QRandomGenerator generator;
+ QList<QByteArray> result;
+
+ for (int i = 0; i < numArrays; i++) {
+ QByteArray data;
+ auto numLines = generator.bounded(1, 100);
+ for (int l = 0; l < numLines; l++) {
+ auto numChars = generator.bounded(10, 40);
+ for (int c = 0; c < numChars; c++) {
+ data += static_cast<char>(generator.bounded('a', 'z'));
+ }
+ data += '\n';
+ }
+ result.append(data);
+ }
+ return result;
+ }
+
+ void test(int maxNumThreads, int numCalls)
+ {
+ TestShell shell;
+ QCOMPARE(shell.state(), DeviceShell::State::Succeeded);
+
+ QThreadPool::globalInstance()->setMaxThreadCount(maxNumThreads);
+
+ QList<QByteArray> testArray = testArrays(numCalls);
+
+ QElapsedTimer t;
+ t.start();
+
+ const QList<QByteArray> result
+ = mapped<QList>(testArray, [&shell](QByteArray data) -> QByteArray {
+ return shell.outputForRunInShell({"cat", {}}, data).stdOut;
+ }, MapReduceOption::Ordered, QThreadPool::globalInstance());
+
+ QCOMPARE(result, testArray);
+
+ qDebug() << "maxThreads:" << maxNumThreads << ", took:" << t.elapsed() / 1000.0
+ << "seconds";
+ }
+
+ void testSleep(QList<int> testData, int nThreads)
+ {
+ TestShell shell;
+ QCOMPARE(shell.state(), DeviceShell::State::Succeeded);
+
+ QThreadPool::globalInstance()->setMaxThreadCount(nThreads);
+
+ QElapsedTimer t;
+ t.start();
+
+ const auto result = mapped<QList>(testData, [&shell](const int &time) {
+ shell.runInShell({"sleep", {QString("%1").arg(time)}});
+ return 0;
+ }, MapReduceOption::Unordered, QThreadPool::globalInstance());
+
+ qDebug() << "maxThreads:" << nThreads << ", took:" << t.elapsed() / 1000.0 << "seconds";
+ }
+
+private slots:
+ void initTestCase()
+ {
+ TemporaryDirectory::setMasterTemporaryDirectory(
+ QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX");
+
+ const QString libExecPath(qApp->applicationDirPath() + '/'
+ + QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
+ LauncherInterface::setPathToLauncher(libExecPath);
+
+ if (TestShell::cmdLine().isEmpty()) {
+ QSKIP("Skipping deviceshell tests, as no compatible shell could be found");
+ }
+ }
+
+ void cleanupTestCase() { Singleton::deleteAll(); }
+
+ void testEncoding_data()
+ {
+ QTest::addColumn<QString>("utf8string");
+
+ QTest::newRow("japanese") << QString::fromUtf8(
+ u8"\xe8\xac\x9d\xe3\x81\x8d\xe3\x82\x81\xe9\x80\x80\x31\x30\xe8\x89\xaf\xe3\x81\x9a\xe3"
+ u8"\x82\xa4\xe3\x81\xb5\xe3\x81\x8b\xe7\x89\x88\xe8\x84\xb3\xe3\x83\xa9\xe3\x83\xaf\xe6"
+ u8"\xad\xa2\xe9\x80\x9a\xe3\x83\xa8\xe3\x83\xb2\xe3\x82\xad\n");
+ QTest::newRow("german") << QString::fromUtf8(
+ u8"\x48\x61\x6c\x6c\xc3\xb6\x2c\x20\x77\x69\x65\x20\x67\xc3\xa4\x68\x74\x20\x65\x73\x20"
+ u8"\x64\xc3\xbc\x72\n");
+ }
+
+ void testEncoding()
+ {
+ QFETCH(QString, utf8string);
+
+ TestShell shell;
+ QCOMPARE(shell.state(), DeviceShell::State::Succeeded);
+
+ const DeviceShell::RunResult r = shell.outputForRunInShell({"cat", {}}, utf8string.toUtf8());
+ const QString output = QString::fromUtf8(r.stdOut);
+ QCOMPARE(output, utf8string);
+ }
+
+ void testThreading_data()
+ {
+ QTest::addColumn<int>("numThreads");
+ QTest::addColumn<int>("numIterations");
+
+ QTest::newRow("multi-threaded") << 10 << 1000;
+ QTest::newRow("single-threaded") << 1 << 1000;
+ }
+
+ void testThreading()
+ {
+ QFETCH(int, numThreads);
+ QFETCH(int, numIterations);
+
+ test(numThreads, numIterations);
+ }
+
+ void testSleepMulti()
+ {
+ QList<int> testData{4, 7, 10, 3, 1, 10, 3, 3, 5, 4};
+ int full = std::accumulate(testData.begin(), testData.end(), 0);
+ qDebug() << "Testing sleep, full time is:" << full << "seconds";
+ QElapsedTimer t;
+ t.start();
+ testSleep(testData, 10);
+ const int multiThreadRunTime = t.restart();
+ testSleep(testData, 1);
+ const int singleThreadRunTime = t.elapsed();
+ QVERIFY(multiThreadRunTime < singleThreadRunTime);
+ }
+};
+
+QTEST_MAIN(tst_DeviceShell)
+
+#include "tst_deviceshell.moc"
diff --git a/tests/manual/manual.qbs b/tests/manual/manual.qbs
index 717d3745d5..f36312fcd6 100644
--- a/tests/manual/manual.qbs
+++ b/tests/manual/manual.qbs
@@ -8,6 +8,7 @@ Project {
references: [
"debugger/gui/gui.qbs",
"debugger/simple/simple.qbs",
+ "deviceshell/deviceshell.qbs",
"fakevim/fakevim.qbs",
"pluginview/pluginview.qbs",
"process/process.qbs",