TaskTree: Add tests for ConcurrentCall

Change-Id: I493ab170656a61a841614dff87495fa324336c45
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Jarek Kobus
2023-11-09 01:17:07 +01:00
parent 6c57c51981
commit 37ed2ce42f
7 changed files with 271 additions and 13 deletions

View File

@@ -31,6 +31,10 @@ public:
{ {
return m_future.resultCount() ? m_future.result() : ResultType(); return m_future.resultCount() ? m_future.result() : ResultType();
} }
QList<ResultType> results() const
{
return m_future.results();
}
QFuture<ResultType> future() const { return m_future; } QFuture<ResultType> future() const { return m_future; }
private: private:
@@ -38,9 +42,8 @@ private:
void wrapConcurrent(Function &&function, Args &&...args) void wrapConcurrent(Function &&function, Args &&...args)
{ {
m_startHandler = [=] { m_startHandler = [=] {
if (m_threadPool) QThreadPool *threadPool = m_threadPool ? m_threadPool : QThreadPool::globalInstance();
return QtConcurrent::run(m_threadPool, function, args...); return QtConcurrent::run(threadPool, function, args...);
return QtConcurrent::run(function, args...);
}; };
} }
@@ -48,11 +51,9 @@ private:
void wrapConcurrent(std::reference_wrapper<const Function> &&wrapper, Args &&...args) void wrapConcurrent(std::reference_wrapper<const Function> &&wrapper, Args &&...args)
{ {
m_startHandler = [=] { m_startHandler = [=] {
if (m_threadPool) { QThreadPool *threadPool = m_threadPool ? m_threadPool : QThreadPool::globalInstance();
return QtConcurrent::run(m_threadPool, return QtConcurrent::run(threadPool, std::forward<const Function>(wrapper.get()),
std::forward<const Function>(wrapper.get()), args...); args...);
}
return QtConcurrent::run(std::forward<const Function>(wrapper.get()), args...);
}; };
} }

View File

@@ -1,2 +1,3 @@
add_subdirectory(concurrentcall)
add_subdirectory(qprocesstask) add_subdirectory(qprocesstask)
add_subdirectory(tasking) add_subdirectory(tasking)

View File

@@ -0,0 +1,4 @@
add_qtc_test(tst_concurrentcall
DEPENDS Tasking Qt::Concurrent Qt::Network
SOURCES tst_concurrentcall.cpp
)

View File

@@ -0,0 +1,8 @@
QtcAutotest {
name: "ConcurrentCall autotest"
Depends { name: "Qt"; submodules: ["concurrent", "network"] }
Depends { name: "Tasking" }
files: "tst_concurrentcall.cpp"
}

View File

@@ -0,0 +1,243 @@
// 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 <tasking/concurrentcall.h>
#include <QtTest>
using namespace Tasking;
using namespace std::chrono;
using namespace std::chrono_literals;
class MyObject
{
public:
static void staticMember(QPromise<double> &promise, int n)
{
for (int i = 0; i < n; ++i)
promise.addResult(0);
}
void member(QPromise<double> &promise, int n) const
{
for (int i = 0; i < n; ++i)
promise.addResult(0);
}
};
class tst_ConcurrentCall : public QObject
{
Q_OBJECT
private slots:
// void futureSynchonizer();
void taskTree_data();
void taskTree();
private:
QThreadPool m_threadPool;
MyObject m_myObject;
};
struct TestData
{
TreeStorage<bool> storage;
Group root;
};
void report3(QPromise<int> &promise)
{
promise.addResult(0);
promise.addResult(2);
promise.addResult(1);
}
static void staticReport3(QPromise<int> &promise)
{
promise.addResult(0);
promise.addResult(2);
promise.addResult(1);
}
void reportN(QPromise<double> &promise, int n)
{
for (int i = 0; i < n; ++i)
promise.addResult(0);
}
static void staticReportN(QPromise<double> &promise, int n)
{
for (int i = 0; i < n; ++i)
promise.addResult(0);
}
class Functor {
public:
void operator()(QPromise<double> &promise, int n) const
{
for (int i = 0; i < n; ++i)
promise.addResult(0);
}
};
// void tst_ConcurrentCall::futureSynchonizer()
// {
// auto lambda = [](QPromise<int> &promise) {
// while (true) {
// if (promise.isCanceled()) {
// promise.future().cancel();
// promise.finish();
// return;
// }
// QThread::msleep(100);
// }
// };
// FutureSynchronizer synchronizer;
// synchronizer.setCancelOnWait(false);
// {
// Async<int> task;
// task.setConcurrentCallData(lambda);
// task.setFutureSynchronizer(&synchronizer);
// task.start();
// QThread::msleep(10);
// // We assume here that worker thread will still work for about 90 ms.
// QVERIFY(!task.isCanceled());
// QVERIFY(!task.isDone());
// }
// synchronizer.flushFinishedFutures();
// QVERIFY(!synchronizer.isEmpty());
// // The destructor of synchronizer should wait for about 90 ms for worker thread to be canceled
// }
void multiplyBy2(QPromise<int> &promise, int input) { promise.addResult(input * 2); }
template <typename...>
struct FutureArgType;
template <typename Arg>
struct FutureArgType<QFuture<Arg>>
{
using Type = Arg;
};
template <typename...>
struct ConcurrentResultType;
template<typename Function, typename ...Args>
struct ConcurrentResultType<Function, Args...>
{
using Type = typename FutureArgType<decltype(QtConcurrent::run(
std::declval<Function>(), std::declval<Args>()...))>::Type;
};
template <typename Function, typename ...Args,
typename ResultType = typename ConcurrentResultType<Function, Args...>::Type>
TestData createTestData(const QList<ResultType> &expectedResults, Function &&function, Args &&...args)
{
TreeStorage<bool> storage;
const auto onSetup = [=](ConcurrentCall<ResultType> &task) {
task.setConcurrentCallData(function, args...);
};
const auto onDone = [storage, expectedResults](const ConcurrentCall<ResultType> &task) {
*storage = task.results() == expectedResults;
};
const Group root {
Storage(storage),
ConcurrentCallTask<ResultType>(onSetup, onDone)
};
return TestData{storage, root};
}
void tst_ConcurrentCall::taskTree_data()
{
QTest::addColumn<TestData>("testData");
const QList<int> report3Result{0, 2, 1};
const QList<double> reportNResult{0, 0};
auto lambda = [](QPromise<double> &promise, int n) {
for (int i = 0; i < n; ++i)
promise.addResult(0);
};
const std::function<void(QPromise<double> &, int)> fun = [](QPromise<double> &promise, int n) {
for (int i = 0; i < n; ++i)
promise.addResult(0);
};
QTest::newRow("RefGlobalNoArgs")
<< createTestData(report3Result, &report3);
QTest::newRow("GlobalNoArgs")
<< createTestData(report3Result, report3);
QTest::newRow("RefStaticNoArgs")
<< createTestData(report3Result, &staticReport3);
QTest::newRow("StaticNoArgs")
<< createTestData(report3Result, staticReport3);
QTest::newRow("RefGlobalIntArg")
<< createTestData(reportNResult, &reportN, 2);
QTest::newRow("GlobalIntArg")
<< createTestData(reportNResult, reportN, 2);
QTest::newRow("RefStaticIntArg")
<< createTestData(reportNResult, &staticReportN, 2);
QTest::newRow("StaticIntArg")
<< createTestData(reportNResult, staticReportN, 2);
QTest::newRow("Lambda")
<< createTestData(reportNResult, lambda, 2);
QTest::newRow("Function")
<< createTestData(reportNResult, fun, 2);
QTest::newRow("Functor")
<< createTestData(reportNResult, Functor(), 2);
QTest::newRow("StaticMemberFunction")
<< createTestData(reportNResult, &MyObject::staticMember, 2);
QTest::newRow("MemberFunction")
<< createTestData(reportNResult, &MyObject::member, &m_myObject, 2);
{
TreeStorage<bool> storage;
TreeStorage<int> internalStorage;
const auto onSetup = [internalStorage](ConcurrentCall<int> &task) {
task.setConcurrentCallData(multiplyBy2, *internalStorage);
};
const auto onDone = [internalStorage](const ConcurrentCall<int> &task) {
*internalStorage = task.result();
};
const Group root {
Storage(storage),
Storage(internalStorage),
onGroupSetup([internalStorage] { *internalStorage = 1; }),
ConcurrentCallTask<int>(onSetup, onDone, CallDoneIf::Success),
ConcurrentCallTask<int>(onSetup, onDone, CallDoneIf::Success),
ConcurrentCallTask<int>(onSetup, onDone, CallDoneIf::Success),
ConcurrentCallTask<int>(onSetup, onDone, CallDoneIf::Success),
onGroupDone([storage, internalStorage] { *storage = *internalStorage == 16; })
};
QTest::newRow("Sequential") << TestData{storage, root};
}
}
void tst_ConcurrentCall::taskTree()
{
QFETCH(TestData, testData);
TaskTree taskTree({testData.root.withTimeout(1000ms)});
bool actualResult = false;
const auto collectResult = [&actualResult](const bool &storage) {
actualResult = storage;
};
taskTree.onStorageDone(testData.storage, collectResult);
const DoneWith result = taskTree.runBlocking();
QCOMPARE(taskTree.isRunning(), false);
QCOMPARE(result, DoneWith::Success);
QVERIFY(actualResult);
}
QTEST_GUILESS_MAIN(tst_ConcurrentCall)
#include "tst_concurrentcall.moc"

View File

@@ -3,6 +3,7 @@ import qbs
Project { Project {
name: "Solutions autotests" name: "Solutions autotests"
references: [ references: [
"concurrentcall/concurrentcall.qbs",
"qprocesstask/qprocesstask.qbs", "qprocesstask/qprocesstask.qbs",
"tasking/tasking.qbs", "tasking/tasking.qbs",
] ]

View File

@@ -62,7 +62,7 @@ void reportString2(QPromise<QString> &promise, QString s)
promise.addResult(s); promise.addResult(s);
} }
class Callable { class Functor {
public: public:
void operator()(QPromise<double> &promise, int n) const void operator()(QPromise<double> &promise, int n) const
{ {
@@ -262,11 +262,11 @@ void tst_Async::runAsync()
QList<double>({0, 0})); QList<double>({0, 0}));
// operator() // operator()
QCOMPARE(createAsyncTask(Callable(), 3)->results(), QCOMPARE(createAsyncTask(Functor(), 3)->results(),
QList<double>({0, 0, 0})); QList<double>({0, 0, 0}));
QCOMPARE(Utils::asyncRun(Callable(), 3).results(), QCOMPARE(Utils::asyncRun(Functor(), 3).results(),
QList<double>({0, 0, 0})); QList<double>({0, 0, 0}));
const Callable c{}; const Functor c{};
QCOMPARE(createAsyncTask(c, 2)->results(), QCOMPARE(createAsyncTask(c, 2)->results(),
QList<double>({0, 0})); QList<double>({0, 0}));
QCOMPARE(Utils::asyncRun(c, 2).results(), QCOMPARE(Utils::asyncRun(c, 2).results(),
@@ -361,7 +361,7 @@ void tst_Async::crefFunction()
QList<double>({0, 0})); QList<double>({0, 0}));
// callable with promise // callable with promise
const Callable c{}; const Functor c{};
QCOMPARE(createAsyncTask(std::cref(c), 2)->results(), QCOMPARE(createAsyncTask(std::cref(c), 2)->results(),
QList<double>({0, 0})); QList<double>({0, 0}));
QCOMPARE(Utils::asyncRun(std::cref(c), 2).results(), QCOMPARE(Utils::asyncRun(std::cref(c), 2).results(),