Files
qt-creator/tests/manual/deviceshell/tst_deviceshell.cpp
Marcus Tillmanns 0135c47849 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>
2022-06-07 09:22:14 +00:00

229 lines
7.5 KiB
C++

/****************************************************************************
**
** 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"