summaryrefslogtreecommitdiffstats
path: root/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/corelib/thread/qfuture/tst_qfuture.cpp')
-rw-r--r--tests/auto/corelib/thread/qfuture/tst_qfuture.cpp1963
1 files changed, 1856 insertions, 107 deletions
diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp
index a6be2e9727..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
@@ -143,7 +203,10 @@ private slots:
void onFailedForMoveOnlyTypes();
#endif
void onCanceled();
+ void cancelContinuations();
+ void continuationsWithContext_data();
void continuationsWithContext();
+ void continuationsWithMoveOnlyLambda();
#if 0
// TODO: enable when QFuture::takeResults() is enabled
void takeResults();
@@ -163,10 +226,30 @@ private slots:
void rejectPendingResultOverwrite();
void createReadyFutures();
+ void continuationsAfterReadyFutures();
void getFutureInterface();
void convertQMetaType();
+ void whenAllIterators();
+ void whenAllIteratorsWithCanceled();
+ void whenAllIteratorsWithFailed();
+ void whenAllDifferentTypes();
+ void whenAllDifferentTypesWithCanceled();
+ void whenAllDifferentTypesWithFailed();
+ void whenAnyIterators();
+ void whenAnyIteratorsWithCanceled();
+ void whenAnyIteratorsWithFailed();
+ void whenAnyDifferentTypes();
+ void whenAnyDifferentTypesWithCanceled();
+ void whenAnyDifferentTypesWithFailed();
+
+ void continuationOverride();
+ void continuationsDontLeak();
+ void cancelAfterFinishWithContinuations();
+
+ void unwrap();
+
private:
using size_type = std::vector<int>::size_type;
@@ -180,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;
@@ -187,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());
@@ -195,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();
@@ -217,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();
@@ -240,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);
@@ -271,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);
@@ -301,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);
@@ -313,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);
@@ -324,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);
@@ -332,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);
@@ -366,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);
@@ -403,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);
@@ -449,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);
@@ -463,7 +580,9 @@ void tst_QFuture::resultStore()
}
{
- ResultStoreInt store;
+ QtPrivate::ResultStoreBase store;
+ IntResultsCleaner cleanGuard(store);
+
store.addResult(2, &int0);
QCOMPARE(store.count(), 0);
@@ -475,7 +594,9 @@ void tst_QFuture::resultStore()
}
{
- ResultStoreInt store;
+ QtPrivate::ResultStoreBase store;
+ IntResultsCleaner cleanGuard(store);
+
store.addResults(2, &vec1);
QCOMPARE(store.count(), 0);
@@ -487,7 +608,9 @@ void tst_QFuture::resultStore()
}
{
- ResultStoreInt store;
+ QtPrivate::ResultStoreBase store;
+ IntResultsCleaner cleanGuard(store);
+
store.addResults(2, &vec1);
QCOMPARE(store.count(), 0);
@@ -495,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);
@@ -507,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);
@@ -520,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);
@@ -530,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);
@@ -543,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);
@@ -558,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);
@@ -586,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);
@@ -965,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);
}
@@ -1844,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();
}
@@ -2129,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>
@@ -2882,15 +3039,220 @@ void tst_QFuture::onCanceled()
#endif // QT_NO_EXCEPTIONS
}
+void tst_QFuture::cancelContinuations()
+{
+ // The chain is cancelled in the middle of execution of continuations
+ {
+ QPromise<int> promise;
+
+ int checkpoint = 0;
+ auto future = promise.future().then([&](int value) {
+ ++checkpoint;
+ return value + 1;
+ }).then([&](int value) {
+ ++checkpoint;
+ promise.future().cancel();
+ return value + 1;
+ }).then([&](int value) {
+ ++checkpoint;
+ return value + 1;
+ }).onCanceled([] {
+ return -1;
+ });
+
+ promise.start();
+ promise.addResult(42);
+ promise.finish();
+
+ QCOMPARE(future.result(), -1);
+ QCOMPARE(checkpoint, 2);
+ }
+
+ // The chain is cancelled before the execution of continuations
+ {
+ auto f = QtFuture::makeReadyValueFuture(42);
+ f.cancel();
+
+ int checkpoint = 0;
+ auto future = f.then([&](int value) {
+ ++checkpoint;
+ return value + 1;
+ }).then([&](int value) {
+ ++checkpoint;
+ return value + 1;
+ }).then([&](int value) {
+ ++checkpoint;
+ return value + 1;
+ }).onCanceled([] {
+ return -1;
+ });
+
+ QCOMPARE(future.result(), -1);
+ QCOMPARE(checkpoint, 0);
+ }
+
+ // The chain is canceled partially, through an intermediate future
+ {
+ QPromise<int> promise;
+
+ int checkpoint = 0;
+ auto intermediate = promise.future().then([&](int value) {
+ ++checkpoint;
+ return value + 1;
+ });
+
+ auto future = intermediate.then([&](int value) {
+ ++checkpoint;
+ return value + 1;
+ }).then([&](int value) {
+ ++checkpoint;
+ return value + 1;
+ }).onCanceled([] {
+ return -1;
+ });
+
+ promise.start();
+ promise.addResult(42);
+
+ // This should cancel only the chain starting from intermediate
+ intermediate.cancel();
+
+ promise.finish();
+
+ QCOMPARE(future.result(), -1);
+ QCOMPARE(checkpoint, 1);
+ }
+
+#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 handled inside
+ // the continuations.
+ {
+ QPromise<int> promise;
+
+ int checkpoint = 0;
+ auto future = promise.future().then([&](int value) {
+ ++checkpoint;
+ throw QException();
+ return value + 1;
+ }).then([&](QFuture<int> future) {
+ try {
+ auto res = future.result();
+ Q_UNUSED(res);
+ } catch (const QException &) {
+ ++checkpoint;
+ }
+ return 2;
+ }).then([&](int value) {
+ ++checkpoint;
+ promise.future().cancel();
+ return value + 1;
+ }).then([&](int value) {
+ ++checkpoint;
+ return value + 1;
+ }).onCanceled([] {
+ return -1;
+ });
+
+ promise.start();
+ promise.addResult(42);
+ promise.finish();
+
+ QCOMPARE(future.result(), -1);
+ 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()
{
@@ -2903,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;
});
@@ -2924,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;
});
@@ -2939,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()
{
@@ -2951,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;
});
@@ -2965,11 +3361,72 @@ void tst_QFuture::continuationsWithContext()
QCOMPARE(future.result(), 2);
}
#endif // QT_NO_EXCEPTIONS
+}
- context->deleteLater();
+void tst_QFuture::continuationsWithMoveOnlyLambda()
+{
+ // .then()
+ {
+ std::unique_ptr<int> uniquePtr(new int(42));
+ auto future = QtFuture::makeReadyVoidFuture()
+ .then([p = std::move(uniquePtr)] { return *p; });
+ QCOMPARE(future.result(), 42);
+ }
+ // .then() with thread pool
+ {
+ QThreadPool pool;
- thread.quit();
- thread.wait();
+ std::unique_ptr<int> uniquePtr(new int(42));
+ auto future = QtFuture::makeReadyVoidFuture()
+ .then(&pool, [p = std::move(uniquePtr)] { return *p; });
+ QCOMPARE(future.result(), 42);
+ }
+ // .then() with context
+ {
+ QObject object;
+
+ std::unique_ptr<int> uniquePtr(new int(42));
+ auto future = QtFuture::makeReadyVoidFuture()
+ .then(&object, [p = std::move(uniquePtr)] { return *p; });
+ QCOMPARE(future.result(), 42);
+ }
+
+ // .onCanceled()
+ {
+ std::unique_ptr<int> uniquePtr(new int(42));
+ auto future =
+ createCanceledFuture<int>().onCanceled([p = std::move(uniquePtr)] { return *p; });
+ QCOMPARE(future.result(), 42);
+ }
+
+ // .onCanceled() with context
+ {
+ QObject object;
+
+ std::unique_ptr<int> uniquePtr(new int(42));
+ auto future = createCanceledFuture<int>().onCanceled(
+ &object, [p = std::move(uniquePtr)] { return *p; });
+ QCOMPARE(future.result(), 42);
+ }
+
+#ifndef QT_NO_EXCEPTIONS
+ // .onFailed()
+ {
+ std::unique_ptr<int> uniquePtr(new int(42));
+ auto future = QtFuture::makeExceptionalFuture<int>(QException())
+ .onFailed([p = std::move(uniquePtr)] { return *p; });
+ QCOMPARE(future.result(), 42);
+ }
+ // .onFailed() with context
+ {
+ QObject object;
+
+ std::unique_ptr<int> uniquePtr(new int(42));
+ auto future = QtFuture::makeExceptionalFuture<int>(QException())
+ .onFailed(&object, [p = std::move(uniquePtr)] { return *p; });
+ QCOMPARE(future.result(), 42);
+ }
+#endif // QT_NO_EXCEPTIONS
}
void tst_QFuture::testSingleResult(const UniquePtr &p)
@@ -2994,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);
}
@@ -3092,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));
};
@@ -3105,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");
@@ -3176,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);
}
@@ -3224,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);
}
@@ -3257,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(std::get<0>(result), 42);
- QCOMPARE(std::get<1>(result), 42.5);
- QCOMPARE(std::get<2>(result), "42");
+ 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(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
@@ -3278,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());
@@ -3304,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();
@@ -3331,7 +3947,6 @@ void tst_QFuture::waitForFinished()
QVERIFY(waitingThread->wait());
QVERIFY(waitingThread->isFinished());
-#endif
}
void tst_QFuture::rejectResultOverwrite_data()
@@ -3376,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
@@ -3415,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);
}
@@ -3454,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();
}
@@ -3498,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();
}
@@ -3513,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;
@@ -3548,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
@@ -3576,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);
@@ -3595,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));
@@ -3604,5 +4439,919 @@ void tst_QFuture::convertQMetaType()
QVERIFY(voidFuture.isFinished());
}
+template<class OutputContainer>
+void testWhenAllIterators()
+{
+ QPromise<int> p0;
+ QPromise<int> p1;
+ QPromise<int> p2;
+ QList<QFuture<int>> futures = { p0.future(), p1.future(), p2.future() };
+
+ bool finished = false;
+ QFuture<OutputContainer> whenAll;
+ if constexpr (std::is_same_v<QList<QFuture<int>>, OutputContainer>)
+ whenAll = QtFuture::whenAll(futures.begin(), futures.end());
+ else
+ whenAll = QtFuture::whenAll<OutputContainer>(futures.begin(), futures.end());
+ whenAll.then([&](const OutputContainer &output) {
+ QCOMPARE(output.size(), 3u);
+ QCOMPARE(output[0].result(), 0);
+ QCOMPARE(output[1].result(), 1);
+ QCOMPARE(output[2].result(), 2);
+ finished = true;
+ });
+ QVERIFY(whenAll.isRunning());
+
+ p0.start();
+ p0.addResult(0);
+ p0.finish();
+ QVERIFY(whenAll.isRunning());
+
+ p2.start();
+ p2.addResult(2);
+ p2.finish();
+ QVERIFY(whenAll.isRunning());
+
+ p1.start();
+ p1.addResult(1);
+ p1.finish();
+ QVERIFY(!whenAll.isRunning());
+ QVERIFY(finished);
+
+ // Try with empty sequence
+ QFuture<OutputContainer> whenAllEmpty;
+ if constexpr (std::is_same_v<QList<QFuture<int>>, OutputContainer>)
+ whenAllEmpty = QtFuture::whenAll(futures.end(), futures.end());
+ else
+ whenAllEmpty = QtFuture::whenAll<OutputContainer>(futures.end(), futures.end());
+ QVERIFY(whenAllEmpty.isStarted());
+ QVERIFY(whenAllEmpty.isFinished());
+ QVERIFY(whenAllEmpty.result().empty());
+}
+
+void tst_QFuture::whenAllIterators()
+{
+ // Try with different output containers
+ testWhenAllIterators<QList<QFuture<int>>>();
+ if (QTest::currentTestFailed())
+ QSKIP("testWhenAllIterators() with QList failed!");
+
+ testWhenAllIterators<std::vector<QFuture<int>>>();
+ if (QTest::currentTestFailed())
+ QSKIP("testWhenAllIterators() with std::vector failed!");
+
+ testWhenAllIterators<QVarLengthArray<QFuture<int>>>();
+ if (QTest::currentTestFailed())
+ QSKIP("testWhenAllIterators() with QVarLengthArray failed!");
+}
+
+void tst_QFuture::whenAllIteratorsWithCanceled()
+{
+ QPromise<int> p0;
+ QPromise<int> p1;
+ QList<QFuture<int>> futures = { p0.future(), p1.future() };
+ bool finished = false;
+ auto whenAll = QtFuture::whenAll(futures.begin(), futures.end())
+ .then([&](const QList<QFuture<int>> &results) {
+ QCOMPARE(results.size(), 2);
+ QVERIFY(results[0].isCanceled());
+ QVERIFY(!results[1].isCanceled());
+ QCOMPARE(results[1].result(), 1);
+ finished = true;
+ });
+
+ p0.start();
+ p0.future().cancel();
+ p0.finish();
+ QVERIFY(!finished);
+
+ p1.start();
+ p1.addResult(1);
+ p1.finish();
+ QVERIFY(finished);
+}
+
+void tst_QFuture::whenAllIteratorsWithFailed()
+{
+#ifndef QT_NO_EXCEPTIONS
+ QPromise<int> p0;
+ QPromise<int> p1;
+ QList<QFuture<int>> futures = { p0.future(), p1.future() };
+ bool finished = false;
+ auto whenAll = QtFuture::whenAll(futures.begin(), futures.end())
+ .then([&](QList<QFuture<int>> results) {
+ QCOMPARE(results.size(), 2);
+ QCOMPARE(results[1].result(), 1);
+ // A shorter way of handling the exception
+ results[0].onFailed([&](const QException &) {
+ finished = true;
+ return 0;
+ });
+ });
+
+ p0.start();
+ p0.setException(QException());
+ p0.finish();
+ QVERIFY(!finished);
+
+ p1.start();
+ p1.addResult(1);
+ p1.finish();
+ QVERIFY(finished);
+#else
+ QSKIP("Exceptions are disabled, skipping the test");
+#endif
+}
+
+// A helper for std::visit, see https://en.cppreference.com/w/cpp/utility/variant/visit
+template<class... Ts>
+struct overloaded : public Ts...
+{
+ using Ts::operator()...;
+};
+
+// explicit deduction guide
+template<class... Ts>
+overloaded(Ts...)->overloaded<Ts...>;
+
+template<class OutputContainer>
+void testWhenAllDifferentTypes()
+{
+ QPromise<int> pInt1;
+ QPromise<int> pInt2;
+ QPromise<void> pVoid;
+
+ using Futures = std::variant<QFuture<int>, QFuture<int>, QFuture<void>>;
+
+ QFuture<OutputContainer> whenAll;
+ if constexpr (std::is_same_v<QList<Futures>, OutputContainer>) {
+ whenAll = QtFuture::whenAll(pInt1.future(), pInt2.future(), pVoid.future());
+ } else {
+ whenAll =
+ QtFuture::whenAll<OutputContainer>(pInt1.future(), pInt2.future(), pVoid.future());
+ }
+
+ int sumOfInts = 0;
+ whenAll.then([&](const OutputContainer &results) {
+ for (auto future : results) {
+ std::visit(overloaded {
+ [&](const QFuture<int> &f) {
+ QVERIFY(f.isFinished());
+ sumOfInts += f.result();
+ },
+ [](const QFuture<void> &f) { QVERIFY(f.isFinished()); },
+ },
+ future);
+ }
+ });
+
+ pVoid.start();
+ pVoid.finish();
+ QVERIFY(whenAll.isRunning());
+
+ pInt2.start();
+ pInt2.addResult(2);
+ pInt2.finish();
+ QVERIFY(whenAll.isRunning());
+ QCOMPARE(sumOfInts, 0);
+
+ pInt1.start();
+ pInt1.addResult(1);
+ pInt1.finish();
+ QVERIFY(!whenAll.isRunning());
+ QCOMPARE(sumOfInts, 3);
+}
+
+void tst_QFuture::whenAllDifferentTypes()
+{
+ using Futures = std::variant<QFuture<int>, QFuture<int>, QFuture<void>>;
+ testWhenAllDifferentTypes<QList<Futures>>();
+ if (QTest::currentTestFailed())
+ QSKIP("testWhenAllDifferentTypes() with QList failed!");
+
+ testWhenAllDifferentTypes<std::vector<Futures>>();
+ if (QTest::currentTestFailed())
+ QSKIP("testWhenAllDifferentTypes() with std::vector failed!");
+
+ testWhenAllDifferentTypes<QVarLengthArray<Futures>>();
+ if (QTest::currentTestFailed())
+ QSKIP("testWhenAllDifferentTypes() with QVarLengthArray failed!");
+}
+
+void tst_QFuture::whenAllDifferentTypesWithCanceled()
+{
+ QPromise<int> pInt;
+ QPromise<QString> pString;
+
+ const QString someValue = u"some value"_s;
+
+ bool finished = false;
+ using Futures = std::variant<QFuture<int>, QFuture<QString>>;
+ auto whenAll = QtFuture::whenAll(pInt.future(), pString.future())
+ .then([&](const QList<Futures> &results) {
+ finished = true;
+ for (auto future : results) {
+ std::visit(overloaded {
+ [](const QFuture<int> &f) {
+ QVERIFY(f.isFinished());
+ QVERIFY(f.isCanceled());
+ },
+ [&](const QFuture<QString> &f) {
+ QVERIFY(f.isFinished());
+ QCOMPARE(f.result(), someValue);
+ },
+ },
+ future);
+ }
+ });
+
+ pString.start();
+ pString.addResult(someValue);
+ pString.finish();
+ QVERIFY(!finished);
+
+ pInt.start();
+ pInt.future().cancel();
+ pInt.finish();
+ QVERIFY(finished);
+}
+
+void tst_QFuture::whenAllDifferentTypesWithFailed()
+{
+#ifndef QT_NO_EXCEPTIONS
+ QPromise<int> pInt;
+ QPromise<QString> pString;
+
+ const QString someValue = u"some value"_s;
+
+ bool finished = false;
+ using Futures = std::variant<QFuture<int>, QFuture<QString>>;
+ auto whenAll = QtFuture::whenAll(pInt.future(), pString.future())
+ .then([&](const QList<Futures> &results) {
+ finished = true;
+ for (auto future : results) {
+ std::visit(overloaded {
+ [](QFuture<int> f) {
+ QVERIFY(f.isFinished());
+ bool failed = false;
+ // A shorter way of handling the exception
+ f.onFailed([&](const QException &) {
+ failed = true;
+ return -1;
+ });
+ QVERIFY(failed);
+ },
+ [&](const QFuture<QString> &f) {
+ QVERIFY(f.isFinished());
+ QCOMPARE(f.result(), someValue);
+ },
+ },
+ future);
+ }
+ });
+
+ pInt.start();
+ pInt.setException(QException());
+ pInt.finish();
+ QVERIFY(!finished);
+
+ pString.start();
+ pString.addResult(someValue);
+ pString.finish();
+ QVERIFY(finished);
+#else
+ QSKIP("Exceptions are disabled, skipping the test")
+#endif
+}
+
+void tst_QFuture::whenAnyIterators()
+{
+ QPromise<int> p0;
+ QPromise<int> p1;
+ QPromise<int> p2;
+ QList<QFuture<int>> futures = { p0.future(), p1.future(), p2.future() };
+
+ auto whenAny = QtFuture::whenAny(futures.begin(), futures.end());
+ int count = 0;
+ whenAny.then([&](const QtFuture::WhenAnyResult<int> &result) {
+ QCOMPARE(result.index, 1);
+ QCOMPARE(result.future.result(), 1);
+ QVERIFY(!futures[0].isFinished());
+ QVERIFY(futures[1].isFinished());
+ QVERIFY(!futures[2].isFinished());
+ ++count;
+ });
+
+ p0.start();
+ p1.start();
+ p2.start();
+ p0.addResult(0);
+ p1.addResult(1);
+ p2.addResult(2);
+ QVERIFY(!whenAny.isFinished());
+ QCOMPARE(count, 0);
+
+ p1.finish();
+ QVERIFY(whenAny.isFinished());
+ QCOMPARE(count, 1);
+
+ p0.finish();
+ QCOMPARE(count, 1);
+
+ p2.finish();
+ QCOMPARE(count, 1);
+
+ auto whenAnyEmpty = QtFuture::whenAny(futures.end(), futures.end());
+ QVERIFY(whenAnyEmpty.isStarted());
+ QVERIFY(whenAnyEmpty.isFinished());
+ QCOMPARE(whenAnyEmpty.result().index, -1);
+ auto whenAnyEmptyResult = whenAnyEmpty.result().future;
+ QVERIFY(whenAnyEmptyResult.isStarted());
+ QVERIFY(whenAnyEmptyResult.isFinished());
+ QVERIFY(whenAnyEmptyResult.isCanceled());
+}
+
+void tst_QFuture::whenAnyIteratorsWithCanceled()
+{
+ QPromise<int> p0;
+ QPromise<int> p1;
+ QList<QFuture<int>> futures = { p0.future(), p1.future() };
+ int count = 0;
+ auto whenAny = QtFuture::whenAny(futures.begin(), futures.end())
+ .then([&](const QtFuture::WhenAnyResult<int> &result) {
+ QCOMPARE(result.index, 1);
+ QVERIFY(result.future.isCanceled());
+ QVERIFY(!futures[0].isFinished());
+ QVERIFY(futures[1].isFinished());
+ ++count;
+ });
+
+ p1.start();
+ p1.future().cancel();
+ p1.finish();
+ QVERIFY(whenAny.isFinished());
+ QCOMPARE(count, 1);
+
+ p0.start();
+ p0.addResult(0);
+ p0.finish();
+ QCOMPARE(count, 1);
+}
+
+void tst_QFuture::whenAnyIteratorsWithFailed()
+{
+#ifndef QT_NO_EXCEPTIONS
+ QPromise<int> p0;
+ QPromise<int> p1;
+ QList<QFuture<int>> futures = { p0.future(), p1.future() };
+ int count = 0;
+ auto whenAny = QtFuture::whenAny(futures.begin(), futures.end())
+ .then([&](QtFuture::WhenAnyResult<int> result) {
+ QCOMPARE(result.index, 1);
+ QVERIFY(p1.future().isFinished());
+ QVERIFY(!p0.future().isFinished());
+ // A shorter way of handling the exception
+ result.future.onFailed([&](const QException &) {
+ ++count;
+ return 0;
+ });
+ });
+
+ p1.start();
+ p1.setException(QException());
+ p1.finish();
+ QCOMPARE(count, 1);
+
+ p0.start();
+ p0.addResult(0);
+ p0.finish();
+ QCOMPARE(count, 1);
+#else
+ QSKIP("Exceptions are disabled, skipping the test")
+#endif
+}
+
+void tst_QFuture::whenAnyDifferentTypes()
+{
+ QPromise<int> pInt1;
+ QPromise<int> pInt2;
+ QPromise<void> pVoid;
+
+ auto whenAny = QtFuture::whenAny(pInt1.future(), pInt2.future(), pVoid.future());
+ int count = 0;
+ whenAny.then([&](const std::variant<QFuture<int>, QFuture<int>, QFuture<void>> &result) {
+ QCOMPARE(result.index(), 1u);
+ std::visit(overloaded { [&](const QFuture<int> &future) {
+ QVERIFY(future.isFinished());
+ QCOMPARE(future.result(), 2);
+ ++count;
+ },
+ [](auto) { QFAIL("The wrong future completed."); }
+ },
+ result);
+ });
+
+ pInt2.start();
+ pInt1.start();
+ pVoid.start();
+ pInt1.addResult(1);
+ pInt2.addResult(2);
+
+ QVERIFY(!whenAny.isFinished());
+ QCOMPARE(count, 0);
+
+ pInt2.finish();
+ QVERIFY(whenAny.isFinished());
+ QCOMPARE(count, 1);
+
+ pInt1.finish();
+ QCOMPARE(count, 1);
+
+ pVoid.finish();
+ QCOMPARE(count, 1);
+}
+
+void tst_QFuture::whenAnyDifferentTypesWithCanceled()
+{
+ QPromise<int> pInt;
+ QPromise<void> pVoid;
+
+ int count = 0;
+ auto whenAny = QtFuture::whenAny(pInt.future(), pVoid.future())
+ .then([&](const std::variant<QFuture<int>, QFuture<void>> &result) {
+ QCOMPARE(result.index(), 0u);
+ std::visit(overloaded { [&](const QFuture<int> &future) {
+ QVERIFY(future.isFinished());
+ QVERIFY(future.isCanceled());
+ ++count;
+ },
+ [](auto) {
+ QFAIL("The wrong future completed.");
+ }
+ },
+ result);
+ });
+
+ pInt.start();
+ pInt.future().cancel();
+ pInt.finish();
+ QCOMPARE(count, 1);
+
+ pVoid.start();
+ pVoid.finish();
+ QCOMPARE(count, 1);
+}
+
+void tst_QFuture::whenAnyDifferentTypesWithFailed()
+{
+#ifndef QT_NO_EXCEPTIONS
+ QPromise<int> pInt;
+ QPromise<void> pVoid;
+
+ int count = 0;
+ auto whenAny = QtFuture::whenAny(pInt.future(), pVoid.future())
+ .then([&](const std::variant<QFuture<int>, QFuture<void>> &result) {
+ QCOMPARE(result.index(), 0u);
+ std::visit(overloaded { [&](QFuture<int> future) {
+ QVERIFY(future.isFinished());
+ // A shorter way of handling the exception
+ future.onFailed([&](const QException &) {
+ ++count;
+ return -1;
+ });
+ },
+ [](auto) {
+ QFAIL("The wrong future completed.");
+ }
+ },
+ result);
+ });
+
+ pInt.start();
+ pInt.setException(QException());
+ pInt.finish();
+ QCOMPARE(count, 1);
+
+ pVoid.start();
+ pVoid.finish();
+ QCOMPARE(count, 1);
+#else
+ QSKIP("Exceptions are disabled, skipping the test")
+#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"