diff --git a/src/libs/solutions/tasking/concurrentcall.h b/src/libs/solutions/tasking/concurrentcall.h index d9a757c9ad3..6298b053724 100644 --- a/src/libs/solutions/tasking/concurrentcall.h +++ b/src/libs/solutions/tasking/concurrentcall.h @@ -31,6 +31,10 @@ public: { return m_future.resultCount() ? m_future.result() : ResultType(); } + QList results() const + { + return m_future.results(); + } QFuture future() const { return m_future; } private: @@ -38,9 +42,8 @@ private: void wrapConcurrent(Function &&function, Args &&...args) { m_startHandler = [=] { - if (m_threadPool) - return QtConcurrent::run(m_threadPool, function, args...); - return QtConcurrent::run(function, args...); + QThreadPool *threadPool = m_threadPool ? m_threadPool : QThreadPool::globalInstance(); + return QtConcurrent::run(threadPool, function, args...); }; } @@ -48,11 +51,9 @@ private: void wrapConcurrent(std::reference_wrapper &&wrapper, Args &&...args) { m_startHandler = [=] { - if (m_threadPool) { - return QtConcurrent::run(m_threadPool, - std::forward(wrapper.get()), args...); - } - return QtConcurrent::run(std::forward(wrapper.get()), args...); + QThreadPool *threadPool = m_threadPool ? m_threadPool : QThreadPool::globalInstance(); + return QtConcurrent::run(threadPool, std::forward(wrapper.get()), + args...); }; } diff --git a/tests/auto/solutions/CMakeLists.txt b/tests/auto/solutions/CMakeLists.txt index 848b48eca9a..b17f5c90453 100644 --- a/tests/auto/solutions/CMakeLists.txt +++ b/tests/auto/solutions/CMakeLists.txt @@ -1,2 +1,3 @@ +add_subdirectory(concurrentcall) add_subdirectory(qprocesstask) add_subdirectory(tasking) diff --git a/tests/auto/solutions/concurrentcall/CMakeLists.txt b/tests/auto/solutions/concurrentcall/CMakeLists.txt new file mode 100644 index 00000000000..28339eac881 --- /dev/null +++ b/tests/auto/solutions/concurrentcall/CMakeLists.txt @@ -0,0 +1,4 @@ +add_qtc_test(tst_concurrentcall + DEPENDS Tasking Qt::Concurrent Qt::Network + SOURCES tst_concurrentcall.cpp +) diff --git a/tests/auto/solutions/concurrentcall/concurrentcall.qbs b/tests/auto/solutions/concurrentcall/concurrentcall.qbs new file mode 100644 index 00000000000..12bd0cb8cfc --- /dev/null +++ b/tests/auto/solutions/concurrentcall/concurrentcall.qbs @@ -0,0 +1,8 @@ +QtcAutotest { + name: "ConcurrentCall autotest" + + Depends { name: "Qt"; submodules: ["concurrent", "network"] } + Depends { name: "Tasking" } + + files: "tst_concurrentcall.cpp" +} diff --git a/tests/auto/solutions/concurrentcall/tst_concurrentcall.cpp b/tests/auto/solutions/concurrentcall/tst_concurrentcall.cpp new file mode 100644 index 00000000000..8195238005a --- /dev/null +++ b/tests/auto/solutions/concurrentcall/tst_concurrentcall.cpp @@ -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 + +#include + +using namespace Tasking; + +using namespace std::chrono; +using namespace std::chrono_literals; + +class MyObject +{ +public: + static void staticMember(QPromise &promise, int n) + { + for (int i = 0; i < n; ++i) + promise.addResult(0); + } + + void member(QPromise &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 storage; + Group root; +}; + +void report3(QPromise &promise) +{ + promise.addResult(0); + promise.addResult(2); + promise.addResult(1); +} + +static void staticReport3(QPromise &promise) +{ + promise.addResult(0); + promise.addResult(2); + promise.addResult(1); +} + +void reportN(QPromise &promise, int n) +{ + for (int i = 0; i < n; ++i) + promise.addResult(0); +} + +static void staticReportN(QPromise &promise, int n) +{ + for (int i = 0; i < n; ++i) + promise.addResult(0); +} + +class Functor { +public: + void operator()(QPromise &promise, int n) const + { + for (int i = 0; i < n; ++i) + promise.addResult(0); + } +}; + +// void tst_ConcurrentCall::futureSynchonizer() +// { +// auto lambda = [](QPromise &promise) { +// while (true) { +// if (promise.isCanceled()) { +// promise.future().cancel(); +// promise.finish(); +// return; +// } +// QThread::msleep(100); +// } +// }; + +// FutureSynchronizer synchronizer; +// synchronizer.setCancelOnWait(false); +// { +// Async 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 &promise, int input) { promise.addResult(input * 2); } + +template +struct FutureArgType; + +template +struct FutureArgType> +{ + using Type = Arg; +}; + +template +struct ConcurrentResultType; + +template +struct ConcurrentResultType +{ + using Type = typename FutureArgType(), std::declval()...))>::Type; +}; + +template ::Type> +TestData createTestData(const QList &expectedResults, Function &&function, Args &&...args) +{ + TreeStorage storage; + + const auto onSetup = [=](ConcurrentCall &task) { + task.setConcurrentCallData(function, args...); + }; + const auto onDone = [storage, expectedResults](const ConcurrentCall &task) { + *storage = task.results() == expectedResults; + }; + + const Group root { + Storage(storage), + ConcurrentCallTask(onSetup, onDone) + }; + + return TestData{storage, root}; +} + +void tst_ConcurrentCall::taskTree_data() +{ + QTest::addColumn("testData"); + + const QList report3Result{0, 2, 1}; + const QList reportNResult{0, 0}; + + auto lambda = [](QPromise &promise, int n) { + for (int i = 0; i < n; ++i) + promise.addResult(0); + }; + const std::function &, int)> fun = [](QPromise &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 storage; + TreeStorage internalStorage; + + const auto onSetup = [internalStorage](ConcurrentCall &task) { + task.setConcurrentCallData(multiplyBy2, *internalStorage); + }; + const auto onDone = [internalStorage](const ConcurrentCall &task) { + *internalStorage = task.result(); + }; + + const Group root { + Storage(storage), + Storage(internalStorage), + onGroupSetup([internalStorage] { *internalStorage = 1; }), + ConcurrentCallTask(onSetup, onDone, CallDoneIf::Success), + ConcurrentCallTask(onSetup, onDone, CallDoneIf::Success), + ConcurrentCallTask(onSetup, onDone, CallDoneIf::Success), + ConcurrentCallTask(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" diff --git a/tests/auto/solutions/solutions.qbs b/tests/auto/solutions/solutions.qbs index b96095f3dbe..d36316a550f 100644 --- a/tests/auto/solutions/solutions.qbs +++ b/tests/auto/solutions/solutions.qbs @@ -3,6 +3,7 @@ import qbs Project { name: "Solutions autotests" references: [ + "concurrentcall/concurrentcall.qbs", "qprocesstask/qprocesstask.qbs", "tasking/tasking.qbs", ] diff --git a/tests/auto/utils/async/tst_async.cpp b/tests/auto/utils/async/tst_async.cpp index ee7025bad1d..0c3174d794d 100644 --- a/tests/auto/utils/async/tst_async.cpp +++ b/tests/auto/utils/async/tst_async.cpp @@ -62,7 +62,7 @@ void reportString2(QPromise &promise, QString s) promise.addResult(s); } -class Callable { +class Functor { public: void operator()(QPromise &promise, int n) const { @@ -262,11 +262,11 @@ void tst_Async::runAsync() QList({0, 0})); // operator() - QCOMPARE(createAsyncTask(Callable(), 3)->results(), + QCOMPARE(createAsyncTask(Functor(), 3)->results(), QList({0, 0, 0})); - QCOMPARE(Utils::asyncRun(Callable(), 3).results(), + QCOMPARE(Utils::asyncRun(Functor(), 3).results(), QList({0, 0, 0})); - const Callable c{}; + const Functor c{}; QCOMPARE(createAsyncTask(c, 2)->results(), QList({0, 0})); QCOMPARE(Utils::asyncRun(c, 2).results(), @@ -361,7 +361,7 @@ void tst_Async::crefFunction() QList({0, 0})); // callable with promise - const Callable c{}; + const Functor c{}; QCOMPARE(createAsyncTask(std::cref(c), 2)->results(), QList({0, 0})); QCOMPARE(Utils::asyncRun(std::cref(c), 2).results(),