Files
qt-creator/tests/auto/utils/tasktree/tst_tasktree.cpp
Jarek Kobus 234b3d1e6f TaskTree: Add DynamicSetup handler for groups
DynamicSetup enables finishing group (with Done or Error)
immediately before the group was even started.
The handler is invoked dynamically, just before starting
the group. The handler may also allow for running only
selected children and omitting others, so it may serve as
dynamic condition or dynamic multiplexer. This can be achieved
by modifying QSet<int> childrenToRun field inside returned
GroupConfig and setting the policy to ContinueSelected.

Change-Id: I15bddbb866a89ae9f783c59cc4bca89dd6a81c75
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: hjk <hjk@qt.io>
2022-11-17 15:07:22 +00:00

593 lines
23 KiB
C++

// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 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>
#include <iostream>
#include <fstream>
using namespace Utils;
enum class Handler {
Setup,
Done,
Error,
GroupSetup,
GroupDone,
GroupError
};
using Log = QList<QPair<int, Handler>>;
class tst_TaskTree : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void validConstructs(); // compile test
void processTree_data();
void processTree();
void storage_data();
void storage();
void cleanupTestCase();
private:
Log m_log;
FilePath m_testAppPath;
};
void tst_TaskTree::initTestCase()
{
Utils::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()
{
Utils::Singleton::deleteAll();
}
void tst_TaskTree::validConstructs()
{
using namespace Tasking;
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([] {})
};
}
static const char s_processIdProperty[] = "__processId";
void tst_TaskTree::processTree_data()
{
using namespace Tasking;
using namespace std::placeholders;
QTest::addColumn<Group>("root");
QTest::addColumn<Log>("expectedLog");
QTest::addColumn<bool>("runningAfterStart");
QTest::addColumn<bool>("success");
const auto setupProcessHelper = [this](QtcProcess &process, const QStringList &args, int processId) {
process.setCommand(CommandLine(m_testAppPath, args));
process.setProperty(s_processIdProperty, processId);
m_log.append({processId, Handler::Setup});
};
const auto setupProcess = [setupProcessHelper](QtcProcess &process, int processId) {
setupProcessHelper(process, {"-return", "0"}, processId);
};
// const auto setupErrorProcess = [setupProcessHelper](QtcProcess &process, int processId) {
// setupProcessHelper(process, {"-return", "1"}, processId);
// };
const auto setupCrashProcess = [setupProcessHelper](QtcProcess &process, int processId) {
setupProcessHelper(process, {"-crash"}, processId);
};
const auto readResultAnonymous = [this](const QtcProcess &) {
m_log.append({-1, Handler::Done});
};
const auto readResult = [this](const QtcProcess &process) {
const int processId = process.property(s_processIdProperty).toInt();
m_log.append({processId, Handler::Done});
};
const auto readError = [this](const QtcProcess &process) {
const int processId = process.property(s_processIdProperty).toInt();
m_log.append({processId, Handler::Error});
};
const auto groupSetup = [this](int processId) {
m_log.append({processId, Handler::GroupSetup});
};
const auto groupDone = [this](int processId) {
m_log.append({processId, Handler::GroupDone});
};
const auto rootDone = [this] {
m_log.append({-1, Handler::GroupDone});
};
const auto rootError = [this] {
m_log.append({-1, Handler::GroupError});
};
const Group emptyRoot {
OnGroupDone(rootDone)
};
const Log emptyLog{{-1, Handler::GroupDone}};
const Group nestedRoot {
Group {
Group {
Group {
Group {
Group {
Process(std::bind(setupProcess, _1, 5), readResult),
OnGroupSetup(std::bind(groupSetup, 5)),
OnGroupDone(std::bind(groupDone, 5))
},
OnGroupSetup(std::bind(groupSetup, 4)),
OnGroupDone(std::bind(groupDone, 4))
},
OnGroupSetup(std::bind(groupSetup, 3)),
OnGroupDone(std::bind(groupDone, 3))
},
OnGroupSetup(std::bind(groupSetup, 2)),
OnGroupDone(std::bind(groupDone, 2))
},
OnGroupSetup(std::bind(groupSetup, 1)),
OnGroupDone(std::bind(groupDone, 1))
},
OnGroupDone(rootDone)
};
const Log nestedLog{{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},
{-1, Handler::GroupDone}};
const Group parallelRoot {
parallel,
Process(std::bind(setupProcess, _1, 1), readResultAnonymous),
Process(std::bind(setupProcess, _1, 2), readResultAnonymous),
Process(std::bind(setupProcess, _1, 3), readResultAnonymous),
Process(std::bind(setupProcess, _1, 4), readResultAnonymous),
Process(std::bind(setupProcess, _1, 5), readResultAnonymous),
OnGroupDone(rootDone)
};
const Log parallelLog{{1, Handler::Setup}, // Setup order is determined in parallel mode
{2, Handler::Setup},
{3, Handler::Setup},
{4, Handler::Setup},
{5, Handler::Setup},
{-1, Handler::Done}, // Done order isn't determined in parallel mode
{-1, Handler::Done},
{-1, Handler::Done},
{-1, Handler::Done},
{-1, Handler::Done},
{-1, Handler::GroupDone}}; // Done handlers may come in different order
const Group sequentialRoot {
Process(std::bind(setupProcess, _1, 1), readResult),
Process(std::bind(setupProcess, _1, 2), readResult),
Process(std::bind(setupProcess, _1, 3), readResult),
Process(std::bind(setupProcess, _1, 4), readResult),
Process(std::bind(setupProcess, _1, 5), readResult),
OnGroupDone(rootDone)
};
const Group sequentialEncapsulatedRoot {
Group {
Process(std::bind(setupProcess, _1, 1), readResult)
},
Group {
Process(std::bind(setupProcess, _1, 2), readResult)
},
Group {
Process(std::bind(setupProcess, _1, 3), readResult)
},
Group {
Process(std::bind(setupProcess, _1, 4), readResult)
},
Group {
Process(std::bind(setupProcess, _1, 5), readResult)
},
OnGroupDone(rootDone)
};
const Log sequentialLog{{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},
{-1, Handler::GroupDone}};
const Group sequentialNestedRoot {
Group {
Process(std::bind(setupProcess, _1, 1), readResult),
Group {
Process(std::bind(setupProcess, _1, 2), readResult),
Group {
Process(std::bind(setupProcess, _1, 3), readResult),
Group {
Process(std::bind(setupProcess, _1, 4), readResult),
Group {
Process(std::bind(setupProcess, _1, 5), readResult),
OnGroupDone(std::bind(groupDone, 5))
},
OnGroupDone(std::bind(groupDone, 4))
},
OnGroupDone(std::bind(groupDone, 3))
},
OnGroupDone(std::bind(groupDone, 2))
},
OnGroupDone(std::bind(groupDone, 1))
},
OnGroupDone(rootDone)
};
const Log sequentialNestedLog{{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},
{-1, Handler::GroupDone}};
const Group sequentialErrorRoot {
Process(std::bind(setupProcess, _1, 1), readResult),
Process(std::bind(setupProcess, _1, 2), readResult),
Process(std::bind(setupCrashProcess, _1, 3), readResult, readError),
Process(std::bind(setupProcess, _1, 4), readResult),
Process(std::bind(setupProcess, _1, 5), readResult),
OnGroupDone(rootDone),
OnGroupError(rootError)
};
const Log sequentialErrorLog{{1, Handler::Setup},
{1, Handler::Done},
{2, Handler::Setup},
{2, Handler::Done},
{3, Handler::Setup},
{3, Handler::Error},
{-1, Handler::GroupError}};
const QList<TaskItem> simpleSequence {
Process(std::bind(setupProcess, _1, 1), readResult),
Process(std::bind(setupCrashProcess, _1, 2), readResult, readError),
Process(std::bind(setupProcess, _1, 3), readResult),
OnGroupDone(rootDone),
OnGroupError(rootError)
};
const auto constructSimpleSequence = [=](const WorkflowPolicy &policy) {
return Group {
policy,
Process(std::bind(setupProcess, _1, 1), readResult),
Process(std::bind(setupCrashProcess, _1, 2), readResult, readError),
Process(std::bind(setupProcess, _1, 3), readResult),
OnGroupDone(rootDone),
OnGroupError(rootError)
};
};
const Group stopOnErrorRoot = constructSimpleSequence(stopOnError);
const Log stopOnErrorLog{{1, Handler::Setup},
{1, Handler::Done},
{2, Handler::Setup},
{2, Handler::Error},
{-1, Handler::GroupError}};
const Group continueOnErrorRoot = constructSimpleSequence(continueOnError);
const Log continueOnErrorLog{{1, Handler::Setup},
{1, Handler::Done},
{2, Handler::Setup},
{2, Handler::Error},
{3, Handler::Setup},
{3, Handler::Done},
{-1, Handler::GroupError}};
const Group stopOnDoneRoot = constructSimpleSequence(stopOnDone);
const Log stopOnDoneLog{{1, Handler::Setup},
{1, Handler::Done},
{-1, Handler::GroupDone}};
const Group continueOnDoneRoot = constructSimpleSequence(continueOnDone);
const Log continueOnDoneLog{{1, Handler::Setup},
{1, Handler::Done},
{2, Handler::Setup},
{2, Handler::Error},
{3, Handler::Setup},
{3, Handler::Done},
{-1, Handler::GroupDone}};
const Group optionalRoot {
optional,
Process(std::bind(setupCrashProcess, _1, 1), readResult, readError),
Process(std::bind(setupCrashProcess, _1, 2), readResult, readError),
OnGroupDone(rootDone),
OnGroupError(rootError)
};
const Log optionalLog{{1, Handler::Setup},
{1, Handler::Error},
{2, Handler::Setup},
{2, Handler::Error},
{-1, Handler::GroupDone}};
const auto stopWithDoneSetup = [] { return GroupConfig{GroupAction::StopWithDone}; };
const auto stopWithErrorSetup = [] { return GroupConfig{GroupAction::StopWithError}; };
const auto continueAllSetup = [] { return GroupConfig{GroupAction::ContinueAll}; };
const auto continueSelSetup = [] { return GroupConfig{GroupAction::ContinueSelected, {0, 2}}; };
const auto constructDynamicSetup = [=](const DynamicSetup &dynamicSetup) {
return Group {
Group {
Process(std::bind(setupProcess, _1, 1), readResult)
},
Group {
dynamicSetup,
Process(std::bind(setupProcess, _1, 2), readResult),
Process(std::bind(setupProcess, _1, 3), readResult),
Process(std::bind(setupProcess, _1, 4), readResult)
},
OnGroupDone(rootDone),
OnGroupError(rootError)
};
};
const Group dynamicSetupDoneRoot = constructDynamicSetup({stopWithDoneSetup});
const Log dynamicSetupDoneLog{{1, Handler::Setup},
{1, Handler::Done},
{-1, Handler::GroupDone}};
const Group dynamicSetupErrorRoot = constructDynamicSetup({stopWithErrorSetup});
const Log dynamicSetupErrorLog{{1, Handler::Setup},
{1, Handler::Done},
{-1, Handler::GroupError}};
const Group dynamicSetupAllRoot = constructDynamicSetup({continueAllSetup});
const Log dynamicSetupAllLog{{1, Handler::Setup},
{1, Handler::Done},
{2, Handler::Setup},
{2, Handler::Done},
{3, Handler::Setup},
{3, Handler::Done},
{4, Handler::Setup},
{4, Handler::Done},
{-1, Handler::GroupDone}};
const Group dynamicSetupSelRoot = constructDynamicSetup({continueSelSetup});
const Log dynamicSetupSelLog{{1, Handler::Setup},
{1, Handler::Done},
{2, Handler::Setup},
{2, Handler::Done},
{4, Handler::Setup},
{4, Handler::Done},
{-1, Handler::GroupDone}};
QTest::newRow("Empty") << emptyRoot << emptyLog << false << true;
QTest::newRow("Nested") << nestedRoot << nestedLog << true << true;
QTest::newRow("Parallel") << parallelRoot << parallelLog << true << true;
QTest::newRow("Sequential") << sequentialRoot << sequentialLog << true << true;
QTest::newRow("SequentialEncapsulated") << sequentialEncapsulatedRoot << sequentialLog << true << true;
QTest::newRow("SequentialNested") << sequentialNestedRoot << sequentialNestedLog << true << true;
QTest::newRow("SequentialError") << sequentialErrorRoot << sequentialErrorLog << true << false;
QTest::newRow("StopOnError") << stopOnErrorRoot << stopOnErrorLog << true << false;
QTest::newRow("ContinueOnError") << continueOnErrorRoot << continueOnErrorLog << true << false;
QTest::newRow("StopOnDone") << stopOnDoneRoot << stopOnDoneLog << true << true;
QTest::newRow("ContinueOnDone") << continueOnDoneRoot << continueOnDoneLog << true << true;
QTest::newRow("Optional") << optionalRoot << optionalLog << true << true;
QTest::newRow("DynamicSetupDone") << dynamicSetupDoneRoot << dynamicSetupDoneLog << true << true;
QTest::newRow("DynamicSetupError") << dynamicSetupErrorRoot << dynamicSetupErrorLog << true << false;
QTest::newRow("DynamicSetupAll") << dynamicSetupAllRoot << dynamicSetupAllLog << true << true;
QTest::newRow("DynamicSetupSelected") << dynamicSetupSelRoot << dynamicSetupSelLog << true << true;
}
void tst_TaskTree::processTree()
{
m_log = {};
using namespace Tasking;
QFETCH(Group, root);
QFETCH(Log, expectedLog);
QFETCH(bool, runningAfterStart);
QFETCH(bool, success);
QEventLoop eventLoop;
TaskTree processTree(root);
int doneCount = 0;
int errorCount = 0;
connect(&processTree, &TaskTree::done, this, [&doneCount, &eventLoop] { ++doneCount; eventLoop.quit(); });
connect(&processTree, &TaskTree::errorOccurred, this, [&errorCount, &eventLoop] { ++errorCount; eventLoop.quit(); });
processTree.start();
QCOMPARE(processTree.isRunning(), runningAfterStart);
QTimer timer;
connect(&timer, &QTimer::timeout, &eventLoop, &QEventLoop::quit);
timer.setInterval(1000);
timer.setSingleShot(true);
timer.start();
eventLoop.exec();
QVERIFY(!processTree.isRunning());
QCOMPARE(m_log, expectedLog);
const int expectedDoneCount = success ? 1 : 0;
const int expectedErrorCount = success ? 0 : 1;
QCOMPARE(doneCount, expectedDoneCount);
QCOMPARE(errorCount, expectedErrorCount);
}
void tst_TaskTree::storage_data()
{
using namespace Tasking;
using namespace std::placeholders;
QTest::addColumn<Group>("root");
QTest::addColumn<std::shared_ptr<Log>>("storageLog");
QTest::addColumn<Log>("expectedLog");
QTest::addColumn<bool>("runningAfterStart");
QTest::addColumn<bool>("success");
// TODO: Much better approach would be that the TaskTree, when started, creates a
// storage dynamically (then Group should be a template with storage type as a
// template parameter) and a pointer (or reference) to the storage is being passed
// into handlers (? how ?).
std::shared_ptr<Log> log(new Log);
const auto setupProcessHelper = [this, log](QtcProcess &process, const QStringList &args, int processId) {
process.setCommand(CommandLine(m_testAppPath, args));
process.setProperty(s_processIdProperty, processId);
log->append({processId, Handler::Setup});
};
const auto setupProcess = [setupProcessHelper](QtcProcess &process, int processId) {
setupProcessHelper(process, {"-return", "0"}, processId);
};
const auto readResult = [log](const QtcProcess &process) {
const int processId = process.property(s_processIdProperty).toInt();
log->append({processId, Handler::Done});
};
const auto groupSetup = [log](int processId) {
log->append({processId, Handler::GroupSetup});
};
const auto groupDone = [log](int processId) {
log->append({processId, Handler::GroupDone});
};
const auto rootDone = [log] {
log->append({-1, Handler::GroupDone});
};
const Group nestedRoot {
Group {
Group {
Group {
Group {
Group {
Process(std::bind(setupProcess, _1, 5), readResult),
OnGroupSetup(std::bind(groupSetup, 5)),
OnGroupDone(std::bind(groupDone, 5))
},
OnGroupSetup(std::bind(groupSetup, 4)),
OnGroupDone(std::bind(groupDone, 4))
},
OnGroupSetup(std::bind(groupSetup, 3)),
OnGroupDone(std::bind(groupDone, 3))
},
OnGroupSetup(std::bind(groupSetup, 2)),
OnGroupDone(std::bind(groupDone, 2))
},
OnGroupSetup(std::bind(groupSetup, 1)),
OnGroupDone(std::bind(groupDone, 1))
},
OnGroupDone(rootDone)
};
const Log nestedLog{{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},
{-1, Handler::GroupDone}};
QTest::newRow("Nested") << nestedRoot << log << nestedLog << true << true;
}
void tst_TaskTree::storage()
{
using namespace Tasking;
QFETCH(Group, root);
QFETCH(std::shared_ptr<Log>, storageLog);
QFETCH(Log, expectedLog);
QFETCH(bool, runningAfterStart);
QFETCH(bool, success);
QEventLoop eventLoop;
TaskTree processTree(root);
int doneCount = 0;
int errorCount = 0;
connect(&processTree, &TaskTree::done, this, [&doneCount, &eventLoop] { ++doneCount; eventLoop.quit(); });
connect(&processTree, &TaskTree::errorOccurred, this, [&errorCount, &eventLoop] { ++errorCount; eventLoop.quit(); });
processTree.start();
QCOMPARE(processTree.isRunning(), runningAfterStart);
QTimer timer;
connect(&timer, &QTimer::timeout, &eventLoop, &QEventLoop::quit);
timer.setInterval(1000);
timer.setSingleShot(true);
timer.start();
eventLoop.exec();
QVERIFY(!processTree.isRunning());
QCOMPARE(*storageLog, expectedLog);
const int expectedDoneCount = success ? 1 : 0;
const int expectedErrorCount = success ? 0 : 1;
QCOMPARE(doneCount, expectedDoneCount);
QCOMPARE(errorCount, expectedErrorCount);
}
QTEST_GUILESS_MAIN(tst_TaskTree)
#include "tst_tasktree.moc"