diff options
Diffstat (limited to 'tests/auto/solutions/tasking')
-rw-r--r-- | tests/auto/solutions/tasking/CMakeLists.txt | 4 | ||||
-rw-r--r-- | tests/auto/solutions/tasking/tasking.qbs | 7 | ||||
-rw-r--r-- | tests/auto/solutions/tasking/tst_tasking.cpp | 1592 |
3 files changed, 1603 insertions, 0 deletions
diff --git a/tests/auto/solutions/tasking/CMakeLists.txt b/tests/auto/solutions/tasking/CMakeLists.txt new file mode 100644 index 0000000000..534d215ccb --- /dev/null +++ b/tests/auto/solutions/tasking/CMakeLists.txt @@ -0,0 +1,4 @@ +add_qtc_test(tst_solutions_tasking + DEPENDS Utils + SOURCES tst_tasking.cpp +) diff --git a/tests/auto/solutions/tasking/tasking.qbs b/tests/auto/solutions/tasking/tasking.qbs new file mode 100644 index 0000000000..173c1fc575 --- /dev/null +++ b/tests/auto/solutions/tasking/tasking.qbs @@ -0,0 +1,7 @@ +QtcAutotest { + name: "Tasking autotest" + + Depends { name: "Utils" } + + files: "tst_tasking.cpp" +} diff --git a/tests/auto/solutions/tasking/tst_tasking.cpp b/tests/auto/solutions/tasking/tst_tasking.cpp new file mode 100644 index 0000000000..9efba89bb2 --- /dev/null +++ b/tests/auto/solutions/tasking/tst_tasking.cpp @@ -0,0 +1,1592 @@ +// 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 <solutions/tasking/barrier.h> + +#include <utils/async.h> + +#include <QtTest> + +#include <QDeadlineTimer> + +using namespace std::literals::chrono_literals; + +using namespace Utils; +using namespace Tasking; + +using TestTask = Async<void>; +using Test = AsyncTask<void>; + +enum class Handler { + Setup, + Done, + Error, + GroupSetup, + GroupDone, + GroupError, + Sync, + BarrierAdvance, +}; + +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_taskIdProperty[] = "__taskId"; + +static FutureSynchronizer *s_futureSynchronizer = nullptr; + +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_Tasking : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + + void validConstructs(); // compile test + void testTree_data(); + void testTree(); + void storageOperators(); + void storageDestructor(); + + void cleanupTestCase(); +}; + +void tst_Tasking::initTestCase() +{ + s_futureSynchronizer = new FutureSynchronizer; +} + +void tst_Tasking::cleanupTestCase() +{ + delete s_futureSynchronizer; + s_futureSynchronizer = nullptr; +} + +void tst_Tasking::validConstructs() +{ + const Group task { + parallel, + Test([](TestTask &) {}, [](const TestTask &) {}), + Test([](TestTask &) {}, [](const TestTask &) {}), + Test([](TestTask &) {}, [](const TestTask &) {}) + }; + + const Group group1 { + task + }; + + const Group group2 { + parallel, + Group { + parallel, + Test([](TestTask &) {}, [](const TestTask &) {}), + Group { + parallel, + Test([](TestTask &) {}, [](const TestTask &) {}), + Group { + parallel, + Test([](TestTask &) {}, [](const TestTask &) {}) + } + }, + Group { + parallel, + Test([](TestTask &) {}, [](const TestTask &) {}), + OnGroupDone([] {}) + } + }, + task, + OnGroupDone([] {}), + OnGroupError([] {}) + }; + + const auto setupHandler = [](TestTask &) {}; + const auto doneHandler = [](const TestTask &) {}; + const auto errorHandler = [](const TestTask &) {}; + + // Not fluent interface + + const Group task2 { + parallel, + Test(setupHandler), + Test(setupHandler, doneHandler), + Test(setupHandler, doneHandler, errorHandler), + // need to explicitly pass empty handler for done + Test(setupHandler, {}, errorHandler) + }; + + // Fluent interface + + const Group fluent { + parallel, + Test().onSetup(setupHandler), + Test().onSetup(setupHandler).onDone(doneHandler), + Test().onSetup(setupHandler).onDone(doneHandler).onError(errorHandler), + // possible to skip the empty done + Test().onSetup(setupHandler).onError(errorHandler), + // possible to set handlers in a different order + Test().onError(errorHandler).onDone(doneHandler).onSetup(setupHandler), + }; + + + // When turning each of below blocks on, you should see the specific compiler error message. + +#if 0 + { + // "Sync element: The synchronous function has to return void or bool." + const auto setupSync = [] { return 3; }; + const Sync sync(setupSync); + } +#endif + +#if 0 + { + // "Sync element: The synchronous function can't take any arguments." + const auto setupSync = [](int) { }; + const Sync sync(setupSync); + } +#endif + +#if 0 + { + // "Sync element: The synchronous function can't take any arguments." + const auto setupSync = [](int) { return true; }; + const Sync sync(setupSync); + } +#endif +} + +static void runTask(QPromise<void> &promise, bool success, std::chrono::milliseconds sleep) +{ + QDeadlineTimer deadline(sleep); + while (!deadline.hasExpired()) { + QThread::msleep(1); + if (promise.isCanceled()) + return; + } + if (!success) + promise.future().cancel(); +} + +static void reportAndSleep(QPromise<bool> &promise) +{ + promise.addResult(false); + QThread::msleep(5); +}; + +template <typename SharedBarrierType> +auto setupBarrierAdvance(const TreeStorage<CustomStorage> &storage, + const SharedBarrierType &barrier, int taskId) +{ + return [storage, barrier, taskId](Async<bool> &async) { + async.setFutureSynchronizer(s_futureSynchronizer); + async.setConcurrentCallData(reportAndSleep); + async.setProperty(s_taskIdProperty, taskId); + storage->m_log.append({taskId, Handler::Setup}); + + CustomStorage *currentStorage = storage.activeStorage(); + Barrier *sharedBarrier = barrier->barrier(); + QObject::connect(&async, &TestTask::resultReadyAt, sharedBarrier, + [currentStorage, sharedBarrier, taskId](int index) { + Q_UNUSED(index) + currentStorage->m_log.append({taskId, Handler::BarrierAdvance}); + sharedBarrier->advance(); + }); + }; +} + +void tst_Tasking::testTree_data() +{ + QTest::addColumn<TestData>("testData"); + + TreeStorage<CustomStorage> storage; + + const auto setupTaskHelper = [storage](TestTask &task, int taskId, bool success = true, + std::chrono::milliseconds sleep = 0ms) { + task.setFutureSynchronizer(s_futureSynchronizer); + task.setConcurrentCallData(runTask, success, sleep); + task.setProperty(s_taskIdProperty, taskId); + storage->m_log.append({taskId, Handler::Setup}); + }; + const auto setupTask = [setupTaskHelper](int taskId) { + return [=](TestTask &task) { setupTaskHelper(task, taskId); }; + }; + const auto setupFailingTask = [setupTaskHelper](int taskId) { + return [=](TestTask &task) { setupTaskHelper(task, taskId, false); }; + }; + const auto setupSleepingTask = [setupTaskHelper](int taskId, std::chrono::milliseconds sleep) { + return [=](TestTask &task) { setupTaskHelper(task, taskId, true, sleep); }; + }; + const auto setupDynamicTask = [setupTaskHelper](int taskId, TaskAction action) { + return [=](TestTask &task) { + setupTaskHelper(task, taskId); + return action; + }; + }; + const auto logDone = [storage](const TestTask &task) { + storage->m_log.append({task.property(s_taskIdProperty).toInt(), Handler::Done}); + }; + const auto logError = [storage](const TestTask &task) { + storage->m_log.append({task.property(s_taskIdProperty).toInt(), Handler::Error}); + }; + const auto groupSetup = [storage](int taskId) { + return [=] { storage->m_log.append({taskId, Handler::GroupSetup}); }; + }; + const auto groupDone = [storage](int taskId) { + return [=] { storage->m_log.append({taskId, Handler::GroupDone}); }; + }; + const auto groupError = [storage](int taskId) { + return [=] { storage->m_log.append({taskId, Handler::GroupError}); }; + }; + const auto setupSync = [storage](int taskId) { + return [=] { storage->m_log.append({taskId, Handler::Sync}); }; + }; + const auto setupSyncWithReturn = [storage](int taskId, bool success) { + return [=] { storage->m_log.append({taskId, Handler::Sync}); return success; }; + }; + + const auto constructSimpleSequence = [=](const Workflow &policy) { + return Group { + Storage(storage), + policy, + Test(setupTask(1), logDone), + Test(setupFailingTask(2), logDone, logError), + Test(setupTask(3), logDone), + OnGroupDone(groupDone(0)), + OnGroupError(groupError(0)) + }; + }; + const auto constructDynamicHierarchy = [=](TaskAction taskAction) { + return Group { + Storage(storage), + Group { + Test(setupTask(1), logDone) + }, + Group { + OnGroupSetup([=] { return taskAction; }), + Test(setupTask(2), logDone), + Test(setupTask(3), logDone), + Test(setupTask(4), logDone) + }, + OnGroupDone(groupDone(0)), + OnGroupError(groupError(0)) + }; + }; + + { + const Group root1 { + Storage(storage), + OnGroupDone(groupDone(0)), + OnGroupError(groupError(0)) + }; + const Group root2 { + Storage(storage), + OnGroupSetup([] { return TaskAction::Continue; }), + OnGroupDone(groupDone(0)), + OnGroupError(groupError(0)) + }; + const Group root3 { + Storage(storage), + OnGroupSetup([] { return TaskAction::StopWithDone; }), + OnGroupDone(groupDone(0)), + OnGroupError(groupError(0)) + }; + const Group root4 { + Storage(storage), + OnGroupSetup([] { return TaskAction::StopWithError; }), + OnGroupDone(groupDone(0)), + OnGroupError(groupError(0)) + }; + const Log logDone {{0, Handler::GroupDone}}; + const Log logError {{0, Handler::GroupError}}; + QTest::newRow("Empty") + << TestData{storage, root1, logDone, 0, OnStart::NotRunning, OnDone::Success}; + QTest::newRow("EmptyContinue") + << TestData{storage, root2, logDone, 0, OnStart::NotRunning, OnDone::Success}; + QTest::newRow("EmptyDone") + << TestData{storage, root3, logDone, 0, OnStart::NotRunning, OnDone::Success}; + QTest::newRow("EmptyError") + << TestData{storage, root4, logError, 0, OnStart::NotRunning, OnDone::Failure}; + } + + { + const Group root { + Storage(storage), + Test(setupDynamicTask(1, TaskAction::StopWithDone), logDone, logError), + Test(setupDynamicTask(2, TaskAction::StopWithDone), logDone, logError) + }; + 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), + Test(setupDynamicTask(1, TaskAction::StopWithError), logDone, logError), + Test(setupDynamicTask(2, TaskAction::StopWithError), logDone, logError) + }; + const Log log {{1, Handler::Setup}}; + QTest::newRow("DynamicTaskError") + << TestData{storage, root, log, 2, OnStart::NotRunning, OnDone::Failure}; + } + + { + const Group root { + Storage(storage), + Test(setupDynamicTask(1, TaskAction::Continue), logDone, logError), + Test(setupDynamicTask(2, TaskAction::Continue), logDone, logError), + Test(setupDynamicTask(3, TaskAction::StopWithError), logDone, logError), + Test(setupDynamicTask(4, TaskAction::Continue), logDone, logError) + }; + 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), + Test(setupDynamicTask(1, TaskAction::Continue), logDone, logError), + Test(setupDynamicTask(2, TaskAction::Continue), logDone, logError), + Test(setupDynamicTask(3, TaskAction::StopWithError), logDone, logError), + Test(setupDynamicTask(4, TaskAction::Continue), logDone, logError) + }; + 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), + Test(setupDynamicTask(1, TaskAction::Continue), logDone, logError), + Test(setupDynamicTask(2, TaskAction::Continue), logDone, logError), + Group { + Test(setupDynamicTask(3, TaskAction::StopWithError), logDone, logError) + }, + Test(setupDynamicTask(4, TaskAction::Continue), logDone, logError) + }; + 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), + Test(setupDynamicTask(1, TaskAction::Continue), logDone, logError), + Test(setupDynamicTask(2, TaskAction::Continue), logDone, logError), + Group { + OnGroupSetup([storage] { + storage->m_log.append({0, Handler::GroupSetup}); + return TaskAction::StopWithError; + }), + Test(setupDynamicTask(3, TaskAction::Continue), logDone, logError) + }, + Test(setupDynamicTask(4, TaskAction::Continue), logDone, logError) + }; + 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 { + Test(setupTask(5), logDone, logError), + 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 logDoneAnonymously = [=](const TestTask &) { + storage->m_log.append({0, Handler::Done}); + }; + const Group root { + Storage(storage), + parallel, + Test(setupTask(1), logDoneAnonymously), + Test(setupTask(2), logDoneAnonymously), + Test(setupTask(3), logDoneAnonymously), + Test(setupTask(4), logDoneAnonymously), + Test(setupTask(5), logDoneAnonymously), + 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), + Test(setupTask(2), logDone), + Test(setupTask(3), logDone), + Test(setupTask(4), logDone) + }; + 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), + Test(setupTask(1), logDone), + Test(setupTask(2), logDone), + Test(setupTask(3), logDone), + Test(setupTask(4), logDone), + Test(setupTask(5), logDone), + OnGroupDone(groupDone(0)) + }; + const Group root2 { + Storage(storage), + Group { Test(setupTask(1), logDone) }, + Group { Test(setupTask(2), logDone) }, + Group { Test(setupTask(3), logDone) }, + Group { Test(setupTask(4), logDone) }, + Group { Test(setupTask(5), logDone) }, + OnGroupDone(groupDone(0)) + }; + const Group root3 { + Storage(storage), + Test(setupTask(1), logDone), + TaskTreeTask(setupSubTree), + Test(setupTask(5), logDone), + 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 { + Test(setupTask(1), logDone), + Group { + Test(setupTask(2), logDone), + Group { + Test(setupTask(3), logDone), + Group { + Test(setupTask(4), logDone), + Group { + Test(setupTask(5), logDone), + 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), + Test(setupTask(1), logDone), + Test(setupTask(2), logDone), + Test(setupFailingTask(3), logDone, logError), + Test(setupTask(4), logDone), + Test(setupTask(5), logDone), + 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, + Test(setupFailingTask(1), logDone, logError), + Test(setupFailingTask(2), logDone, logError), + 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 { + OnGroupSetup(groupSetup(1)), + Test(setupTask(1)) + }, + Group { + OnGroupSetup(groupSetup(2)), + Test(setupTask(2)) + }, + Group { + OnGroupSetup(groupSetup(3)), + Test(setupTask(3)) + }, + Group { + OnGroupSetup(groupSetup(4)), + Test(setupTask(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}; + } + + { + const Group root { + ParallelLimit(2), + Storage(storage), + Group { + OnGroupSetup(groupSetup(1)), + Test(setupTask(1)) + }, + Group { + OnGroupSetup(groupSetup(2)), + Test(setupTask(2)) + }, + Group { + OnGroupSetup(groupSetup(3)), + Test(setupDynamicTask(3, TaskAction::StopWithDone)) + }, + Group { + OnGroupSetup(groupSetup(4)), + Test(setupTask(4)) + }, + Group { + OnGroupSetup(groupSetup(5)), + Test(setupTask(5)) + } + }; + 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}, + {5, Handler::GroupSetup}, + {5, Handler::Setup} + }; + QTest::newRow("NestedParallelDone") + << TestData{storage, root, log, 5, OnStart::Running, OnDone::Success}; + } + + { + const Group root1 { + ParallelLimit(2), + Storage(storage), + Group { + OnGroupSetup(groupSetup(1)), + Test(setupTask(1)) + }, + Group { + OnGroupSetup(groupSetup(2)), + Test(setupTask(2)) + }, + Group { + OnGroupSetup(groupSetup(3)), + Test(setupDynamicTask(3, TaskAction::StopWithError)) + }, + Group { + OnGroupSetup(groupSetup(4)), + Test(setupTask(4)) + }, + Group { + OnGroupSetup(groupSetup(5)), + Test(setupTask(5)) + } + }; + + // Inside this test the task 2 should finish first, then synchonously: + // - task 3 should exit setup with error + // - task 1 should be stopped as a consequence of error inside the group + // - tasks 4 and 5 should be skipped + const Group root2 { + ParallelLimit(2), + Storage(storage), + Group { + OnGroupSetup(groupSetup(1)), + Test(setupSleepingTask(1, 10ms)) + }, + Group { + OnGroupSetup(groupSetup(2)), + Test(setupTask(2)) + }, + Group { + OnGroupSetup(groupSetup(3)), + Test(setupDynamicTask(3, TaskAction::StopWithError)) + }, + Group { + OnGroupSetup(groupSetup(4)), + Test(setupTask(4)) + }, + Group { + OnGroupSetup(groupSetup(5)), + Test(setupTask(5)) + } + }; + + // This test ensures that the task 1 doesn't invoke its done handler, + // being ready while sleeping in the task's 2 done handler. + // Inside this test the task 2 should finish first, then synchonously: + // - task 3 should exit setup with error + // - task 1 should be stopped as a consequence of error inside the group + // - task 4 should be skipped + // - the first child group of root should finish with error + // - task 5 should be started (because of root's continueOnError policy) + const Group root3 { + continueOnError, + Storage(storage), + Group { + ParallelLimit(2), + Group { + OnGroupSetup(groupSetup(1)), + Test(setupSleepingTask(1, 20ms)) + }, + Group { + OnGroupSetup(groupSetup(2)), + Test(setupTask(2), [](const TestTask &) { QThread::msleep(10); }) + }, + Group { + OnGroupSetup(groupSetup(3)), + Test(setupDynamicTask(3, TaskAction::StopWithError)) + }, + Group { + OnGroupSetup(groupSetup(4)), + Test(setupTask(4)) + } + }, + Group { + OnGroupSetup(groupSetup(5)), + Test(setupTask(5)) + } + }; + const Log shortLog { + {1, Handler::GroupSetup}, + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {3, Handler::GroupSetup}, + {3, Handler::Setup} + }; + const Log longLog = shortLog + Log {{5, Handler::GroupSetup}, {5, Handler::Setup}}; + QTest::newRow("NestedParallelError1") + << TestData{storage, root1, shortLog, 5, OnStart::Running, OnDone::Failure}; + QTest::newRow("NestedParallelError2") + << TestData{storage, root2, shortLog, 5, OnStart::Running, OnDone::Failure}; + QTest::newRow("NestedParallelError3") + << TestData{storage, root3, longLog, 5, OnStart::Running, OnDone::Failure}; + } + + { + const Group root { + ParallelLimit(2), + Storage(storage), + Group { + Storage(TreeStorage<CustomStorage>()), + OnGroupSetup(groupSetup(1)), + Group { + parallel, + Test(setupTask(1)) + } + }, + Group { + Storage(TreeStorage<CustomStorage>()), + OnGroupSetup(groupSetup(2)), + Group { + parallel, + Test(setupTask(2)) + } + }, + Group { + Storage(TreeStorage<CustomStorage>()), + OnGroupSetup(groupSetup(3)), + Group { + parallel, + Test(setupTask(3)) + } + }, + Group { + Storage(TreeStorage<CustomStorage>()), + OnGroupSetup(groupSetup(4)), + Group { + parallel, + Test(setupTask(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("DeeplyNestedParallel") + << TestData{storage, root, log, 4, OnStart::Running, OnDone::Success}; + } + + { + const Group root { + ParallelLimit(2), + Storage(storage), + Group { + Storage(TreeStorage<CustomStorage>()), + OnGroupSetup(groupSetup(1)), + Group { Test(setupTask(1)) } + }, + Group { + Storage(TreeStorage<CustomStorage>()), + OnGroupSetup(groupSetup(2)), + Group { Test(setupTask(2)) } + }, + Group { + Storage(TreeStorage<CustomStorage>()), + OnGroupSetup(groupSetup(3)), + Group { Test(setupDynamicTask(3, TaskAction::StopWithDone)) } + }, + Group { + Storage(TreeStorage<CustomStorage>()), + OnGroupSetup(groupSetup(4)), + Group { Test(setupTask(4)) } + }, + Group { + Storage(TreeStorage<CustomStorage>()), + OnGroupSetup(groupSetup(5)), + Group { Test(setupTask(5)) } + } + }; + 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}, + {5, Handler::GroupSetup}, + {5, Handler::Setup} + }; + QTest::newRow("DeeplyNestedParallelDone") + << TestData{storage, root, log, 5, OnStart::Running, OnDone::Success}; + } + + { + const Group root { + ParallelLimit(2), + Storage(storage), + Group { + Storage(TreeStorage<CustomStorage>()), + OnGroupSetup(groupSetup(1)), + Group { Test(setupTask(1)) } + }, + Group { + Storage(TreeStorage<CustomStorage>()), + OnGroupSetup(groupSetup(2)), + Group { Test(setupTask(2)) } + }, + Group { + Storage(TreeStorage<CustomStorage>()), + OnGroupSetup(groupSetup(3)), + Group { Test(setupDynamicTask(3, TaskAction::StopWithError)) } + }, + Group { + Storage(TreeStorage<CustomStorage>()), + OnGroupSetup(groupSetup(4)), + Group { Test(setupTask(4)) } + }, + Group { + Storage(TreeStorage<CustomStorage>()), + OnGroupSetup(groupSetup(5)), + Group { Test(setupTask(5)) } + } + }; + const Log log { + {1, Handler::GroupSetup}, + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {3, Handler::GroupSetup}, + {3, Handler::Setup} + }; + QTest::newRow("DeeplyNestedParallelError") + << TestData{storage, root, log, 5, OnStart::Running, OnDone::Failure}; + } + + { + const Group root { + Storage(storage), + Sync(setupSync(1)), + Sync(setupSync(2)), + Sync(setupSync(3)), + Sync(setupSync(4)), + Sync(setupSync(5)) + }; + const Log log { + {1, Handler::Sync}, + {2, Handler::Sync}, + {3, Handler::Sync}, + {4, Handler::Sync}, + {5, Handler::Sync} + }; + QTest::newRow("SyncSequential") + << TestData{storage, root, log, 0, OnStart::NotRunning, OnDone::Success}; + } + + { + const Group root { + Storage(storage), + Sync(setupSyncWithReturn(1, true)), + Sync(setupSyncWithReturn(2, true)), + Sync(setupSyncWithReturn(3, true)), + Sync(setupSyncWithReturn(4, true)), + Sync(setupSyncWithReturn(5, true)) + }; + const Log log { + {1, Handler::Sync}, + {2, Handler::Sync}, + {3, Handler::Sync}, + {4, Handler::Sync}, + {5, Handler::Sync} + }; + QTest::newRow("SyncWithReturn") + << TestData{storage, root, log, 0, OnStart::NotRunning, OnDone::Success}; + } + + { + const Group root { + Storage(storage), + parallel, + Sync(setupSync(1)), + Sync(setupSync(2)), + Sync(setupSync(3)), + Sync(setupSync(4)), + Sync(setupSync(5)) + }; + const Log log { + {1, Handler::Sync}, + {2, Handler::Sync}, + {3, Handler::Sync}, + {4, Handler::Sync}, + {5, Handler::Sync} + }; + QTest::newRow("SyncParallel") + << TestData{storage, root, log, 0, OnStart::NotRunning, OnDone::Success}; + } + + { + const Group root { + Storage(storage), + parallel, + Sync(setupSync(1)), + Sync(setupSync(2)), + Sync(setupSyncWithReturn(3, false)), + Sync(setupSync(4)), + Sync(setupSync(5)) + }; + const Log log { + {1, Handler::Sync}, + {2, Handler::Sync}, + {3, Handler::Sync} + }; + QTest::newRow("SyncError") + << TestData{storage, root, log, 0, OnStart::NotRunning, OnDone::Failure}; + } + + { + const Group root { + Storage(storage), + Sync(setupSync(1)), + Test(setupTask(2)), + Sync(setupSync(3)), + Test(setupTask(4)), + Sync(setupSync(5)), + OnGroupDone(groupDone(0)) + }; + const Log log { + {1, Handler::Sync}, + {2, Handler::Setup}, + {3, Handler::Sync}, + {4, Handler::Setup}, + {5, Handler::Sync}, + {0, Handler::GroupDone} + }; + QTest::newRow("SyncAndAsync") + << TestData{storage, root, log, 2, OnStart::Running, OnDone::Success}; + } + + { + const Group root { + Storage(storage), + Sync(setupSync(1)), + Test(setupTask(2)), + Sync(setupSyncWithReturn(3, false)), + Test(setupTask(4)), + Sync(setupSync(5)), + OnGroupError(groupError(0)) + }; + const Log log { + {1, Handler::Sync}, + {2, Handler::Setup}, + {3, Handler::Sync}, + {0, Handler::GroupError} + }; + QTest::newRow("SyncAndAsyncError") + << TestData{storage, root, log, 2, OnStart::Running, OnDone::Failure}; + } + + { + SingleBarrier barrier; + + // Test that barrier advance, triggered from inside the task described by + // setupBarrierAdvance, placed BEFORE the group containing the waitFor() element + // in the tree order, works OK in SEQUENTIAL mode. + const Group root1 { + Storage(storage), + Storage(barrier), + sequential, + AsyncTask<bool>(setupBarrierAdvance(storage, barrier, 1)), + Group { + OnGroupSetup(groupSetup(2)), + WaitForBarrierTask(barrier), + Test(setupTask(2)), + Test(setupTask(3)) + } + }; + const Log log1 { + {1, Handler::Setup}, + {1, Handler::BarrierAdvance}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {3, Handler::Setup} + }; + + // Test that barrier advance, triggered from inside the task described by + // setupTaskWithCondition, placed BEFORE the group containing the waitFor() element + // in the tree order, works OK in PARALLEL mode. + const Group root2 { + Storage(storage), + Storage(barrier), + parallel, + AsyncTask<bool>(setupBarrierAdvance(storage, barrier, 1)), + Group { + OnGroupSetup(groupSetup(2)), + WaitForBarrierTask(barrier), + Test(setupTask(2)), + Test(setupTask(3)) + } + }; + const Log log2 { + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {1, Handler::BarrierAdvance}, + {2, Handler::Setup}, + {3, Handler::Setup} + }; + + // Test that barrier advance, triggered from inside the task described by + // setupTaskWithCondition, placed AFTER the group containing the waitFor() element + // in the tree order, works OK in PARALLEL mode. + // + // Notice: This won't work in SEQUENTIAL mode, since the advancing barrier, placed after the + // group containing the WaitFor element, has no chance to be started in SEQUENTIAL mode, + // as in SEQUENTIAL mode the next task may only be started after the previous one finished. + // In this case, the previous task (Group element) awaits for the barrier's advance to + // come from the not yet started next task, causing a deadlock. + // The minimal requirement for this scenario to succeed is to set ParallelLimit(2) or more. + const Group root3 { + Storage(storage), + Storage(barrier), + parallel, + Group { + OnGroupSetup(groupSetup(2)), + WaitForBarrierTask(barrier), + Test(setupTask(2)), + Test(setupTask(3)) + }, + AsyncTask<bool>(setupBarrierAdvance(storage, barrier, 1)) + }; + const Log log3 { + {2, Handler::GroupSetup}, + {1, Handler::Setup}, + {1, Handler::BarrierAdvance}, + {2, Handler::Setup}, + {3, Handler::Setup} + }; + + // Test that barrier advance, triggered from inside the task described by + // setupBarrierAdvance, placed BEFORE the groups containing the waitFor() element + // in the tree order, wakes both waitFor tasks. + const Group root4 { + Storage(storage), + Storage(barrier), + parallel, + AsyncTask<bool>(setupBarrierAdvance(storage, barrier, 1)), + Group { + OnGroupSetup(groupSetup(2)), + WaitForBarrierTask(barrier), + Test(setupTask(4)) + }, + Group { + OnGroupSetup(groupSetup(3)), + WaitForBarrierTask(barrier), + Test(setupTask(5)) + } + }; + const Log log4 { + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {3, Handler::GroupSetup}, + {1, Handler::BarrierAdvance}, + {4, Handler::Setup}, + {5, Handler::Setup} + }; + + // Test two separate single barriers. + + SingleBarrier barrier2; + + const Group root5 { + Storage(storage), + Storage(barrier), + Storage(barrier2), + parallel, + AsyncTask<bool>(setupBarrierAdvance(storage, barrier, 0)), + AsyncTask<bool>(setupBarrierAdvance(storage, barrier2, 0)), + Group { + Group { + parallel, + OnGroupSetup(groupSetup(1)), + WaitForBarrierTask(barrier), + WaitForBarrierTask(barrier2) + }, + Test(setupTask(2)) + }, + }; + const Log log5 { + {0, Handler::Setup}, + {0, Handler::Setup}, + {1, Handler::GroupSetup}, + {0, Handler::BarrierAdvance}, + {0, Handler::BarrierAdvance}, + {2, Handler::Setup} + }; + + // Notice the different log order for each scenario. + QTest::newRow("BarrierSequential") + << TestData{storage, root1, log1, 4, OnStart::Running, OnDone::Success}; + QTest::newRow("BarrierParallelAdvanceFirst") + << TestData{storage, root2, log2, 4, OnStart::Running, OnDone::Success}; + QTest::newRow("BarrierParallelWaitForFirst") + << TestData{storage, root3, log3, 4, OnStart::Running, OnDone::Success}; + QTest::newRow("BarrierParallelMultiWaitFor") + << TestData{storage, root4, log4, 5, OnStart::Running, OnDone::Success}; + QTest::newRow("BarrierParallelTwoSingleBarriers") + << TestData{storage, root5, log5, 5, OnStart::Running, OnDone::Success}; + } + + { + MultiBarrier<2> barrier; + + // Test that multi barrier advance, triggered from inside the tasks described by + // setupBarrierAdvance, placed BEFORE the group containing the waitFor() element + // in the tree order, works OK in SEQUENTIAL mode. + const Group root1 { + Storage(storage), + Storage(barrier), + sequential, + AsyncTask<bool>(setupBarrierAdvance(storage, barrier, 1)), + AsyncTask<bool>(setupBarrierAdvance(storage, barrier, 2)), + Group { + OnGroupSetup(groupSetup(2)), + WaitForBarrierTask(barrier), + Test(setupTask(2)), + Test(setupTask(3)) + } + }; + const Log log1 { + {1, Handler::Setup}, + {1, Handler::BarrierAdvance}, + {2, Handler::Setup}, + {2, Handler::BarrierAdvance}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {3, Handler::Setup} + }; + + // Test that multi barrier advance, triggered from inside the tasks described by + // setupBarrierAdvance, placed BEFORE the group containing the waitFor() element + // in the tree order, works OK in PARALLEL mode. + const Group root2 { + Storage(storage), + Storage(barrier), + parallel, + AsyncTask<bool>(setupBarrierAdvance(storage, barrier, 0)), + AsyncTask<bool>(setupBarrierAdvance(storage, barrier, 0)), + Group { + OnGroupSetup(groupSetup(2)), + WaitForBarrierTask(barrier), + Test(setupTask(2)), + Test(setupTask(3)) + } + }; + const Log log2 { + {0, Handler::Setup}, + {0, Handler::Setup}, + {2, Handler::GroupSetup}, + {0, Handler::BarrierAdvance}, // Barrier advances may come in different order in + {0, Handler::BarrierAdvance}, // parallel mode, that's why id = 0 (same for both). + {2, Handler::Setup}, + {3, Handler::Setup} + }; + + // Test that multi barrier advance, triggered from inside the tasks described by + // setupBarrierAdvance, placed AFTER the group containing the waitFor() element + // in the tree order, works OK in PARALLEL mode. + // + // Notice: This won't work in SEQUENTIAL mode, since the advancing barriers, placed after + // the group containing the WaitFor element, has no chance to be started in SEQUENTIAL mode, + // as in SEQUENTIAL mode the next task may only be started after the previous one finished. + // In this case, the previous task (Group element) awaits for the barrier's advance to + // come from the not yet started next task, causing a deadlock. + // The minimal requirement for this scenario to succeed is to set ParallelLimit(2) or more. + const Group root3 { + Storage(storage), + Storage(barrier), + parallel, + Group { + OnGroupSetup(groupSetup(2)), + WaitForBarrierTask(barrier), + Test(setupTask(2)), + Test(setupTask(3)) + }, + AsyncTask<bool>(setupBarrierAdvance(storage, barrier, 0)), + AsyncTask<bool>(setupBarrierAdvance(storage, barrier, 0)) + }; + const Log log3 { + {2, Handler::GroupSetup}, + {0, Handler::Setup}, + {0, Handler::Setup}, + {0, Handler::BarrierAdvance}, // Barrier advances may come in different order in + {0, Handler::BarrierAdvance}, // parallel mode, that's why id = 0 (same for both). + {2, Handler::Setup}, + {3, Handler::Setup} + }; + + // Test that multi barrier advance, triggered from inside the task described by + // setupBarrierAdvance, placed BEFORE the groups containing the waitFor() element + // in the tree order, wakes both waitFor tasks. + const Group root4 { + Storage(storage), + Storage(barrier), + parallel, + AsyncTask<bool>(setupBarrierAdvance(storage, barrier, 0)), + AsyncTask<bool>(setupBarrierAdvance(storage, barrier, 0)), + Group { + OnGroupSetup(groupSetup(2)), + WaitForBarrierTask(barrier), + Test(setupTask(4)) + }, + Group { + OnGroupSetup(groupSetup(3)), + WaitForBarrierTask(barrier), + Test(setupTask(5)) + } + }; + const Log log4 { + {0, Handler::Setup}, + {0, Handler::Setup}, + {2, Handler::GroupSetup}, + {3, Handler::GroupSetup}, + {0, Handler::BarrierAdvance}, + {0, Handler::BarrierAdvance}, + {4, Handler::Setup}, + {5, Handler::Setup} + }; + + // Notice the different log order for each scenario. + QTest::newRow("MultiBarrierSequential") + << TestData{storage, root1, log1, 5, OnStart::Running, OnDone::Success}; + QTest::newRow("MultiBarrierParallelAdvanceFirst") + << TestData{storage, root2, log2, 5, OnStart::Running, OnDone::Success}; + QTest::newRow("MultiBarrierParallelWaitForFirst") + << TestData{storage, root3, log3, 5, OnStart::Running, OnDone::Success}; + QTest::newRow("MultiBarrierParallelMultiWaitFor") + << TestData{storage, root4, log4, 6, OnStart::Running, OnDone::Success}; + } +} + +void tst_Tasking::testTree() +{ + 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_Tasking::storageOperators() +{ + TreeStorageBase storage1 = TreeStorage<CustomStorage>(); + TreeStorageBase storage2 = TreeStorage<CustomStorage>(); + TreeStorageBase storage3 = storage1; + + QVERIFY(storage1 == storage3); + QVERIFY(storage1 != storage2); + QVERIFY(storage2 != storage3); +} + +// This test checks whether a running task tree may be safely destructed. +// It also checks whether the destructor of a task tree deletes properly the storage created +// while starting the task tree. When running task tree is destructed, the storage done +// handler shouldn't be invoked. +void tst_Tasking::storageDestructor() +{ + bool setupCalled = false; + const auto setupHandler = [&setupCalled](CustomStorage *) { + setupCalled = true; + }; + bool doneCalled = false; + const auto doneHandler = [&doneCalled](CustomStorage *) { + doneCalled = true; + }; + QCOMPARE(CustomStorage::instanceCount(), 0); + { + TreeStorage<CustomStorage> storage; + const auto setupSleepingTask = [](TestTask &task) { + task.setFutureSynchronizer(s_futureSynchronizer); + task.setConcurrentCallData(runTask, true, 1000ms); + }; + const Group root { + Storage(storage), + Test(setupSleepingTask) + }; + + TaskTree taskTree(root); + QCOMPARE(CustomStorage::instanceCount(), 0); + taskTree.onStorageSetup(storage, setupHandler); + taskTree.onStorageDone(storage, doneHandler); + taskTree.start(); + QCOMPARE(CustomStorage::instanceCount(), 1); + QThread::msleep(5); // Give the sleeping task a change to start + } + QCOMPARE(CustomStorage::instanceCount(), 0); + QVERIFY(setupCalled); + QVERIFY(!doneCalled); +} + +QTEST_GUILESS_MAIN(tst_Tasking) + +#include "tst_tasking.moc" |