forked from qt-creator/qt-creator
TaskTree tests: Add stress test for storages
Run the same recipe concurrently. Test whether handling the same storage concurrently works properly. This addresses the 24th point in the master task below. Task-number: QTCREATORBUG-28741 Change-Id: Ic3358bef335b96b7dc2b88ad8102c440db5dafbf Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
add_qtc_test(tst_solutions_tasking
|
add_qtc_test(tst_solutions_tasking
|
||||||
DEPENDS Tasking Qt::Network
|
DEPENDS Tasking Qt::Concurrent Qt::Network
|
||||||
SOURCES tst_tasking.cpp
|
SOURCES tst_tasking.cpp
|
||||||
)
|
)
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
QtcAutotest {
|
QtcAutotest {
|
||||||
name: "Tasking autotest"
|
name: "Tasking autotest"
|
||||||
|
|
||||||
Depends { name: "Qt"; submodules: ["network"] }
|
Depends { name: "Qt"; submodules: ["concurrent", "network"] }
|
||||||
Depends { name: "Tasking" }
|
Depends { name: "Tasking" }
|
||||||
|
|
||||||
files: "tst_tasking.cpp"
|
files: "tst_tasking.cpp"
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
#include <tasking/barrier.h>
|
#include <tasking/barrier.h>
|
||||||
|
#include <tasking/concurrentcall.h>
|
||||||
|
|
||||||
#include <QtTest>
|
#include <QtTest>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
@@ -48,6 +49,18 @@ Q_ENUM_NS(Handler);
|
|||||||
enum class OnDone { Success, Failure };
|
enum class OnDone { Success, Failure };
|
||||||
Q_ENUM_NS(OnDone);
|
Q_ENUM_NS(OnDone);
|
||||||
|
|
||||||
|
enum class ThreadResult
|
||||||
|
{
|
||||||
|
Success,
|
||||||
|
FailOnTaskCountCheck,
|
||||||
|
FailOnRunningCheck,
|
||||||
|
FailOnProgressCheck,
|
||||||
|
FailOnLogCheck,
|
||||||
|
FailOnDoneStatusCheck,
|
||||||
|
Canceled
|
||||||
|
};
|
||||||
|
Q_ENUM_NS(ThreadResult);
|
||||||
|
|
||||||
} // namespace PrintableEnums
|
} // namespace PrintableEnums
|
||||||
|
|
||||||
using namespace PrintableEnums;
|
using namespace PrintableEnums;
|
||||||
@@ -56,17 +69,18 @@ using Log = QList<QPair<int, Handler>>;
|
|||||||
|
|
||||||
struct CustomStorage
|
struct CustomStorage
|
||||||
{
|
{
|
||||||
CustomStorage() { ++s_count; }
|
CustomStorage() { s_count.fetch_add(1); }
|
||||||
~CustomStorage() { --s_count; }
|
~CustomStorage() { s_count.fetch_add(-1); }
|
||||||
Log m_log;
|
Log m_log;
|
||||||
static int instanceCount() { return s_count; }
|
static int instanceCount() { return s_count.load(); }
|
||||||
private:
|
private:
|
||||||
static int s_count;
|
static std::atomic_int s_count;
|
||||||
};
|
};
|
||||||
|
|
||||||
int CustomStorage::s_count = 0;
|
std::atomic_int CustomStorage::s_count = 0;
|
||||||
|
|
||||||
struct TestData {
|
struct TestData
|
||||||
|
{
|
||||||
TreeStorage<CustomStorage> storage;
|
TreeStorage<CustomStorage> storage;
|
||||||
Group root;
|
Group root;
|
||||||
Log expectedLog;
|
Log expectedLog;
|
||||||
@@ -82,6 +96,8 @@ private slots:
|
|||||||
void validConstructs(); // compile test
|
void validConstructs(); // compile test
|
||||||
void testTree_data();
|
void testTree_data();
|
||||||
void testTree();
|
void testTree();
|
||||||
|
void testInThread_data();
|
||||||
|
void testInThread();
|
||||||
void storageIO_data();
|
void storageIO_data();
|
||||||
void storageIO();
|
void storageIO();
|
||||||
void storageOperators();
|
void storageOperators();
|
||||||
@@ -312,6 +328,83 @@ static Handler doneToTweakHandler(bool result)
|
|||||||
return result ? Handler::TweakDoneToSuccess : Handler::TweakDoneToError;
|
return result ? Handler::TweakDoneToSuccess : Handler::TweakDoneToError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static TestData storageShadowing()
|
||||||
|
{
|
||||||
|
// This test check if storage shadowing works OK.
|
||||||
|
|
||||||
|
const TreeStorage<CustomStorage> storage;
|
||||||
|
// This helper storage collect the pointers to storages created by shadowedStorage.
|
||||||
|
const TreeStorage<QHash<int, int *>> helperStorage; // One instance in this test.
|
||||||
|
// This storage is repeated in nested groups, the innermost storage will shadow outer ones.
|
||||||
|
const TreeStorage<int> shadowedStorage; // Three instances in this test.
|
||||||
|
|
||||||
|
const auto groupSetupWithStorage = [storage, helperStorage, shadowedStorage](int taskId) {
|
||||||
|
return onGroupSetup([storage, helperStorage, shadowedStorage, taskId] {
|
||||||
|
storage->m_log.append({taskId, Handler::GroupSetup});
|
||||||
|
helperStorage->insert(taskId, shadowedStorage.activeStorage());
|
||||||
|
*shadowedStorage = taskId;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const auto groupDoneWithStorage = [storage, helperStorage, shadowedStorage](int taskId) {
|
||||||
|
return onGroupDone([storage, helperStorage, shadowedStorage, taskId](DoneWith result) {
|
||||||
|
storage->m_log.append({taskId, resultToGroupHandler(result)});
|
||||||
|
auto it = helperStorage->find(taskId);
|
||||||
|
if (it == helperStorage->end()) {
|
||||||
|
qWarning() << "The helperStorage is missing the shadowedStorage.";
|
||||||
|
return;
|
||||||
|
} else if (*it != shadowedStorage.activeStorage()) {
|
||||||
|
qWarning() << "Wrong active instance of the shadowedStorage.";
|
||||||
|
return;
|
||||||
|
} else if (**it != taskId) {
|
||||||
|
qWarning() << "Wrong data of the active instance of the shadowedStorage.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
helperStorage->erase(it);
|
||||||
|
storage->m_log.append({*shadowedStorage, Handler::Storage});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const Group root {
|
||||||
|
Storage(storage),
|
||||||
|
Storage(helperStorage),
|
||||||
|
Storage(shadowedStorage),
|
||||||
|
groupSetupWithStorage(1),
|
||||||
|
Group {
|
||||||
|
Storage(shadowedStorage),
|
||||||
|
groupSetupWithStorage(2),
|
||||||
|
Group {
|
||||||
|
Storage(shadowedStorage),
|
||||||
|
groupSetupWithStorage(3),
|
||||||
|
groupDoneWithStorage(3)
|
||||||
|
},
|
||||||
|
Group {
|
||||||
|
Storage(shadowedStorage),
|
||||||
|
groupSetupWithStorage(4),
|
||||||
|
groupDoneWithStorage(4)
|
||||||
|
},
|
||||||
|
groupDoneWithStorage(2)
|
||||||
|
},
|
||||||
|
groupDoneWithStorage(1)
|
||||||
|
};
|
||||||
|
|
||||||
|
const Log log {
|
||||||
|
{1, Handler::GroupSetup},
|
||||||
|
{2, Handler::GroupSetup},
|
||||||
|
{3, Handler::GroupSetup},
|
||||||
|
{3, Handler::GroupSuccess},
|
||||||
|
{3, Handler::Storage},
|
||||||
|
{4, Handler::GroupSetup},
|
||||||
|
{4, Handler::GroupSuccess},
|
||||||
|
{4, Handler::Storage},
|
||||||
|
{2, Handler::GroupSuccess},
|
||||||
|
{2, Handler::Storage},
|
||||||
|
{1, Handler::GroupSuccess},
|
||||||
|
{1, Handler::Storage},
|
||||||
|
};
|
||||||
|
|
||||||
|
return {storage, root, log, 0, OnDone::Success};
|
||||||
|
}
|
||||||
|
|
||||||
void tst_Tasking::testTree_data()
|
void tst_Tasking::testTree_data()
|
||||||
{
|
{
|
||||||
QTest::addColumn<TestData>("testData");
|
QTest::addColumn<TestData>("testData");
|
||||||
@@ -2430,77 +2523,7 @@ void tst_Tasking::testTree_data()
|
|||||||
|
|
||||||
{
|
{
|
||||||
// This test check if storage shadowing works OK.
|
// This test check if storage shadowing works OK.
|
||||||
|
QTest::newRow("StorageShadowing") << storageShadowing();
|
||||||
// This helper storage collect the pointers to storages created by shadowedStorage.
|
|
||||||
const TreeStorage<QHash<int, int *>> helperStorage; // One instance in this test.
|
|
||||||
// This storage is repeated in nested groups, the innermost storage will shadow outer ones.
|
|
||||||
const TreeStorage<int> shadowedStorage; // Three instances in this test.
|
|
||||||
|
|
||||||
const auto groupSetupWithStorage = [storage, helperStorage, shadowedStorage](int taskId) {
|
|
||||||
return onGroupSetup([storage, helperStorage, shadowedStorage, taskId] {
|
|
||||||
storage->m_log.append({taskId, Handler::GroupSetup});
|
|
||||||
helperStorage->insert(taskId, shadowedStorage.activeStorage());
|
|
||||||
*shadowedStorage = taskId;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const auto groupDoneWithStorage = [storage, helperStorage, shadowedStorage](int taskId) {
|
|
||||||
return onGroupDone([storage, helperStorage, shadowedStorage, taskId](DoneWith result) {
|
|
||||||
storage->m_log.append({taskId, resultToGroupHandler(result)});
|
|
||||||
auto it = helperStorage->find(taskId);
|
|
||||||
if (it == helperStorage->end()) {
|
|
||||||
qWarning() << "The helperStorage is missing the shadowedStorage.";
|
|
||||||
return;
|
|
||||||
} else if (*it != shadowedStorage.activeStorage()) {
|
|
||||||
qWarning() << "Wrong active instance of the shadowedStorage.";
|
|
||||||
return;
|
|
||||||
} else if (**it != taskId) {
|
|
||||||
qWarning() << "Wrong data of the active instance of the shadowedStorage.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
helperStorage->erase(it);
|
|
||||||
storage->m_log.append({*shadowedStorage, Handler::Storage});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const Group root {
|
|
||||||
Storage(storage),
|
|
||||||
Storage(helperStorage),
|
|
||||||
Storage(shadowedStorage),
|
|
||||||
groupSetupWithStorage(1),
|
|
||||||
Group {
|
|
||||||
Storage(shadowedStorage),
|
|
||||||
groupSetupWithStorage(2),
|
|
||||||
Group {
|
|
||||||
Storage(shadowedStorage),
|
|
||||||
groupSetupWithStorage(3),
|
|
||||||
groupDoneWithStorage(3)
|
|
||||||
},
|
|
||||||
Group {
|
|
||||||
Storage(shadowedStorage),
|
|
||||||
groupSetupWithStorage(4),
|
|
||||||
groupDoneWithStorage(4)
|
|
||||||
},
|
|
||||||
groupDoneWithStorage(2)
|
|
||||||
},
|
|
||||||
groupDoneWithStorage(1)
|
|
||||||
};
|
|
||||||
|
|
||||||
const Log log {
|
|
||||||
{1, Handler::GroupSetup},
|
|
||||||
{2, Handler::GroupSetup},
|
|
||||||
{3, Handler::GroupSetup},
|
|
||||||
{3, Handler::GroupSuccess},
|
|
||||||
{3, Handler::Storage},
|
|
||||||
{4, Handler::GroupSetup},
|
|
||||||
{4, Handler::GroupSuccess},
|
|
||||||
{4, Handler::Storage},
|
|
||||||
{2, Handler::GroupSuccess},
|
|
||||||
{2, Handler::Storage},
|
|
||||||
{1, Handler::GroupSuccess},
|
|
||||||
{1, Handler::Storage},
|
|
||||||
};
|
|
||||||
|
|
||||||
QTest::newRow("StorageShadowing") << TestData{storage, root, log, 0, OnDone::Success};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2525,6 +2548,89 @@ void tst_Tasking::testTree()
|
|||||||
QCOMPARE(result, testData.onDone);
|
QCOMPARE(result, testData.onDone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_Tasking::testInThread_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<TestData>("testData");
|
||||||
|
QTest::newRow("StorageShadowing") << storageShadowing();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestResult
|
||||||
|
{
|
||||||
|
int executeCount = 0;
|
||||||
|
ThreadResult threadResult = ThreadResult::Success;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int s_loopCount = 1000;
|
||||||
|
static const int s_threadCount = 12;
|
||||||
|
|
||||||
|
static void runInThread(QPromise<TestResult> &promise, const TestData &testData)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < s_loopCount; ++i) {
|
||||||
|
if (promise.isCanceled()) {
|
||||||
|
promise.addResult(TestResult{i, ThreadResult::Canceled});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskTree taskTree({testData.root.withTimeout(1000ms)});
|
||||||
|
if (taskTree.taskCount() - 1 != testData.taskCount) { // -1 for the timeout task above
|
||||||
|
promise.addResult(TestResult{i, ThreadResult::FailOnTaskCountCheck});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log actualLog;
|
||||||
|
const auto collectLog = [&actualLog](const CustomStorage &storage) {
|
||||||
|
actualLog = storage.m_log;
|
||||||
|
};
|
||||||
|
taskTree.onStorageDone(testData.storage, collectLog);
|
||||||
|
|
||||||
|
const OnDone result = taskTree.runBlocking(QFuture<void>(promise.future()))
|
||||||
|
? OnDone::Success : OnDone::Failure;
|
||||||
|
|
||||||
|
if (taskTree.isRunning()) {
|
||||||
|
promise.addResult(TestResult{i, ThreadResult::FailOnRunningCheck});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (taskTree.progressValue() != taskTree.progressMaximum()) {
|
||||||
|
promise.addResult(TestResult{i, ThreadResult::FailOnProgressCheck});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (actualLog != testData.expectedLog) {
|
||||||
|
promise.addResult(TestResult{i, ThreadResult::FailOnLogCheck});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result != testData.onDone) {
|
||||||
|
promise.addResult(TestResult{i, ThreadResult::FailOnDoneStatusCheck});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
promise.addResult(TestResult{s_loopCount, ThreadResult::Success});
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_Tasking::testInThread()
|
||||||
|
{
|
||||||
|
QFETCH(TestData, testData);
|
||||||
|
|
||||||
|
const auto onSetup = [testData](ConcurrentCall<TestResult> &task) {
|
||||||
|
task.setConcurrentCallData(runInThread, testData);
|
||||||
|
};
|
||||||
|
const auto onDone = [testData](const ConcurrentCall<TestResult> &task) {
|
||||||
|
QVERIFY(task.future().resultCount());
|
||||||
|
const TestResult result = task.result();
|
||||||
|
QCOMPARE(result.executeCount, s_loopCount);
|
||||||
|
QCOMPARE(result.threadResult, ThreadResult::Success);
|
||||||
|
};
|
||||||
|
|
||||||
|
QList<GroupItem> tasks = { parallel };
|
||||||
|
for (int i = 0; i < s_threadCount; ++i)
|
||||||
|
tasks.append(ConcurrentCallTask<TestResult>(onSetup, onDone));
|
||||||
|
|
||||||
|
TaskTree taskTree(Group{tasks});
|
||||||
|
const OnDone result = taskTree.runBlocking() ? OnDone::Success : OnDone::Failure;
|
||||||
|
QCOMPARE(taskTree.isRunning(), false);
|
||||||
|
|
||||||
|
QCOMPARE(CustomStorage::instanceCount(), 0);
|
||||||
|
QCOMPARE(result, testData.onDone);
|
||||||
|
}
|
||||||
|
|
||||||
struct StorageIO
|
struct StorageIO
|
||||||
{
|
{
|
||||||
int value = 0;
|
int value = 0;
|
||||||
|
Reference in New Issue
Block a user