diff options
author | Jarek Kobus <jaroslaw.kobus@qt.io> | 2022-10-27 11:59:09 +0200 |
---|---|---|
committer | Jarek Kobus <jaroslaw.kobus@qt.io> | 2022-11-18 16:06:18 +0000 |
commit | 30eac65e09028c2b7bf0b2f695248715b83c5fd7 (patch) | |
tree | d926095e061fa35e17d73bac88e5121b15fabde9 /tests | |
parent | 95609cdd576797f07c0b92032271069778515f9f (diff) |
Utils: Introduce AsyncTask
AsyncTask encapsulates a function and arguments list
for further asynchronous invocation (using Utils::runAsync).
This is going to be a part of TaskTree hierarchy.
It will enable keeping asynchronous tasks inside the tree.
This means we will be able to construct a task tree
consisting of a mixture of processes and asynchronous tasks.
Implementation-wise this is a simple templated subclass of QObject,
where template parameter is of asynchronous result's type.
It holds QFutureWatcher object internally in order to
track task's running state.
Change-Id: I96f307cdf663cadc840465debb353ab55a2c3550
Reviewed-by: hjk <hjk@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Diffstat (limited to 'tests')
-rw-r--r-- | tests/auto/utils/CMakeLists.txt | 7 | ||||
-rw-r--r-- | tests/auto/utils/asynctask/CMakeLists.txt | 4 | ||||
-rw-r--r-- | tests/auto/utils/asynctask/asynctask.qbs | 7 | ||||
-rw-r--r-- | tests/auto/utils/asynctask/tst_asynctask.cpp | 255 | ||||
-rw-r--r-- | tests/auto/utils/utils.qbs | 7 |
5 files changed, 274 insertions, 6 deletions
diff --git a/tests/auto/utils/CMakeLists.txt b/tests/auto/utils/CMakeLists.txt index 2c30269d86d..c626b154de7 100644 --- a/tests/auto/utils/CMakeLists.txt +++ b/tests/auto/utils/CMakeLists.txt @@ -1,7 +1,11 @@ add_subdirectory(ansiescapecodehandler) +add_subdirectory(asynctask) +add_subdirectory(deviceshell) add_subdirectory(fileutils) +add_subdirectory(fsengine) add_subdirectory(fuzzymatcher) add_subdirectory(indexedcontainerproxyconstiterator) +add_subdirectory(multicursor) add_subdirectory(persistentsettings) add_subdirectory(qtcprocess) add_subdirectory(settings) @@ -9,6 +13,3 @@ add_subdirectory(stringutils) add_subdirectory(templateengine) add_subdirectory(tasktree) add_subdirectory(treemodel) -add_subdirectory(multicursor) -add_subdirectory(deviceshell) -add_subdirectory(fsengine) diff --git a/tests/auto/utils/asynctask/CMakeLists.txt b/tests/auto/utils/asynctask/CMakeLists.txt new file mode 100644 index 00000000000..8b234dbb024 --- /dev/null +++ b/tests/auto/utils/asynctask/CMakeLists.txt @@ -0,0 +1,4 @@ +add_qtc_test(tst_utils_asynctask + DEPENDS Utils + SOURCES tst_asynctask.cpp +) diff --git a/tests/auto/utils/asynctask/asynctask.qbs b/tests/auto/utils/asynctask/asynctask.qbs new file mode 100644 index 00000000000..8f478147e8d --- /dev/null +++ b/tests/auto/utils/asynctask/asynctask.qbs @@ -0,0 +1,7 @@ +import qbs + +QtcAutotest { + name: "AsyncTask autotest" + Depends { name: "Utils" } + files: "tst_asynctask.cpp" +} diff --git a/tests/auto/utils/asynctask/tst_asynctask.cpp b/tests/auto/utils/asynctask/tst_asynctask.cpp new file mode 100644 index 00000000000..de0236f0722 --- /dev/null +++ b/tests/auto/utils/asynctask/tst_asynctask.cpp @@ -0,0 +1,255 @@ +// 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 "utils/asynctask.h" + +#include <QtTest> + +using namespace Utils; + +class tst_AsyncTask : public QObject +{ + Q_OBJECT + +private slots: + void runAsync(); + void crefFunction(); + void futureSynchonizer(); +}; + +void report3(QFutureInterface<int> &fi) +{ + fi.reportResults({0, 2, 1}); +} + +void reportN(QFutureInterface<double> &fi, int n) +{ + fi.reportResults(QVector<double>(n, 0)); +} + +void reportString1(QFutureInterface<QString> &fi, const QString &s) +{ + fi.reportResult(s); +} + +void reportString2(QFutureInterface<QString> &fi, QString s) +{ + fi.reportResult(s); +} + +class Callable { +public: + void operator()(QFutureInterface<double> &fi, int n) const + { + fi.reportResults(QVector<double>(n, 0)); + } +}; + +class MyObject { +public: + static void staticMember0(QFutureInterface<double> &fi) + { + fi.reportResults({0, 2, 1}); + } + + static void staticMember1(QFutureInterface<double> &fi, int n) + { + fi.reportResults(QVector<double>(n, 0)); + } + + void member0(QFutureInterface<double> &fi) const + { + fi.reportResults({0, 2, 1}); + } + + void member1(QFutureInterface<double> &fi, int n) const + { + fi.reportResults(QVector<double>(n, 0)); + } + + void memberString1(QFutureInterface<QString> &fi, const QString &s) const + { + fi.reportResult(s); + } + + void memberString2(QFutureInterface<QString> &fi, QString s) const + { + fi.reportResult(s); + } + + void nonConstMember(QFutureInterface<double> &fi) + { + fi.reportResults({0, 2, 1}); + } +}; + +template <typename Function, typename ...Args, + typename ResultType = typename Internal::resultType<Function>::type> +std::shared_ptr<AsyncTask<ResultType>> createAsyncTask(const Function &function, const Args &...args) +{ + auto asyncTask = std::make_shared<AsyncTask<ResultType>>(); + asyncTask->setAsyncCallData(function, args...); + asyncTask->start(); + return asyncTask; +} + +void tst_AsyncTask::runAsync() +{ + // free function pointer + QCOMPARE(createAsyncTask(&report3)->results(), + QList<int>({0, 2, 1})); + QCOMPARE(createAsyncTask(report3)->results(), + QList<int>({0, 2, 1})); + + QCOMPARE(createAsyncTask(reportN, 4)->results(), + QList<double>({0, 0, 0, 0})); + QCOMPARE(createAsyncTask(reportN, 2)->results(), + QList<double>({0, 0})); + + QString s = QLatin1String("string"); + const QString &crs = QLatin1String("cr string"); + const QString cs = QLatin1String("c string"); + + QCOMPARE(createAsyncTask(reportString1, s)->results(), + QList<QString>({s})); + QCOMPARE(createAsyncTask(reportString1, crs)->results(), + QList<QString>({crs})); + QCOMPARE(createAsyncTask(reportString1, cs)->results(), + QList<QString>({cs})); + QCOMPARE(createAsyncTask(reportString1, QString(QLatin1String("rvalue")))->results(), + QList<QString>({QString(QLatin1String("rvalue"))})); + + QCOMPARE(createAsyncTask(reportString2, s)->results(), + QList<QString>({s})); + QCOMPARE(createAsyncTask(reportString2, crs)->results(), + QList<QString>({crs})); + QCOMPARE(createAsyncTask(reportString2, cs)->results(), + QList<QString>({cs})); + QCOMPARE(createAsyncTask(reportString2, QString(QLatin1String("rvalue")))->results(), + QList<QString>({QString(QLatin1String("rvalue"))})); + + // lambda + QCOMPARE(createAsyncTask([](QFutureInterface<double> &fi, int n) { + fi.reportResults(QVector<double>(n, 0)); + }, 3)->results(), + QList<double>({0, 0, 0})); + + // std::function + const std::function<void(QFutureInterface<double>&,int)> fun = [](QFutureInterface<double> &fi, int n) { + fi.reportResults(QVector<double>(n, 0)); + }; + QCOMPARE(createAsyncTask(fun, 2)->results(), + QList<double>({0, 0})); + + // operator() + QCOMPARE(createAsyncTask(Callable(), 3)->results(), + QList<double>({0, 0, 0})); + const Callable c{}; + QCOMPARE(createAsyncTask(c, 2)->results(), + QList<double>({0, 0})); + + // static member functions + QCOMPARE(createAsyncTask(&MyObject::staticMember0)->results(), + QList<double>({0, 2, 1})); + QCOMPARE(createAsyncTask(&MyObject::staticMember1, 2)->results(), + QList<double>({0, 0})); + + // member functions + const MyObject obj{}; + QCOMPARE(createAsyncTask(&MyObject::member0, &obj)->results(), + QList<double>({0, 2, 1})); + QCOMPARE(createAsyncTask(&MyObject::member1, &obj, 4)->results(), + QList<double>({0, 0, 0, 0})); + QCOMPARE(createAsyncTask(&MyObject::memberString1, &obj, s)->results(), + QList<QString>({s})); + QCOMPARE(createAsyncTask(&MyObject::memberString1, &obj, crs)->results(), + QList<QString>({crs})); + QCOMPARE(createAsyncTask(&MyObject::memberString1, &obj, cs)->results(), + QList<QString>({cs})); + QCOMPARE(createAsyncTask(&MyObject::memberString1, &obj, QString(QLatin1String("rvalue")))->results(), + QList<QString>({QString(QLatin1String("rvalue"))})); + QCOMPARE(createAsyncTask(&MyObject::memberString2, &obj, s)->results(), + QList<QString>({s})); + QCOMPARE(createAsyncTask(&MyObject::memberString2, &obj, crs)->results(), + QList<QString>({crs})); + QCOMPARE(createAsyncTask(&MyObject::memberString2, &obj, cs)->results(), + QList<QString>({cs})); + QCOMPARE(createAsyncTask(&MyObject::memberString2, &obj, QString(QLatin1String("rvalue")))->results(), + QList<QString>({QString(QLatin1String("rvalue"))})); + MyObject nonConstObj{}; + QCOMPARE(createAsyncTask(&MyObject::nonConstMember, &nonConstObj)->results(), + QList<double>({0, 2, 1})); +} + +void tst_AsyncTask::crefFunction() +{ + // free function pointer with future interface + auto fun = &report3; + QCOMPARE(createAsyncTask(std::cref(fun))->results(), + QList<int>({0, 2, 1})); + + // lambda with future interface + auto lambda = [](QFutureInterface<double> &fi, int n) { + fi.reportResults(QVector<double>(n, 0)); + }; + QCOMPARE(createAsyncTask(std::cref(lambda), 3)->results(), + QList<double>({0, 0, 0})); + + // std::function with future interface + const std::function<void(QFutureInterface<double>&,int)> funObj = [](QFutureInterface<double> &fi, int n) { + fi.reportResults(QVector<double>(n, 0)); + }; + QCOMPARE(createAsyncTask(std::cref(funObj), 2)->results(), + QList<double>({0, 0})); + + // callable with future interface + const Callable c{}; + QCOMPARE(createAsyncTask(std::cref(c), 2)->results(), + QList<double>({0, 0})); + + // member functions with future interface + auto member = &MyObject::member0; + const MyObject obj{}; + QCOMPARE(createAsyncTask(std::cref(member), &obj)->results(), + QList<double>({0, 2, 1})); +} + +template <typename Function, typename ...Args, + typename ResultType = typename Internal::resultType<Function>::type> +typename AsyncTask<ResultType>::StartHandler startHandler(const Function &function, const Args &...args) +{ + return [=] { return Utils::runAsync(function, args...); }; +} + +void tst_AsyncTask::futureSynchonizer() +{ + auto lambda = [](QFutureInterface<int> &fi) { + while (true) { + if (fi.isCanceled()) { + fi.reportCanceled(); + fi.reportFinished(); + return; + } + QThread::msleep(100); + } + }; + + FutureSynchronizer synchronizer; + { + AsyncTask<int> task; + task.setAsyncCallData(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 +} + +QTEST_GUILESS_MAIN(tst_AsyncTask) + +#include "tst_asynctask.moc" diff --git a/tests/auto/utils/utils.qbs b/tests/auto/utils/utils.qbs index e4cf4fdec04..a08e0167596 100644 --- a/tests/auto/utils/utils.qbs +++ b/tests/auto/utils/utils.qbs @@ -3,11 +3,14 @@ import qbs Project { name: "Utils autotests" references: [ + "ansiescapecodehandler/ansiescapecodehandler.qbs", + "asynctask/asynctask.qbs", + "deviceshell/deviceshell.qbs", "fileutils/fileutils.qbs", "fsengine/fsengine.qbs", - "ansiescapecodehandler/ansiescapecodehandler.qbs", "fuzzymatcher/fuzzymatcher.qbs", "indexedcontainerproxyconstiterator/indexedcontainerproxyconstiterator.qbs", + "multicursor/multicursor.qbs", "persistentsettings/persistentsettings.qbs", "qtcprocess/qtcprocess.qbs", "settings/settings.qbs", @@ -15,7 +18,5 @@ Project { "tasktree/tasktree.qbs", "templateengine/templateengine.qbs", "treemodel/treemodel.qbs", - "multicursor/multicursor.qbs", - "deviceshell/deviceshell.qbs", ] } |