diff options
Diffstat (limited to 'tests/auto/corelib/thread/qfuture/tst_qfuture.cpp')
-rw-r--r-- | tests/auto/corelib/thread/qfuture/tst_qfuture.cpp | 1281 |
1 files changed, 1162 insertions, 119 deletions
diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp index f97caa0633..3fc796514d 100644 --- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp +++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp @@ -1,30 +1,8 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses + #define QFUTURE_TEST #include <QCoreApplication> @@ -33,6 +11,10 @@ #include <QTestEventLoop> #include <QTimer> #include <QSignalSpy> +#include <QVarLengthArray> +#include <QSet> +#include <QList> +#include <private/qobject_p.h> #include <QTest> #include <qfuture.h> @@ -44,18 +26,21 @@ #include <QtConcurrent/qtconcurrentrun.h> #include <private/qfutureinterface_p.h> +#include <forward_list> +#include <list> #include <vector> #include <memory> +#include <set> // COM interface macro. #if defined(Q_OS_WIN) && defined(interface) # undef interface #endif -struct ResultStoreInt : QtPrivate::ResultStoreBase -{ - ~ResultStoreInt() { clear<int>(); } -}; +using namespace std::chrono_literals; +static constexpr auto DefaultWaitTime = 2s; + +using namespace Qt::StringLiterals; class SenderObject : public QObject { @@ -69,12 +54,75 @@ public: { emit multipleArgs(value1, value2, value3); } + void emitTupleArgSignal(const std::tuple<int, double, QString> &t) { emit tupleArgSignal(t); } + void emitMultiArgsWithTupleSignal1(int value, const std::tuple<int, double, QString> &t) + { + emit multiArgsWithTupleSignal1(value, t); + } + void emitMultiArgsWithTupleSignal2(const std::tuple<int, double, QString> &t, int value) + { + emit multiArgsWithTupleSignal2(t, value); + } + void emitMultiArgsWithPairSignal1(int value, const std::pair<int, double> &p) + { + emit multiArgsWithPairSignal1(value, p); + } + void emitMultiArgsWithPairSignal2(const std::pair<int, double> &p, int value) + { + emit multiArgsWithPairSignal2(p, value); + } + + void emitNoArgPrivateSignal() { emit noArgPrivateSignal(QPrivateSignal()); } + void emitIntArgPrivateSignal(int value) { emit intArgPrivateSignal(value, QPrivateSignal()); } + void emitMultiArgsPrivateSignal(int value1, double value2, const QString &value3) + { + emit multiArgsPrivateSignal(value1, value2, value3, QPrivateSignal()); + } + void emitTupleArgPrivateSignal(const std::tuple<int, double, QString> &t) + { + emit tupleArgPrivateSignal(t, QPrivateSignal()); + } + void emitMultiArgsWithTuplePrivateSignal1(int value, const std::tuple<int, double, QString> &t) + { + emit multiArgsWithTuplePrivateSignal1(value, t, QPrivateSignal()); + } + void emitMultiArgsWithTuplePrivateSignal2(const std::tuple<int, double, QString> &t, int value) + { + emit multiArgsWithTuplePrivateSignal2(t, value, QPrivateSignal()); + } + void emitMultiArgsWithPairPrivateSignal1(int value, const std::pair<int, double> &p) + { + emit multiArgsWithPairPrivateSignal1(value, p, QPrivateSignal()); + } + void emitMultiArgsWithPairPrivateSignal2(const std::pair<int, double> &p, int value) + { + emit multiArgsWithPairPrivateSignal2(p, value, QPrivateSignal()); + } signals: void noArgSignal(); void intArgSignal(int value); void constRefArg(const QString &value); void multipleArgs(int value1, double value2, const QString &value3); + void tupleArgSignal(const std::tuple<int, double, QString> &t); + void multiArgsWithTupleSignal1(int value, const std::tuple<int, double, QString> &t); + void multiArgsWithTupleSignal2(const std::tuple<int, double, QString> &t, int value); + void multiArgsWithPairSignal1(int value, const std::pair<int, double> &p); + void multiArgsWithPairSignal2(const std::pair<int, double> &p, int value); + + // Private signals + void noArgPrivateSignal(QPrivateSignal); + void intArgPrivateSignal(int value, QPrivateSignal); + void multiArgsPrivateSignal(int value1, double value2, const QString &value3, QPrivateSignal); + void tupleArgPrivateSignal(const std::tuple<int, double, QString> &t, QPrivateSignal); + void multiArgsWithTuplePrivateSignal1(int value, const std::tuple<int, double, QString> &t, + QPrivateSignal); + void multiArgsWithTuplePrivateSignal2(const std::tuple<int, double, QString> &t, int value, + QPrivateSignal); + void multiArgsWithPairPrivateSignal1(int value, const std::pair<int, double> &p, + QPrivateSignal); + void multiArgsWithPairPrivateSignal2(const std::pair<int, double> &p, int value, + QPrivateSignal); }; class LambdaThread : public QThread @@ -95,6 +143,18 @@ private: std::function<void ()> m_fn; }; +// Emulates QWidget behavior by deleting its children early in the destructor +// instead of leaving it to ~QObject() +class FakeQWidget : public QObject +{ + Q_OBJECT +public: + ~FakeQWidget() override { + auto *d = QObjectPrivate::get(this); + d->deleteChildren(); + } +}; + using UniquePtr = std::unique_ptr<int>; class tst_QFuture: public QObject @@ -144,6 +204,7 @@ private slots: #endif void onCanceled(); void cancelContinuations(); + void continuationsWithContext_data(); void continuationsWithContext(); void continuationsWithMoveOnlyLambda(); #if 0 @@ -165,6 +226,7 @@ private slots: void rejectPendingResultOverwrite(); void createReadyFutures(); + void continuationsAfterReadyFutures(); void getFutureInterface(); void convertQMetaType(); @@ -182,6 +244,12 @@ private slots: void whenAnyDifferentTypesWithCanceled(); void whenAnyDifferentTypesWithFailed(); + void continuationOverride(); + void continuationsDontLeak(); + void cancelAfterFinishWithContinuations(); + + void unwrap(); + private: using size_type = std::vector<int>::size_type; @@ -195,6 +263,16 @@ private: static void testTakeResults(QFuture<T> future, size_type resultCount); }; +class IntResultsCleaner +{ +public: + IntResultsCleaner(QtPrivate::ResultStoreBase &s) : store(s) { } + ~IntResultsCleaner() { store.clear<int>(); } + +private: + QtPrivate::ResultStoreBase &store; +}; + void tst_QFuture::resultStore() { int int0 = 0; @@ -202,7 +280,9 @@ void tst_QFuture::resultStore() int int2 = 2; { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + QCOMPARE(store.begin(), store.end()); QCOMPARE(store.resultAt(0), store.end()); QCOMPARE(store.resultAt(1), store.end()); @@ -210,7 +290,9 @@ void tst_QFuture::resultStore() { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(-1, &int0); store.addResult(1, &int1); QtPrivate::ResultIteratorBase it = store.begin(); @@ -232,7 +314,9 @@ void tst_QFuture::resultStore() QList<int> vec1 = QList<int>() << 4 << 5; { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResults(-1, &vec0, 2); store.addResults(-1, &vec1, 2); QtPrivate::ResultIteratorBase it = store.begin(); @@ -255,7 +339,9 @@ void tst_QFuture::resultStore() QCOMPARE(it, store.end()); } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(-1, &int0); store.addResults(-1, &vec1, 2); store.addResult(-1, &int1); @@ -286,7 +372,9 @@ void tst_QFuture::resultStore() QCOMPARE(store.resultAt(4), store.end()); } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(-1, &int0); store.addResults(-1, &vec0); store.addResult(-1, &int1); @@ -316,7 +404,9 @@ void tst_QFuture::resultStore() QCOMPARE(store.resultAt(3).value<int>(), int1); } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(-1, &int0); store.addResults(-1, &vec0); store.addResult(200, &int1); @@ -328,7 +418,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(1, &int1); store.addResult(0, &int0); store.addResult(-1, &int2); @@ -339,7 +431,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + QCOMPARE(store.contains(0), false); QCOMPARE(store.contains(1), false); QCOMPARE(store.contains(INT_MAX), false); @@ -347,7 +441,9 @@ void tst_QFuture::resultStore() { // Test filter mode, where "gaps" in the result array aren't allowed. - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResult(0, &int0); @@ -381,7 +477,9 @@ void tst_QFuture::resultStore() { // test canceled results - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResult(0, &int0); @@ -418,7 +516,9 @@ void tst_QFuture::resultStore() { // test addResult return value - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResult(0, &int0); @@ -464,7 +564,9 @@ void tst_QFuture::resultStore() { // test resultCount in non-filtered mode. It should always be possible // to iterate through the results 0 to resultCount. - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(0, &int0); QCOMPARE(store.count(), 1); @@ -478,7 +580,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(2, &int0); QCOMPARE(store.count(), 0); @@ -490,7 +594,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResults(2, &vec1); QCOMPARE(store.count(), 0); @@ -502,7 +608,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResults(2, &vec1); QCOMPARE(store.count(), 0); @@ -510,7 +618,9 @@ void tst_QFuture::resultStore() QCOMPARE(store.count(), 4); } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResults(3, &vec1); QCOMPARE(store.count(), 0); @@ -522,7 +632,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResults(3, &vec1); QCOMPARE(store.count(), 0); @@ -535,7 +647,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResults(3, &vec1); QCOMPARE(store.count(), 0); @@ -545,7 +659,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResults(3, &vec1); QCOMPARE(store.count(), 0); @@ -558,7 +674,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(1, &int0); store.addResult(3, &int0); store.addResults(6, &vec0); @@ -573,7 +691,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResult(1, &int0); store.addResult(3, &int0); @@ -601,7 +721,9 @@ void tst_QFuture::resultStore() QCOMPARE(store.contains(7), false); } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addCanceledResult(0); QCOMPARE(store.contains(0), false); @@ -980,13 +1102,13 @@ void tst_QFuture::multipleResults() QList<int> fasit = QList<int>() << 1 << 2 << 3 << 4; { QList<int> results; - for (int result : qAsConst(f)) + for (int result : std::as_const(f)) results.append(result); QCOMPARE(results, fasit); } { QList<int> results; - for (int result : qAsConst(copy)) + for (int result : std::as_const(copy)) results.append(result); QCOMPARE(results, fasit); } @@ -1859,7 +1981,7 @@ void tst_QFuture::nonGlobalThreadPool() void run() override { const int ms = 100 + (QRandomGenerator::global()->bounded(100) - 100/2); - QThread::msleep(ulong(ms)); + QThread::sleep(std::chrono::milliseconds{ms}); reportResult(Answer); reportFinished(); } @@ -2144,6 +2266,26 @@ void tst_QFuture::then() QVERIFY(threadId1 != QThread::currentThreadId()); QVERIFY(threadId2 != QThread::currentThreadId()); } + + // QTBUG-106083 & QTBUG-105182 + { + QThread thread; + thread.start(); + + QObject context; + context.moveToThread(&thread); + + auto future = QtConcurrent::run([] { + return 42; + }).then([] (int result) { + return result + 1; + }).then(&context, [] (int result) { + return result + 1; + }); + QCOMPARE(future.result(), 44); + thread.quit(); + thread.wait(); + } } template<class Type, class Callable> @@ -2928,7 +3070,7 @@ void tst_QFuture::cancelContinuations() // The chain is cancelled before the execution of continuations { - auto f = QtFuture::makeReadyFuture(42); + auto f = QtFuture::makeReadyValueFuture(42); f.cancel(); int checkpoint = 0; @@ -2983,7 +3125,7 @@ void tst_QFuture::cancelContinuations() #ifndef QT_NO_EXCEPTIONS // The chain is cancelled in the middle of execution of continuations, - // while there's an exception in the chain, which is handeled inside + // while there's an exception in the chain, which is handled inside // the continuations. { QPromise<int> promise; @@ -3020,17 +3162,97 @@ void tst_QFuture::cancelContinuations() QCOMPARE(checkpoint, 3); } #endif // QT_NO_EXCEPTIONS + + // Check notifications from QFutureWatcher + { + QPromise<void> p; + auto f = p.future(); + + auto f1 = f.then([] {}); + auto f2 = f1.then([] {}); + + QFutureWatcher<void> watcher1, watcher2; + int state = 0; + QObject::connect(&watcher1, &QFutureWatcher<void>::started, [&] { + QCOMPARE(state, 0); + ++state; + }); + QObject::connect(&watcher1, &QFutureWatcher<void>::canceled, [&] { + QCOMPARE(state, 1); + ++state; + }); + QObject::connect(&watcher1, &QFutureWatcher<void>::finished, [&] { + QCOMPARE(state, 2); + ++state; + }); + QObject::connect(&watcher2, &QFutureWatcher<void>::started, [&] { + QCOMPARE(state, 3); + ++state; + }); + QObject::connect(&watcher2, &QFutureWatcher<void>::canceled, [&] { + QCOMPARE(state, 4); + ++state; + }); + QObject::connect(&watcher2, &QFutureWatcher<int>::finished, [&] { + QCOMPARE(state, 5); + ++state; + }); + + watcher1.setFuture(f1); + watcher2.setFuture(f2); + + p.start(); + f.cancel(); + p.finish(); + + qApp->processEvents(); + + QCOMPARE(state, 6); + QVERIFY(watcher1.isFinished()); + QVERIFY(watcher1.isCanceled()); + QVERIFY(watcher2.isFinished()); + QVERIFY(watcher2.isCanceled()); + } + + // Cancel continuations with context (QTBUG-108790) + { + // This test should pass with ASan + auto future = QtConcurrent::run([] {}); + future.then(this, [] {}); + future.waitForFinished(); + future.cancel(); + } +} + +void tst_QFuture::continuationsWithContext_data() +{ + QTest::addColumn<bool>("inOtherThread"); + QTest::addRow("in-other-thread") << true; + QTest::addRow("in-main-thread-qtbug119406") << false; } void tst_QFuture::continuationsWithContext() { - QThread thread; - thread.start(); + QFETCH(bool, inOtherThread); + auto tstThread = QThread::currentThread(); + QThread *thread = inOtherThread ? new QThread + : tstThread; auto context = new QObject(); - context->moveToThread(&thread); - auto tstThread = QThread::currentThread(); + const auto cleanupGuard = qScopeGuard([&] { + context->deleteLater(); + if (thread != tstThread) { + thread->quit(); + thread->wait(); + delete thread; + } + }); + + if (inOtherThread) { + thread->start(); + context->moveToThread(thread); + } // .then() { @@ -3043,12 +3265,12 @@ void tst_QFuture::continuationsWithContext() }) .then(context, [&](int val) { - if (QThread::currentThread() != &thread) + if (QThread::currentThread() != thread) return 0; return val + 1; }) .then([&](int val) { - if (QThread::currentThread() != &thread) + if (QThread::currentThread() != thread) return 0; return val + 1; }); @@ -3064,12 +3286,12 @@ void tst_QFuture::continuationsWithContext() auto future = promise.future() .onCanceled(context, [&] { - if (QThread::currentThread() != &thread) + if (QThread::currentThread() != thread) return 0; return 1; }) .then([&](int val) { - if (QThread::currentThread() != &thread) + if (QThread::currentThread() != thread) return 0; return val + 1; }); @@ -3079,6 +3301,40 @@ void tst_QFuture::continuationsWithContext() QCOMPARE(future.result(), 2); } + // Cancellation when the context object is destroyed + { + // Use something like QWidget which deletes its children early, i.e. + // before ~QObject() runs. This behavior can lead to side-effects + // like QPointers to the parent not being set to nullptr during child + // object destruction. + QPointer shortLivedContext = new FakeQWidget(); + shortLivedContext->moveToThread(thread); + + QPromise<int> promise; + auto future = promise.future() + .then(shortLivedContext, [&](int val) { + if (QThread::currentThread() != thread) + return 0; + return val + 1000; + }) + .onCanceled([&, ptr=QPointer(shortLivedContext)] { + if (QThread::currentThread() != thread) + return 0; + if (ptr) + return 1; + return 2; + }); + promise.start(); + + QMetaObject::invokeMethod(shortLivedContext, [&]() { + delete shortLivedContext; + }, inOtherThread ? Qt::BlockingQueuedConnection + : Qt::DirectConnection); + + promise.finish(); + QCOMPARE(future.result(), 2); + } + #ifndef QT_NO_EXCEPTIONS // .onFaled() { @@ -3091,12 +3347,12 @@ void tst_QFuture::continuationsWithContext() }) .onFailed(context, [&] { - if (QThread::currentThread() != &thread) + if (QThread::currentThread() != thread) return 0; return 1; }) .then([&](int val) { - if (QThread::currentThread() != &thread) + if (QThread::currentThread() != thread) return 0; return val + 1; }); @@ -3105,11 +3361,6 @@ void tst_QFuture::continuationsWithContext() QCOMPARE(future.result(), 2); } #endif // QT_NO_EXCEPTIONS - - context->deleteLater(); - - thread.quit(); - thread.wait(); } void tst_QFuture::continuationsWithMoveOnlyLambda() @@ -3117,7 +3368,8 @@ void tst_QFuture::continuationsWithMoveOnlyLambda() // .then() { std::unique_ptr<int> uniquePtr(new int(42)); - auto future = QtFuture::makeReadyFuture().then([p = std::move(uniquePtr)] { return *p; }); + auto future = QtFuture::makeReadyVoidFuture() + .then([p = std::move(uniquePtr)] { return *p; }); QCOMPARE(future.result(), 42); } // .then() with thread pool @@ -3125,8 +3377,8 @@ void tst_QFuture::continuationsWithMoveOnlyLambda() QThreadPool pool; std::unique_ptr<int> uniquePtr(new int(42)); - auto future = - QtFuture::makeReadyFuture().then(&pool, [p = std::move(uniquePtr)] { return *p; }); + auto future = QtFuture::makeReadyVoidFuture() + .then(&pool, [p = std::move(uniquePtr)] { return *p; }); QCOMPARE(future.result(), 42); } // .then() with context @@ -3134,8 +3386,8 @@ void tst_QFuture::continuationsWithMoveOnlyLambda() QObject object; std::unique_ptr<int> uniquePtr(new int(42)); - auto future = QtFuture::makeReadyFuture().then(&object, - [p = std::move(uniquePtr)] { return *p; }); + auto future = QtFuture::makeReadyVoidFuture() + .then(&object, [p = std::move(uniquePtr)] { return *p; }); QCOMPARE(future.result(), 42); } @@ -3199,17 +3451,6 @@ void tst_QFuture::testFutureTaken(QFuture<T> &noMoreFuture) { QCOMPARE(noMoreFuture.isValid(), false); QCOMPARE(noMoreFuture.resultCount(), 0); - QCOMPARE(noMoreFuture.isStarted(), false); - QCOMPARE(noMoreFuture.isRunning(), false); - QCOMPARE(noMoreFuture.isSuspending(), false); - QCOMPARE(noMoreFuture.isSuspended(), false); -#if QT_DEPRECATED_SINCE(6, 0) -QT_WARNING_PUSH -QT_WARNING_DISABLE_DEPRECATED - QCOMPARE(noMoreFuture.isPaused(), false); -QT_WARNING_POP -#endif - QCOMPARE(noMoreFuture.isFinished(), false); QCOMPARE(noMoreFuture.progressValue(), 0); } @@ -3297,7 +3538,7 @@ void tst_QFuture::runAndTake() auto rabbit = [](){ // Let's wait a bit to give the test below some time // to sync up with us with its watcher. - QThread::currentThread()->msleep(100); + QThread::currentThread()->sleep(std::chrono::milliseconds{100}); return UniquePtr(new int(10)); }; @@ -3310,7 +3551,7 @@ void tst_QFuture::runAndTake() auto gotcha = QtConcurrent::run(rabbit); watcha.setFuture(gotcha); - loop.enterLoopMSecs(500); + loop.enterLoop(500ms); if (loop.timeout()) QSKIP("Failed to run the task, nothing to test"); @@ -3381,14 +3622,14 @@ void tst_QFuture::resultsReadyAt() // Run event loop, QCoreApplication::postEvent is in use // in QFutureInterface: - eventProcessor.enterLoopMSecs(2000); + eventProcessor.enterLoop(DefaultWaitTime); QVERIFY(!eventProcessor.timeout()); if (QTest::currentTestFailed()) // Failed in our lambda observing 'ready at' return; QCOMPARE(reported, nExpectedResults); QCOMPARE(nExpectedResults, iface.future().resultCount()); - QCOMPARE(readyCounter.count(), 3); + QCOMPARE(readyCounter.size(), 3); QCOMPARE(taken, 0b1111); } @@ -3429,11 +3670,21 @@ void tst_QFuture::canceledFutureIsNotValid() void tst_QFuture::signalConnect() { + const int intValue = 42; + const double doubleValue = 42.5; + const QString stringValue = "42"; + + using TupleType = std::tuple<int, double, QString>; + const TupleType tuple(intValue, doubleValue, stringValue); + + using PairType = std::pair<int, double>; + const PairType pair(intValue, doubleValue); + // No arg { SenderObject sender; auto future = - QtFuture::connect(&sender, &SenderObject::noArgSignal).then([&] { return true; }); + QtFuture::connect(&sender, &SenderObject::noArgSignal).then([] { return true; }); sender.emitNoArg(); QCOMPARE(future.result(), true); } @@ -3462,16 +3713,151 @@ void tst_QFuture::signalConnect() // Multiple args { SenderObject sender; - using TupleArgs = std::tuple<int, double, QString>; auto future = - QtFuture::connect(&sender, &SenderObject::multipleArgs).then([](TupleArgs values) { + QtFuture::connect(&sender, &SenderObject::multipleArgs).then([](TupleType values) { return values; }); - sender.emitMultipleArgs(42, 42.5, "42"); + sender.emitMultipleArgs(intValue, doubleValue, stringValue); + auto result = future.result(); + QCOMPARE(result, tuple); + } + + // Single std::tuple arg + { + SenderObject sender; + QFuture<TupleType> future = QtFuture::connect(&sender, &SenderObject::tupleArgSignal); + sender.emitTupleArgSignal(tuple); + auto result = future.result(); + QCOMPARE(result, tuple); + } + + // Multi-args signal(int, std::tuple) + { + SenderObject sender; + QFuture<std::tuple<int, TupleType>> future = + QtFuture::connect(&sender, &SenderObject::multiArgsWithTupleSignal1); + sender.emitMultiArgsWithTupleSignal1(142, tuple); + const auto [v, t] = future.result(); + QCOMPARE(v, 142); + QCOMPARE(t, tuple); + } + + // Multi-args signal(std::tuple, int) + { + SenderObject sender; + QFuture<std::tuple<TupleType, int>> future = + QtFuture::connect(&sender, &SenderObject::multiArgsWithTupleSignal2); + sender.emitMultiArgsWithTupleSignal2(tuple, 142); + const auto [t, v] = future.result(); + QCOMPARE(v, 142); + QCOMPARE(t, tuple); + } + + // Multi-args signal(int, std::pair) + { + SenderObject sender; + QFuture<std::tuple<int, PairType>> future = + QtFuture::connect(&sender, &SenderObject::multiArgsWithPairSignal1); + sender.emitMultiArgsWithPairSignal1(142, pair); + const auto [v, p] = future.result(); + QCOMPARE(v, 142); + QCOMPARE(p, pair); + } + + // Multi-args signal(std::pair, int) + { + SenderObject sender; + QFuture<std::tuple<PairType, int>> future = + QtFuture::connect(&sender, &SenderObject::multiArgsWithPairSignal2); + sender.emitMultiArgsWithPairSignal2(pair, 142); + const auto [p, v] = future.result(); + QCOMPARE(v, 142); + QCOMPARE(p, pair); + } + + // No arg private signal + { + SenderObject sender; + auto future = QtFuture::connect(&sender, &SenderObject::noArgPrivateSignal).then([] { + return true; + }); + sender.emitNoArgPrivateSignal(); + QCOMPARE(future.result(), true); + } + + // One arg private signal + { + SenderObject sender; + auto future = + QtFuture::connect(&sender, &SenderObject::intArgPrivateSignal).then([](int value) { + return value; + }); + sender.emitIntArgPrivateSignal(42); + QCOMPARE(future.result(), 42); + } + + // Multi-args private signal + { + SenderObject sender; + auto future = QtFuture::connect(&sender, &SenderObject::multiArgsPrivateSignal) + .then([](TupleType values) { return values; }); + sender.emitMultiArgsPrivateSignal(intValue, doubleValue, stringValue); + auto result = future.result(); + QCOMPARE(result, tuple); + } + + // Single std::tuple arg private signal + { + SenderObject sender; + QFuture<TupleType> future = + QtFuture::connect(&sender, &SenderObject::tupleArgPrivateSignal); + sender.emitTupleArgPrivateSignal(tuple); auto result = future.result(); - QCOMPARE(std::get<0>(result), 42); - QCOMPARE(std::get<1>(result), 42.5); - QCOMPARE(std::get<2>(result), "42"); + QCOMPARE(result, tuple); + } + + // Multi-args private signal(int, std::tuple) + { + SenderObject sender; + QFuture<std::tuple<int, TupleType>> future = + QtFuture::connect(&sender, &SenderObject::multiArgsWithTuplePrivateSignal1); + sender.emitMultiArgsWithTuplePrivateSignal1(142, tuple); + const auto [v, t] = future.result(); + QCOMPARE(v, 142); + QCOMPARE(t, tuple); + } + + // Multi-args private signal(std::tuple, int) + { + SenderObject sender; + QFuture<std::tuple<TupleType, int>> future = + QtFuture::connect(&sender, &SenderObject::multiArgsWithTuplePrivateSignal2); + sender.emitMultiArgsWithTuplePrivateSignal2(tuple, 142); + const auto [t, v] = future.result(); + QCOMPARE(v, 142); + QCOMPARE(t, tuple); + } + + // Multi-args private signal(int, std::pair) + { + SenderObject sender; + QFuture<std::tuple<int, PairType>> future = + QtFuture::connect(&sender, &SenderObject::multiArgsWithPairPrivateSignal1); + sender.emitMultiArgsWithPairPrivateSignal1(142, pair); + const auto [v, p] = future.result(); + QCOMPARE(v, 142); + QCOMPARE(p, pair); + } + + // Multi-args private signal(std::pair, int) + { + SenderObject sender; + QFuture<std::tuple<PairType, int>> future = + QtFuture::connect(&sender, &SenderObject::multiArgsWithPairPrivateSignal2); + sender.emitMultiArgsWithPairPrivateSignal2(pair, 142); + const auto [p, v] = future.result(); + QCOMPARE(v, 142); + QCOMPARE(p, pair); } // Sender destroyed @@ -3483,8 +3869,6 @@ void tst_QFuture::signalConnect() QSignalSpy spy(sender, &QObject::destroyed); sender->deleteLater(); - // emit the signal when sender is being destroyed - QObject::connect(sender, &QObject::destroyed, [sender] { sender->emitIntArg(42); }); spy.wait(); QVERIFY(future.isCanceled()); @@ -3509,13 +3893,40 @@ void tst_QFuture::signalConnect() QVERIFY(!future.isCanceled()); QVERIFY(future.isValid()); } + + // Connect to nullptr + { + SenderObject *sender = nullptr; + auto future = QtFuture::connect(sender, &SenderObject::intArgSignal); + QVERIFY(future.isFinished()); + QVERIFY(future.isCanceled()); + QVERIFY(!future.isValid()); + } + + // Connect to non-signal + { + SenderObject sender; + +#if defined(Q_CC_MSVC_ONLY) && (Q_CC_MSVC < 1940 || __cplusplus < 202002L) +#define EXPECT_FUTURE_CONNECT_FAIL() QEXPECT_FAIL("", "QTBUG-101761, test fails on Windows/MSVC", Continue) +#else + QTest::ignoreMessage(QtWarningMsg, "QObject::connect: signal not found in SenderObject"); +#define EXPECT_FUTURE_CONNECT_FAIL() +#endif + + auto future = QtFuture::connect(&sender, &SenderObject::emitNoArg); + EXPECT_FUTURE_CONNECT_FAIL(); + QVERIFY(future.isFinished()); + EXPECT_FUTURE_CONNECT_FAIL(); + QVERIFY(future.isCanceled()); + EXPECT_FUTURE_CONNECT_FAIL(); + QVERIFY(!future.isValid()); +#undef EXPECT_FUTURE_CONNECT_FAIL + } } void tst_QFuture::waitForFinished() { -#if !QT_CONFIG(cxx11_future) - QSKIP("This test requires QThread::create"); -#else QFutureInterface<void> fi; auto future = fi.future(); @@ -3536,7 +3947,6 @@ void tst_QFuture::waitForFinished() QVERIFY(waitingThread->wait()); QVERIFY(waitingThread->isFinished()); -#endif } void tst_QFuture::rejectResultOverwrite_data() @@ -3581,9 +3991,9 @@ void tst_QFuture::rejectResultOverwrite() }); // Run event loop, QCoreApplication::postEvent is in use // in QFutureInterface: - eventProcessor.enterLoopMSecs(2000); + eventProcessor.enterLoop(DefaultWaitTime); QVERIFY(!eventProcessor.timeout()); - QCOMPARE(resultCounter.count(), 1); + QCOMPARE(resultCounter.size(), 1); f.resume(); // overwrite with lvalue @@ -3620,9 +4030,9 @@ void tst_QFuture::rejectResultOverwrite() QTimer::singleShot(50, [&f]() { f.suspend(); // should exit the loop }); - eventProcessor.enterLoopMSecs(2000); + eventProcessor.enterLoop(DefaultWaitTime); QVERIFY(!eventProcessor.timeout()); - QCOMPARE(resultCounter.count(), 1); + QCOMPARE(resultCounter.size(), 1); f.resume(); QCOMPARE(f.results(), initResults); } @@ -3659,9 +4069,9 @@ void tst_QFuture::rejectPendingResultOverwrite() }); // Run event loop, QCoreApplication::postEvent is in use // in QFutureInterface: - eventProcessor.enterLoopMSecs(2000); + eventProcessor.enterLoop(DefaultWaitTime); QVERIFY(!eventProcessor.timeout()); - QCOMPARE(resultCounter.count(), 1); + QCOMPARE(resultCounter.size(), 1); f.resume(); } @@ -3703,9 +4113,9 @@ void tst_QFuture::rejectPendingResultOverwrite() QTimer::singleShot(50, [&f]() { f.suspend(); // should exit the loop }); - eventProcessor.enterLoopMSecs(2000); + eventProcessor.enterLoop(DefaultWaitTime); QVERIFY(!eventProcessor.timeout()); - QCOMPARE(resultCounter.count(), 1); + QCOMPARE(resultCounter.size(), 1); f.resume(); } @@ -3718,6 +4128,9 @@ void tst_QFuture::rejectPendingResultOverwrite() void tst_QFuture::createReadyFutures() { +#if QT_DEPRECATED_SINCE(6, 10) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED // using const T & { const int val = 42; @@ -3753,6 +4166,30 @@ void tst_QFuture::createReadyFutures() QCOMPARE(f.resultCount(), 3); QCOMPARE(f.results(), values); } +QT_WARNING_POP +#endif // QT_DEPRECATED_SINCE(6, 10) + + // test makeReadyValueFuture<T>() + { + const int val = 42; + auto f = QtFuture::makeReadyValueFuture(val); + QCOMPARE_EQ(f.result(), val); + + int otherVal = 42; + f = QtFuture::makeReadyValueFuture(otherVal); + QCOMPARE_EQ(f.result(), otherVal); + } + { + auto f = QtFuture::makeReadyValueFuture(std::make_unique<int>(42)); + QCOMPARE(*f.takeResult(), 42); + } + // test makeReadyVoidFuture() + { + auto f = QtFuture::makeReadyVoidFuture(); + QVERIFY(f.isStarted()); + QVERIFY(!f.isRunning()); + QVERIFY(f.isFinished()); + } #ifndef QT_NO_EXCEPTIONS // using QException @@ -3781,12 +4218,205 @@ void tst_QFuture::createReadyFutures() QVERIFY(caught); } #endif + + // testing makeReadyRangeFuture with various containers + { + const QList<int> expectedResult{1, 2, 3}; + + const QList<int> list{1, 2, 3}; + auto f = QtFuture::makeReadyRangeFuture(list); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + + QVarLengthArray<int> varArray{1, 2, 3}; + f = QtFuture::makeReadyRangeFuture(varArray); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + + std::vector<int> vec{1, 2, 3}; + f = QtFuture::makeReadyRangeFuture(std::move(vec)); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + + f = QtFuture::makeReadyRangeFuture(std::array<int, 3>{1, 2, 3}); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + + f = QtFuture::makeReadyRangeFuture(std::list<int>{1, 2, 3}); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + + std::forward_list<int> fwdlist{1, 2, 3}; + f = QtFuture::makeReadyRangeFuture(fwdlist); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + + const QSet<int> qset{1, 2, 3}; + f = QtFuture::makeReadyRangeFuture(qset); + QCOMPARE_EQ(f.resultCount(), 3); + auto result = f.results(); + std::sort(result.begin(), result.end()); + QCOMPARE_EQ(result, expectedResult); + + const QMap<QString, int> qmap{ + {"one", 1}, + {"two", 2}, + {"three", 3} + }; + f = QtFuture::makeReadyRangeFuture(qmap); + QCOMPARE_EQ(f.resultCount(), 3); + result = f.results(); + std::sort(result.begin(), result.end()); + QCOMPARE_EQ(result, expectedResult); + + std::set<int> stdset{1, 2, 3}; + f = QtFuture::makeReadyRangeFuture(stdset); + QCOMPARE_EQ(f.resultCount(), 3); + result = f.results(); + std::sort(result.begin(), result.end()); + QCOMPARE_EQ(result, expectedResult); + + // testing ValueType[N] overload + const int c_array[] = {1, 2, 3}; + f = QtFuture::makeReadyRangeFuture(c_array); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + + f = QtFuture::makeReadyRangeFuture({1, 2, 3}); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + } + // testing makeReadyRangeFuture with a more complex underlying type + { + QObject obj1; + QObject obj2; + QObject obj3; + + const QList<QObject*> expectedResult{&obj1, &obj2, &obj3}; + + const QList<QObject*> list{&obj1, &obj2, &obj3}; + auto f = QtFuture::makeReadyRangeFuture(list); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + + std::list<QObject*> stdlist{&obj1, &obj2, &obj3}; + f = QtFuture::makeReadyRangeFuture(std::move(stdlist)); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + + QObject* const c_array[] = {&obj1, &obj2, &obj3}; + f = QtFuture::makeReadyRangeFuture(c_array); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + } +} + +void tst_QFuture::continuationsAfterReadyFutures() +{ + // continuations without a context + { + QFuture<int> f = QtFuture::makeReadyValueFuture(42) + .then([](int val) { + return val + 10; + }) + .onCanceled([]() { + return -1; + }); + QCOMPARE(f.result(), 52); + } + { + auto rangeF = QtFuture::makeReadyRangeFuture({1, 2, 3}); + QFuture<int> f = rangeF + .then([vals = rangeF.results()](auto) { + return vals.last(); + }) + .onCanceled([]() { + return -1; + }); + QCOMPARE(f.result(), 3); + } + { + QFuture<int> f = QtFuture::makeReadyVoidFuture() + .then([]() { + return 1; + }) + .onCanceled([]() { + return -1; + }); + QCOMPARE(f.result(), 1); + } +#ifndef QT_NO_EXCEPTIONS + { + QException e; + QFuture<int> f = QtFuture::makeExceptionalFuture<int>(e) + .then([](int) { + return 1; + }) + .onCanceled([]() { + return -1; + }) + .onFailed([](const QException &) { + return -2; + }); + QCOMPARE(f.result(), -2); + } +#endif + + // continuations with a context + QObject context; + { + QFuture<int> f = QtFuture::makeReadyValueFuture(42) + .then(&context, [](int val) { + return val + 10; + }) + .onCanceled([]() { + return -1; + }); + QCOMPARE(f.result(), 52); + } + { + auto rangeF = QtFuture::makeReadyRangeFuture({1, 2, 3}); + QFuture<int> f = rangeF + .then(&context, [vals = rangeF.results()](auto) { + return vals.last(); + }) + .onCanceled([]() { + return -1; + }); + QCOMPARE(f.result(), 3); + } + { + QFuture<int> f = QtFuture::makeReadyVoidFuture() + .then(&context, []() { + return 1; + }) + .onCanceled([]() { + return -1; + }); + QCOMPARE(f.result(), 1); + } +#ifndef QT_NO_EXCEPTIONS + { + QException e; + QFuture<int> f = QtFuture::makeExceptionalFuture<int>(e) + .then(&context, [](int) { + return 1; + }) + .onCanceled([]() { + return -1; + }) + .onFailed([](const QException &) { + return -2; + }); + QCOMPARE(f.result(), -2); + } +#endif } void tst_QFuture::getFutureInterface() { const int val = 42; - QFuture<int> f = QtFuture::makeReadyFuture(val); + QFuture<int> f = QtFuture::makeReadyValueFuture(val); auto interface = QFutureInterfaceBase::get(f); QCOMPARE(interface.resultCount(), 1); @@ -3800,7 +4430,7 @@ void tst_QFuture::convertQMetaType() QVERIFY(QMetaType::canConvert(intType, voidType)); const int val = 42; - QFuture<int> f = QtFuture::makeReadyFuture(val); + QFuture<int> f = QtFuture::makeReadyValueFuture(val); auto variant = QVariant::fromValue(f); QVERIFY(variant.convert(voidType)); @@ -3929,7 +4559,7 @@ void tst_QFuture::whenAllIteratorsWithFailed() p1.finish(); QVERIFY(finished); #else - QSKIP("Exceptions are disabled, skipping the test") + QSKIP("Exceptions are disabled, skipping the test"); #endif } @@ -4013,7 +4643,7 @@ void tst_QFuture::whenAllDifferentTypesWithCanceled() QPromise<int> pInt; QPromise<QString> pString; - const QString someValue = u"some value"_qs; + const QString someValue = u"some value"_s; bool finished = false; using Futures = std::variant<QFuture<int>, QFuture<QString>>; @@ -4052,7 +4682,7 @@ void tst_QFuture::whenAllDifferentTypesWithFailed() QPromise<int> pInt; QPromise<QString> pString; - const QString someValue = u"some value"_qs; + const QString someValue = u"some value"_s; bool finished = false; using Futures = std::variant<QFuture<int>, QFuture<QString>>; @@ -4310,5 +4940,418 @@ void tst_QFuture::whenAnyDifferentTypesWithFailed() #endif } +void tst_QFuture::continuationOverride() +{ + QPromise<int> p; + bool firstExecuted = false; + bool secondExecuted = false; + + QTest::ignoreMessage(QtWarningMsg, + "Adding a continuation to a future which already has a continuation. " + "The existing continuation is overwritten."); + + QFuture<int> f1 = p.future(); + f1.then([&firstExecuted](int) { + firstExecuted = true; + }); + + QFuture<int> f2 = p.future(); + f2.then([&secondExecuted](int) { + secondExecuted = true; + }); + + p.start(); + p.addResult(42); + p.finish(); + + QVERIFY(p.future().isFinished()); + QVERIFY(!firstExecuted); + QVERIFY(secondExecuted); +} + +struct InstanceCounter +{ + InstanceCounter() { ++count; } + InstanceCounter(const InstanceCounter &) { ++count; } + ~InstanceCounter() { --count; } + static int count; +}; +int InstanceCounter::count = 0; + +void tst_QFuture::continuationsDontLeak() +{ + { + // QFuture isn't started and isn't finished (has no state) + QPromise<InstanceCounter> promise; + auto future = promise.future(); + + bool continuationIsRun = false; + future.then([future, &continuationIsRun](InstanceCounter) { continuationIsRun = true; }); + + promise.addResult(InstanceCounter {}); + + QVERIFY(!continuationIsRun); + } + QCOMPARE(InstanceCounter::count, 0); + + { + // QFuture is started, but not finished + QPromise<InstanceCounter> promise; + auto future = promise.future(); + + bool continuationIsRun = false; + future.then([future, &continuationIsRun](InstanceCounter) { continuationIsRun = true; }); + + promise.start(); + promise.addResult(InstanceCounter {}); + + QVERIFY(!continuationIsRun); + } + QCOMPARE(InstanceCounter::count, 0); + + { + // QFuture is started and finished, the continuation is run + QPromise<InstanceCounter> promise; + auto future = promise.future(); + + bool continuationIsRun = false; + future.then([future, &continuationIsRun](InstanceCounter) { + QVERIFY(future.isFinished()); + continuationIsRun = true; + }); + + promise.start(); + promise.addResult(InstanceCounter {}); + promise.finish(); + + QVERIFY(continuationIsRun); + } + QCOMPARE(InstanceCounter::count, 0); + + { + // QTBUG-116731: Must pass with ASan enabled + bool continuationIsRun = false; + auto f = QtFuture::makeReadyValueFuture(42); + QtFuture::whenAll(f).then([&](auto) { continuationIsRun = true; }); + QVERIFY(continuationIsRun); + } + + { + // QTBUG-116731: Must pass with ASan enabled + bool continuationIsRun = false; + auto f = QtFuture::makeReadyValueFuture(42); + QList fs{f}; + QtFuture::whenAll(fs.begin(), fs.end()).then([&](auto) { continuationIsRun = true; }); + QVERIFY(continuationIsRun); + } + + { + // QTBUG-116731: Must pass with ASan enabled + bool continuationIsRun = false; + auto f = QtFuture::makeReadyValueFuture(42); + QtFuture::whenAny(f).then([&](auto) { continuationIsRun = true; }); + QVERIFY(continuationIsRun); + } + + { + // QTBUG-116731: Must pass with ASan enabled + bool continuationIsRun = false; + auto f = QtFuture::makeReadyValueFuture(42); + QList fs{f}; + QtFuture::whenAny(fs.begin(), fs.end()).then([&](auto) { continuationIsRun = true; }); + QVERIFY(continuationIsRun); + } +} + +// This test checks that we do not get use-after-free +void tst_QFuture::cancelAfterFinishWithContinuations() +{ + QFuture<void> future; + bool continuationIsRun = false; + bool cancelCalled = false; + { + QPromise<void> promise; + future = promise.future(); + + future.then([&continuationIsRun]() { + continuationIsRun = true; + }).onCanceled([&cancelCalled]() { + cancelCalled = true; + }); + + promise.start(); + promise.finish(); + } + + QVERIFY(continuationIsRun); + future.cancel(); + QVERIFY(!cancelCalled); +} + +void tst_QFuture::unwrap() +{ + // The nested future succeeds + { + QPromise<int> p; + QFuture<QFuture<int>> f = p.future().then([] (int value) { + QFuture<int> nested = QtConcurrent::run([value] { + return value + 1; + }); + return nested; + }); + + QFuture<int> unwrapped = f.unwrap(); + QVERIFY(!unwrapped.isStarted()); + QVERIFY(!unwrapped.isFinished()); + + p.start(); + p.addResult(42); + p.finish(); + + unwrapped.waitForFinished(); + + QVERIFY(unwrapped.isStarted()); + QVERIFY(unwrapped.isFinished()); + QCOMPARE(unwrapped.result(), 43); + } + + // The nested future succeeds with multiple results + { + QPromise<int> p; + QFuture<QFuture<int>> f = p.future().then([] (int value) { + QPromise<int> nested; + nested.start(); + nested.addResult(++value); + nested.addResult(++value); + nested.addResult(++value); + nested.finish(); + return nested.future(); + }); + + QFuture<int> unwrapped = f.unwrap(); + QVERIFY(!unwrapped.isStarted()); + QVERIFY(!unwrapped.isFinished()); + + p.start(); + p.addResult(42); + p.finish(); + + f.waitForFinished(); + + QVERIFY(unwrapped.isStarted()); + QVERIFY(unwrapped.isFinished()); + QCOMPARE(unwrapped.results(), QList<int>() << 43 << 44 << 45); + } + + // The chain is canceled, check that unwrap() propagates the cancellation. + { + QPromise<int> p; + QFuture<int> f = p.future().then([] (int value) { + QFuture<int> nested = QtConcurrent::run([value] { + return value + 1; + }); + return nested; + }).unwrap().then([] (int result) { + return result; + }).onCanceled([] { + return -1; + }); + + p.start(); + p.future().cancel(); + p.finish(); + + f.waitForFinished(); + + QVERIFY(f.isStarted()); + QVERIFY(f.isFinished()); + QCOMPARE(f.result(), -1); + } + +#ifndef QT_NO_EXCEPTIONS + // The chain has an exception, check that unwrap() propagates it. + { + QPromise<int> p; + QFuture<int> f = p.future().then([] (int value) { + QFuture<int> nested = QtConcurrent::run([value] { + return value + 1; + }); + return nested; + }).unwrap().then([] (int result) { + return result; + }).onFailed([] (QException &) { + return -1; + }); + + p.start(); + p.setException(QException()); + p.finish(); + + f.waitForFinished(); + + QVERIFY(f.isStarted()); + QVERIFY(f.isFinished()); + QCOMPARE(f.result(), -1); + } + +#endif // QT_NO_EXCEPTIONS + + // The nested future is canceled + { + QPromise<int> p; + QFuture<int> f = p.future().then([] (int value) { + QFuture<int> nested = QtConcurrent::run([value] { + return value + 1; + }); + nested.cancel(); + return nested; + }).unwrap().then([] (int result) { + return result; + }).onCanceled([] { + return -1; + }); + + p.start(); + p.addResult(42); + p.finish(); + + f.waitForFinished(); + + QVERIFY(f.isStarted()); + QVERIFY(f.isFinished()); + QCOMPARE(f.result(), -1); + } + +#ifndef QT_NO_EXCEPTIONS + // The nested future fails with an exception + { + QPromise<int> p; + QFuture<int> f = p.future().then([] (int value) { + QFuture<int> nested = QtConcurrent::run([value] { + throw QException(); + return value + 1; + }); + return nested; + }).unwrap().then([] (int result) { + return result; + }).onFailed([] (QException &) { + return -1; + }); + + p.start(); + p.addResult(42); + p.finish(); + + f.waitForFinished(); + + QVERIFY(f.isStarted()); + QVERIFY(f.isFinished()); + QCOMPARE(f.result(), -1); + } +#endif // QT_NO_EXCEPTIONS + + // Check that continuations are called in the right order + { + QPromise<void> p; + + std::atomic<bool> firstThenInvoked = false; + std::atomic<bool> secondThenInvoked = false; + std::atomic<bool> nestedThenInvoked = false; + auto f = p.future().then([&] { + if (!firstThenInvoked && !secondThenInvoked && !nestedThenInvoked) + firstThenInvoked = true; + QFuture<void> nested = QtConcurrent::run([&] { + QVERIFY(firstThenInvoked); + QVERIFY(!nestedThenInvoked); + QVERIFY(!secondThenInvoked); + nestedThenInvoked = true; + }); + return nested; + }).unwrap().then([&] { + QVERIFY(firstThenInvoked); + QVERIFY(nestedThenInvoked); + QVERIFY(!secondThenInvoked); + secondThenInvoked = true; + }); + + QVERIFY(!firstThenInvoked); + QVERIFY(!nestedThenInvoked); + QVERIFY(!secondThenInvoked); + + p.start(); + p.finish(); + + f.waitForFinished(); + + if (QTest::currentTestFailed()) + return; + + QVERIFY(firstThenInvoked); + QVERIFY(nestedThenInvoked); + QVERIFY(secondThenInvoked); + } + + // Unwrap multiple nested futures + { + QPromise<int> p; + QFuture<QFuture<QFuture<int>>> f = p.future().then([] (int value) { + QFuture<QFuture<int>> nested = QtConcurrent::run([value] { + QFuture<int> doubleNested = QtConcurrent::run([value] { + return value + 1; + }); + return doubleNested; + }); + return nested; + }); + + QFuture<int> unwrapped = f.unwrap(); + QVERIFY(!unwrapped.isStarted()); + QVERIFY(!unwrapped.isFinished()); + + p.start(); + p.addResult(42); + p.finish(); + + unwrapped.waitForFinished(); + + QVERIFY(unwrapped.isStarted()); + QVERIFY(unwrapped.isFinished()); + QCOMPARE(unwrapped.result(), 43); + } + + // Unwrap multiple nested void futures + { + QPromise<void> p; + std::atomic<bool> nestedInvoked = false; + std::atomic<bool> doubleNestedInvoked = false; + QFuture<QFuture<QFuture<void>>> f = p.future().then([&] { + QFuture<QFuture<void>> nested = QtConcurrent::run([&] { + QFuture<void> doubleNested = QtConcurrent::run([&] { + doubleNestedInvoked = true; + }); + nestedInvoked = true; + return doubleNested; + }); + return nested; + }); + + QFuture<void> unwrapped = f.unwrap(); + QVERIFY(!nestedInvoked); + QVERIFY(!doubleNestedInvoked); + QVERIFY(!unwrapped.isStarted()); + QVERIFY(!unwrapped.isFinished()); + + p.start(); + p.finish(); + + unwrapped.waitForFinished(); + + QVERIFY(unwrapped.isStarted()); + QVERIFY(unwrapped.isFinished()); + QVERIFY(nestedInvoked); + QVERIFY(doubleNestedInvoked); + } +} + QTEST_MAIN(tst_QFuture) #include "tst_qfuture.moc" |