Files
qt-creator/tests/auto/utils/tasktree/tst_tasktree.cpp
Jarek Kobus a4b7e10861 TaskTree: Implement simple progress reporting
The progressMaximum is the number of tasks in tree.
The progressValueChanged signal is emitted whenever task is done
(advance by one) or when some number of tasks were skipped or stopped
due to workflow policy or dynamic setup group handler.

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

600 lines
24 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");
QTest::addColumn<int>("taskCount");
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}};
QTest::newRow("Empty") << emptyRoot << emptyLog << false << true << 0;
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 << nestedLog << true << true << 1;
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
QTest::newRow("Parallel") << parallelRoot << parallelLog << true << true << 5;
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}};
QTest::newRow("Sequential") << sequentialRoot << sequentialLog << true << true << 5;
QTest::newRow("SequentialEncapsulated") << sequentialEncapsulatedRoot << sequentialLog
<< true << true << 5;
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}};
QTest::newRow("SequentialNested") << sequentialNestedRoot << sequentialNestedLog
<< true << true << 5;
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}};
QTest::newRow("SequentialError") << sequentialErrorRoot << sequentialErrorLog
<< true << false << 5;
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}};
QTest::newRow("StopOnError") << stopOnErrorRoot << stopOnErrorLog << true << false << 3;
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}};
QTest::newRow("ContinueOnError") << continueOnErrorRoot << continueOnErrorLog
<< true << false << 3;
const Group stopOnDoneRoot = constructSimpleSequence(stopOnDone);
const Log stopOnDoneLog{{1, Handler::Setup},
{1, Handler::Done},
{-1, Handler::GroupDone}};
QTest::newRow("StopOnDone") << stopOnDoneRoot << stopOnDoneLog
<< true << true << 3;
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}};
QTest::newRow("ContinueOnDone") << continueOnDoneRoot << continueOnDoneLog << true << true << 3;
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}};
QTest::newRow("Optional") << optionalRoot << optionalLog << true << true << 2;
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}};
QTest::newRow("DynamicSetupDone") << dynamicSetupDoneRoot << dynamicSetupDoneLog
<< true << true << 4;
const Group dynamicSetupErrorRoot = constructDynamicSetup({stopWithErrorSetup});
const Log dynamicSetupErrorLog{{1, Handler::Setup},
{1, Handler::Done},
{-1, Handler::GroupError}};
QTest::newRow("DynamicSetupError") << dynamicSetupErrorRoot << dynamicSetupErrorLog
<< true << false << 4;
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}};
QTest::newRow("DynamicSetupAll") << dynamicSetupAllRoot << dynamicSetupAllLog
<< true << true << 4;
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("DynamicSetupSelected") << dynamicSetupSelRoot << dynamicSetupSelLog
<< true << true << 4;
}
void tst_TaskTree::processTree()
{
m_log = {};
using namespace Tasking;
QFETCH(Group, root);
QFETCH(Log, expectedLog);
QFETCH(bool, runningAfterStart);
QFETCH(bool, success);
QFETCH(int, taskCount);
QEventLoop eventLoop;
TaskTree processTree(root);
QCOMPARE(processTree.taskCount(), taskCount);
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(processTree.progressValue(), taskCount);
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"