Files
qt-creator/tests/auto/utils/tasktree/tst_tasktree.cpp
Jarek Kobus 6b11e1d572 TaskTree: Revamp tests
Get rid of std::bind.

Enclose each data tag of processTree inside its own block,
so that "root" and "log" names are unique within each block.
This makes each block look like a pattern with filled data up.

Introduce TestData structure, so that it's harder now to mix
the order of test data in new rows.

Introduce some enums instead of bools to make more meaning
on the returned data.

Change-Id: Iffe49cf1c7f912378467ab9f9c1256003dba7b9c
Reviewed-by: hjk <hjk@qt.io>
2023-01-27 14:15:14 +00:00

777 lines
26 KiB
C++

// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <app/app_version.h>
#include <utils/launcherinterface.h>
#include <utils/qtcprocess.h>
#include <utils/singleton.h>
#include <utils/temporarydirectory.h>
#include <QtTest>
using namespace Utils;
using namespace Utils::Tasking;
enum class Handler {
Setup,
Done,
Error,
GroupSetup,
GroupDone,
GroupError
};
using Log = QList<QPair<int, Handler>>;
struct CustomStorage
{
CustomStorage() { ++s_count; }
~CustomStorage() { --s_count; }
Log m_log;
static int instanceCount() { return s_count; }
private:
static int s_count;
};
int CustomStorage::s_count = 0;
static const char s_processIdProperty[] = "__processId";
enum class OnStart { Running, NotRunning };
enum class OnDone { Success, Failure };
struct TestData {
TreeStorage<CustomStorage> storage;
Group root;
Log expectedLog;
int taskCount = 0;
OnStart onStart = OnStart::Running;
OnDone onDone = OnDone::Success;
};
class tst_TaskTree : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void validConstructs(); // compile test
void processTree_data();
void processTree();
void storageOperators();
void storageDestructor();
void cleanupTestCase();
private:
FilePath m_testAppPath;
};
void tst_TaskTree::initTestCase()
{
TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/"
+ Core::Constants::IDE_CASED_ID + "-XXXXXX");
const QString libExecPath(qApp->applicationDirPath() + '/'
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
LauncherInterface::setPathToLauncher(libExecPath);
m_testAppPath = FilePath::fromString(QLatin1String(TESTAPP_PATH)
+ QLatin1String("/testapp")).withExecutableSuffix();
}
void tst_TaskTree::cleanupTestCase()
{
Singleton::deleteAll();
}
void tst_TaskTree::validConstructs()
{
const Group process {
parallel,
Process([](QtcProcess &) {}, [](const QtcProcess &) {}),
Process([](QtcProcess &) {}, [](const QtcProcess &) {}),
Process([](QtcProcess &) {}, [](const QtcProcess &) {})
};
const Group group1 {
process
};
const Group group2 {
parallel,
Group {
parallel,
Process([](QtcProcess &) {}, [](const QtcProcess &) {}),
Group {
parallel,
Process([](QtcProcess &) {}, [](const QtcProcess &) {}),
Group {
parallel,
Process([](QtcProcess &) {}, [](const QtcProcess &) {})
}
},
Group {
parallel,
Process([](QtcProcess &) {}, [](const QtcProcess &) {}),
OnGroupDone([] {})
}
},
process,
OnGroupDone([] {}),
OnGroupError([] {})
};
}
void tst_TaskTree::processTree_data()
{
QTest::addColumn<TestData>("testData");
TreeStorage<CustomStorage> storage;
const auto setupProcessHelper = [storage, testAppPath = m_testAppPath]
(QtcProcess &process, const QStringList &args, int processId) {
process.setCommand(CommandLine(testAppPath, args));
process.setProperty(s_processIdProperty, processId);
storage->m_log.append({processId, Handler::Setup});
};
const auto setupProcess = [setupProcessHelper](int processId) {
return [=](QtcProcess &process) {
setupProcessHelper(process, {"-return", "0"}, processId);
};
};
const auto setupCrashProcess = [setupProcessHelper](int processId) {
return [=](QtcProcess &process) {
setupProcessHelper(process, {"-crash"}, processId);
};
};
const auto setupDynamicProcess = [setupProcessHelper](int processId, TaskAction action) {
return [=](QtcProcess &process) {
setupProcessHelper(process, {"-return", "0"}, processId);
return action;
};
};
const auto readResult = [storage](const QtcProcess &process) {
const int processId = process.property(s_processIdProperty).toInt();
storage->m_log.append({processId, Handler::Done});
};
const auto readError = [storage](const QtcProcess &process) {
const int processId = process.property(s_processIdProperty).toInt();
storage->m_log.append({processId, Handler::Error});
};
const auto groupSetup = [storage](int groupId) {
return [=] { storage->m_log.append({groupId, Handler::GroupSetup}); };
};
const auto groupDone = [storage](int groupId) {
return [=] { storage->m_log.append({groupId, Handler::GroupDone}); };
};
const auto groupError = [storage](int groupId) {
return [=] { storage->m_log.append({groupId, Handler::GroupError}); };
};
const auto constructSimpleSequence = [=](const Workflow &policy) {
return Group {
Storage(storage),
policy,
Process(setupProcess(1), readResult),
Process(setupCrashProcess(2), readResult, readError),
Process(setupProcess(3), readResult),
OnGroupDone(groupDone(0)),
OnGroupError(groupError(0))
};
};
const auto constructDynamicHierarchy = [=](TaskAction taskAction) {
return Group {
Storage(storage),
Group {
Process(setupProcess(1), readResult)
},
Group {
OnGroupSetup([=] { return taskAction; }),
Process(setupProcess(2), readResult),
Process(setupProcess(3), readResult),
Process(setupProcess(4), readResult)
},
OnGroupDone(groupDone(0)),
OnGroupError(groupError(0))
};
};
{
const Group root {
Storage(storage),
OnGroupDone(groupDone(0))
};
const Log log {{0, Handler::GroupDone}};
QTest::newRow("Empty")
<< TestData{storage, root, log, 0, OnStart::NotRunning, OnDone::Success};
}
{
const Group root {
Storage(storage),
Process(setupDynamicProcess(1, TaskAction::StopWithDone), readResult, readError),
Process(setupDynamicProcess(2, TaskAction::StopWithDone), readResult, readError)
};
const Log log {{1, Handler::Setup}, {2, Handler::Setup}};
QTest::newRow("DynamicTaskDone")
<< TestData{storage, root, log, 2, OnStart::NotRunning, OnDone::Success};
}
{
const Group root {
Storage(storage),
Process(setupDynamicProcess(1, TaskAction::StopWithError), readResult, readError),
Process(setupDynamicProcess(2, TaskAction::StopWithError), readResult, readError)
};
const Log log {{1, Handler::Setup}};
QTest::newRow("DynamicTaskError")
<< TestData{storage, root, log, 2, OnStart::NotRunning, OnDone::Failure};
}
{
const Group root {
Storage(storage),
Process(setupDynamicProcess(1, TaskAction::Continue), readResult, readError),
Process(setupDynamicProcess(2, TaskAction::Continue), readResult, readError),
Process(setupDynamicProcess(3, TaskAction::StopWithError), readResult, readError),
Process(setupDynamicProcess(4, TaskAction::Continue), readResult, readError)
};
const Log log {
{1, Handler::Setup},
{1, Handler::Done},
{2, Handler::Setup},
{2, Handler::Done},
{3, Handler::Setup}
};
QTest::newRow("DynamicMixed")
<< TestData{storage, root, log, 4, OnStart::Running, OnDone::Failure};
}
{
const Group root {
parallel,
Storage(storage),
Process(setupDynamicProcess(1, TaskAction::Continue), readResult, readError),
Process(setupDynamicProcess(2, TaskAction::Continue), readResult, readError),
Process(setupDynamicProcess(3, TaskAction::StopWithError), readResult, readError),
Process(setupDynamicProcess(4, TaskAction::Continue), readResult, readError)
};
const Log log {
{1, Handler::Setup},
{2, Handler::Setup},
{3, Handler::Setup},
{1, Handler::Error},
{2, Handler::Error}
};
QTest::newRow("DynamicParallel")
<< TestData{storage, root, log, 4, OnStart::NotRunning, OnDone::Failure};
}
{
const Group root {
parallel,
Storage(storage),
Process(setupDynamicProcess(1, TaskAction::Continue), readResult, readError),
Process(setupDynamicProcess(2, TaskAction::Continue), readResult, readError),
Group {
Process(setupDynamicProcess(3, TaskAction::StopWithError), readResult, readError)
},
Process(setupDynamicProcess(4, TaskAction::Continue), readResult, readError)
};
const Log log {
{1, Handler::Setup},
{2, Handler::Setup},
{3, Handler::Setup},
{1, Handler::Error},
{2, Handler::Error}
};
QTest::newRow("DynamicParallelGroup")
<< TestData{storage, root, log, 4, OnStart::NotRunning, OnDone::Failure};
}
{
const Group root {
parallel,
Storage(storage),
Process(setupDynamicProcess(1, TaskAction::Continue), readResult, readError),
Process(setupDynamicProcess(2, TaskAction::Continue), readResult, readError),
Group {
OnGroupSetup([storage] {
storage->m_log.append({0, Handler::GroupSetup});
return TaskAction::StopWithError;
}),
Process(setupDynamicProcess(3, TaskAction::Continue), readResult, readError)
},
Process(setupDynamicProcess(4, TaskAction::Continue), readResult, readError)
};
const Log log {
{1, Handler::Setup},
{2, Handler::Setup},
{0, Handler::GroupSetup},
{1, Handler::Error},
{2, Handler::Error}
};
QTest::newRow("DynamicParallelGroupSetup")
<< TestData{storage, root, log, 4, OnStart::NotRunning, OnDone::Failure};
}
{
const Group root {
Storage(storage),
Group {
Group {
Group {
Group {
Group {
Process(setupProcess(5), readResult, readError),
OnGroupSetup(groupSetup(5)),
OnGroupDone(groupDone(5))
},
OnGroupSetup(groupSetup(4)),
OnGroupDone(groupDone(4))
},
OnGroupSetup(groupSetup(3)),
OnGroupDone(groupDone(3))
},
OnGroupSetup(groupSetup(2)),
OnGroupDone(groupDone(2))
},
OnGroupSetup(groupSetup(1)),
OnGroupDone(groupDone(1))
},
OnGroupDone(groupDone(0))
};
const Log log {
{1, Handler::GroupSetup},
{2, Handler::GroupSetup},
{3, Handler::GroupSetup},
{4, Handler::GroupSetup},
{5, Handler::GroupSetup},
{5, Handler::Setup},
{5, Handler::Done},
{5, Handler::GroupDone},
{4, Handler::GroupDone},
{3, Handler::GroupDone},
{2, Handler::GroupDone},
{1, Handler::GroupDone},
{0, Handler::GroupDone}
};
QTest::newRow("Nested")
<< TestData{storage, root, log, 1, OnStart::Running, OnDone::Success};
}
{
const auto readResultAnonymous = [=](const QtcProcess &) {
storage->m_log.append({0, Handler::Done});
};
const Group root {
Storage(storage),
parallel,
Process(setupProcess(1), readResultAnonymous),
Process(setupProcess(2), readResultAnonymous),
Process(setupProcess(3), readResultAnonymous),
Process(setupProcess(4), readResultAnonymous),
Process(setupProcess(5), readResultAnonymous),
OnGroupDone(groupDone(0))
};
const Log log {
{1, Handler::Setup}, // Setup order is determined in parallel mode
{2, Handler::Setup},
{3, Handler::Setup},
{4, Handler::Setup},
{5, Handler::Setup},
{0, Handler::Done}, // Done order isn't determined in parallel mode
{0, Handler::Done},
{0, Handler::Done},
{0, Handler::Done},
{0, Handler::Done},
{0, Handler::GroupDone}
};
QTest::newRow("Parallel")
<< TestData{storage, root, log, 5, OnStart::Running, OnDone::Success};
}
{
auto setupSubTree = [=](TaskTree &taskTree) {
const Group nestedRoot {
Storage(storage),
Process(setupProcess(2), readResult),
Process(setupProcess(3), readResult),
Process(setupProcess(4), readResult)
};
taskTree.setupRoot(nestedRoot);
CustomStorage *activeStorage = storage.activeStorage();
auto collectSubLog = [activeStorage](CustomStorage *subTreeStorage){
activeStorage->m_log += subTreeStorage->m_log;
};
taskTree.onStorageDone(storage, collectSubLog);
};
const Group root1 {
Storage(storage),
Process(setupProcess(1), readResult),
Process(setupProcess(2), readResult),
Process(setupProcess(3), readResult),
Process(setupProcess(4), readResult),
Process(setupProcess(5), readResult),
OnGroupDone(groupDone(0))
};
const Group root2 {
Storage(storage),
Group { Process(setupProcess(1), readResult) },
Group { Process(setupProcess(2), readResult) },
Group { Process(setupProcess(3), readResult) },
Group { Process(setupProcess(4), readResult) },
Group { Process(setupProcess(5), readResult) },
OnGroupDone(groupDone(0))
};
const Group root3 {
Storage(storage),
Process(setupProcess(1), readResult),
Tree(setupSubTree),
Process(setupProcess(5), readResult),
OnGroupDone(groupDone(0))
};
const Log log {
{1, Handler::Setup},
{1, Handler::Done},
{2, Handler::Setup},
{2, Handler::Done},
{3, Handler::Setup},
{3, Handler::Done},
{4, Handler::Setup},
{4, Handler::Done},
{5, Handler::Setup},
{5, Handler::Done},
{0, Handler::GroupDone}
};
QTest::newRow("Sequential")
<< TestData{storage, root1, log, 5, OnStart::Running, OnDone::Success};
QTest::newRow("SequentialEncapsulated")
<< TestData{storage, root2, log, 5, OnStart::Running, OnDone::Success};
QTest::newRow("SequentialSubTree") // We don't inspect subtrees, so taskCount is 3, not 5.
<< TestData{storage, root3, log, 3, OnStart::Running, OnDone::Success};
}
{
const Group root {
Storage(storage),
Group {
Process(setupProcess(1), readResult),
Group {
Process(setupProcess(2), readResult),
Group {
Process(setupProcess(3), readResult),
Group {
Process(setupProcess(4), readResult),
Group {
Process(setupProcess(5), readResult),
OnGroupDone(groupDone(5))
},
OnGroupDone(groupDone(4))
},
OnGroupDone(groupDone(3))
},
OnGroupDone(groupDone(2))
},
OnGroupDone(groupDone(1))
},
OnGroupDone(groupDone(0))
};
const Log log {
{1, Handler::Setup},
{1, Handler::Done},
{2, Handler::Setup},
{2, Handler::Done},
{3, Handler::Setup},
{3, Handler::Done},
{4, Handler::Setup},
{4, Handler::Done},
{5, Handler::Setup},
{5, Handler::Done},
{5, Handler::GroupDone},
{4, Handler::GroupDone},
{3, Handler::GroupDone},
{2, Handler::GroupDone},
{1, Handler::GroupDone},
{0, Handler::GroupDone}
};
QTest::newRow("SequentialNested")
<< TestData{storage, root, log, 5, OnStart::Running, OnDone::Success};
}
{
const Group root {
Storage(storage),
Process(setupProcess(1), readResult),
Process(setupProcess(2), readResult),
Process(setupCrashProcess(3), readResult, readError),
Process(setupProcess(4), readResult),
Process(setupProcess(5), readResult),
OnGroupDone(groupDone(0)),
OnGroupError(groupError(0))
};
const Log log {
{1, Handler::Setup},
{1, Handler::Done},
{2, Handler::Setup},
{2, Handler::Done},
{3, Handler::Setup},
{3, Handler::Error},
{0, Handler::GroupError}
};
QTest::newRow("SequentialError")
<< TestData{storage, root, log, 5, OnStart::Running, OnDone::Failure};
}
{
const Group root = constructSimpleSequence(stopOnError);
const Log log {
{1, Handler::Setup},
{1, Handler::Done},
{2, Handler::Setup},
{2, Handler::Error},
{0, Handler::GroupError}
};
QTest::newRow("StopOnError")
<< TestData{storage, root, log, 3, OnStart::Running, OnDone::Failure};
}
{
const Group root = constructSimpleSequence(continueOnError);
const Log log {
{1, Handler::Setup},
{1, Handler::Done},
{2, Handler::Setup},
{2, Handler::Error},
{3, Handler::Setup},
{3, Handler::Done},
{0, Handler::GroupError}
};
QTest::newRow("ContinueOnError")
<< TestData{storage, root, log, 3, OnStart::Running, OnDone::Failure};
}
{
const Group root = constructSimpleSequence(stopOnDone);
const Log log {
{1, Handler::Setup},
{1, Handler::Done},
{0, Handler::GroupDone}
};
QTest::newRow("StopOnDone")
<< TestData{storage, root, log, 3, OnStart::Running, OnDone::Success};
}
{
const Group root = constructSimpleSequence(continueOnDone);
const Log log {
{1, Handler::Setup},
{1, Handler::Done},
{2, Handler::Setup},
{2, Handler::Error},
{3, Handler::Setup},
{3, Handler::Done},
{0, Handler::GroupDone}
};
QTest::newRow("ContinueOnDone")
<< TestData{storage, root, log, 3, OnStart::Running, OnDone::Success};
}
{
const Group root {
Storage(storage),
optional,
Process(setupCrashProcess(1), readResult, readError),
Process(setupCrashProcess(2), readResult, readError),
OnGroupDone(groupDone(0)),
OnGroupError(groupError(0))
};
const Log log {
{1, Handler::Setup},
{1, Handler::Error},
{2, Handler::Setup},
{2, Handler::Error},
{0, Handler::GroupDone}
};
QTest::newRow("Optional")
<< TestData{storage, root, log, 2, OnStart::Running, OnDone::Success};
}
{
const Group root = constructDynamicHierarchy(TaskAction::StopWithDone);
const Log log {
{1, Handler::Setup},
{1, Handler::Done},
{0, Handler::GroupDone}
};
QTest::newRow("DynamicSetupDone")
<< TestData{storage, root, log, 4, OnStart::Running, OnDone::Success};
}
{
const Group root = constructDynamicHierarchy(TaskAction::StopWithError);
const Log log {
{1, Handler::Setup},
{1, Handler::Done},
{0, Handler::GroupError}
};
QTest::newRow("DynamicSetupError")
<< TestData{storage, root, log, 4, OnStart::Running, OnDone::Failure};
}
{
const Group root = constructDynamicHierarchy(TaskAction::Continue);
const Log log {
{1, Handler::Setup},
{1, Handler::Done},
{2, Handler::Setup},
{2, Handler::Done},
{3, Handler::Setup},
{3, Handler::Done},
{4, Handler::Setup},
{4, Handler::Done},
{0, Handler::GroupDone}
};
QTest::newRow("DynamicSetupContinue")
<< TestData{storage, root, log, 4, OnStart::Running, OnDone::Success};
}
{
const Group root {
ParallelLimit(2),
Storage(storage),
Group {
Storage(TreeStorage<CustomStorage>()),
OnGroupSetup(groupSetup(1)),
Group {
parallel,
Process(setupProcess(1))
}
},
Group {
Storage(TreeStorage<CustomStorage>()),
OnGroupSetup(groupSetup(2)),
Group {
parallel,
Process(setupProcess(2))
}
},
Group {
Storage(TreeStorage<CustomStorage>()),
OnGroupSetup(groupSetup(3)),
Group {
parallel,
Process(setupProcess(3))
}
},
Group {
Storage(TreeStorage<CustomStorage>()),
OnGroupSetup(groupSetup(4)),
Group {
parallel,
Process(setupProcess(4))
}
}
};
const Log log {
{1, Handler::GroupSetup},
{1, Handler::Setup},
{2, Handler::GroupSetup},
{2, Handler::Setup},
{3, Handler::GroupSetup},
{3, Handler::Setup},
{4, Handler::GroupSetup},
{4, Handler::Setup}
};
QTest::newRow("NestedParallel")
<< TestData{storage, root, log, 4, OnStart::Running, OnDone::Success};
}
}
void tst_TaskTree::processTree()
{
QFETCH(TestData, testData);
QEventLoop eventLoop;
TaskTree taskTree(testData.root);
QCOMPARE(taskTree.taskCount(), testData.taskCount);
int doneCount = 0;
int errorCount = 0;
connect(&taskTree, &TaskTree::done, this, [&doneCount, &eventLoop] {
++doneCount;
eventLoop.quit();
});
connect(&taskTree, &TaskTree::errorOccurred, this, [&errorCount, &eventLoop] {
++errorCount;
eventLoop.quit();
});
Log actualLog;
auto collectLog = [&actualLog](CustomStorage *storage){
actualLog = storage->m_log;
};
taskTree.onStorageDone(testData.storage, collectLog);
taskTree.start();
const bool expectRunning = testData.onStart == OnStart::Running;
QCOMPARE(taskTree.isRunning(), expectRunning);
if (expectRunning) {
QTimer timer;
bool timedOut = false;
connect(&timer, &QTimer::timeout, &eventLoop, [&eventLoop, &timedOut] {
timedOut = true;
eventLoop.quit();
});
timer.setInterval(2000);
timer.setSingleShot(true);
timer.start();
eventLoop.exec();
QCOMPARE(timedOut, false);
QCOMPARE(taskTree.isRunning(), false);
}
QCOMPARE(taskTree.progressValue(), testData.taskCount);
QCOMPARE(actualLog, testData.expectedLog);
QCOMPARE(CustomStorage::instanceCount(), 0);
const bool expectSuccess = testData.onDone == OnDone::Success;
const int expectedDoneCount = expectSuccess ? 1 : 0;
const int expectedErrorCount = expectSuccess ? 0 : 1;
QCOMPARE(doneCount, expectedDoneCount);
QCOMPARE(errorCount, expectedErrorCount);
}
void tst_TaskTree::storageOperators()
{
TreeStorageBase storage1 = TreeStorage<CustomStorage>();
TreeStorageBase storage2 = TreeStorage<CustomStorage>();
TreeStorageBase storage3 = storage1;
QVERIFY(storage1 == storage3);
QVERIFY(storage1 != storage2);
QVERIFY(storage2 != storage3);
}
void tst_TaskTree::storageDestructor()
{
QCOMPARE(CustomStorage::instanceCount(), 0);
{
const auto setupProcess = [testAppPath = m_testAppPath](QtcProcess &process) {
process.setCommand(CommandLine(testAppPath, {"-sleep", "1"}));
};
const Group root {
Storage(TreeStorage<CustomStorage>()),
Process(setupProcess)
};
TaskTree processTree(root);
QCOMPARE(CustomStorage::instanceCount(), 0);
processTree.start();
QCOMPARE(CustomStorage::instanceCount(), 1);
}
QCOMPARE(CustomStorage::instanceCount(), 0);
}
QTEST_GUILESS_MAIN(tst_TaskTree)
#include "tst_tasktree.moc"