diff options
Diffstat (limited to 'tests/auto/corelib/thread/qfuture')
-rw-r--r-- | tests/auto/corelib/thread/qfuture/.prev_CMakeLists.txt | 14 | ||||
-rw-r--r-- | tests/auto/corelib/thread/qfuture/CMakeLists.txt | 23 | ||||
-rw-r--r-- | tests/auto/corelib/thread/qfuture/qfuture.pro | 5 | ||||
-rw-r--r-- | tests/auto/corelib/thread/qfuture/tst_qfuture.cpp | 2566 |
4 files changed, 2431 insertions, 177 deletions
diff --git a/tests/auto/corelib/thread/qfuture/.prev_CMakeLists.txt b/tests/auto/corelib/thread/qfuture/.prev_CMakeLists.txt deleted file mode 100644 index 6908bd678c..0000000000 --- a/tests/auto/corelib/thread/qfuture/.prev_CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -# Generated from qfuture.pro. - -##################################################################### -## tst_qfuture Test: -##################################################################### - -qt_add_test(tst_qfuture - SOURCES - tst_qfuture.cpp - DEFINES - -QT_NO_JAVA_STYLE_ITERATORS - PUBLIC_LIBRARIES - Qt::CorePrivate -) diff --git a/tests/auto/corelib/thread/qfuture/CMakeLists.txt b/tests/auto/corelib/thread/qfuture/CMakeLists.txt index 6fdfc977e9..ba5730d5cf 100644 --- a/tests/auto/corelib/thread/qfuture/CMakeLists.txt +++ b/tests/auto/corelib/thread/qfuture/CMakeLists.txt @@ -1,14 +1,27 @@ -# Generated from qfuture.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qfuture Test: ##################################################################### -qt_add_test(tst_qfuture +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qfuture LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qfuture SOURCES tst_qfuture.cpp -# DEFINES -# -QT_NO_JAVA_STYLE_ITERATORS - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate + Qt::TestPrivate +) + +qt_internal_extend_target(tst_qfuture CONDITION MSVC + COMPILE_OPTIONS + /bigobj ) + +qt_internal_undefine_global_definition(tst_qfuture QT_NO_JAVA_STYLE_ITERATORS) diff --git a/tests/auto/corelib/thread/qfuture/qfuture.pro b/tests/auto/corelib/thread/qfuture/qfuture.pro deleted file mode 100644 index fe097edf20..0000000000 --- a/tests/auto/corelib/thread/qfuture/qfuture.pro +++ /dev/null @@ -1,5 +0,0 @@ -CONFIG += testcase -TARGET = tst_qfuture -QT = core core-private testlib -SOURCES = tst_qfuture.cpp -DEFINES -= QT_NO_JAVA_STYLE_ITERATORS diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp index e6f6827f3f..4cb29c514a 100644 --- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp +++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp @@ -1,36 +1,23 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ -#include <QCoreApplication> -#include <QDebug> +// 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 <QtTest/QtTest> +#include <QCoreApplication> +#include <QDebug> +#include <QSemaphore> +#include <QTestEventLoop> +#include <QTimer> +#include <QSignalSpy> +#include <QVarLengthArray> +#include <QSet> +#include <QList> +#include <private/qobject_p.h> + +#include <QTest> +#include <QtTest/private/qcomparisontesthelper_p.h> #include <qfuture.h> #include <qfuturewatcher.h> #include <qresultstore.h> @@ -40,18 +27,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 { @@ -65,12 +55,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 @@ -91,25 +144,41 @@ 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 { Q_OBJECT private slots: + void compareCompiles(); void resultStore(); void future(); + void futureToVoid(); void futureInterface(); void refcounting(); void cancel(); + void cancelAndFinish(); void statePropagation(); void multipleResults(); void indexedResults(); void progress(); + void setProgressRange(); + void progressWithRange(); void progressText(); void resultsAfterFinished(); void resultsAsList(); - void implicitConversions(); void iterators(); void iteratorsThread(); #if QT_DEPRECATED_SINCE(6, 0) @@ -136,7 +205,14 @@ 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(); +#endif void takeResult(); void runAndTake(); void resultsReadyAt_data(); @@ -146,6 +222,36 @@ private slots: void signalConnect(); void waitForFinished(); + void rejectResultOverwrite_data(); + void rejectResultOverwrite(); + void rejectPendingResultOverwrite_data() { rejectResultOverwrite_data(); } + 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; @@ -159,6 +265,22 @@ 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::compareCompiles() +{ + QTestPrivate::testEqualityOperatorsCompile<QFuture<int>::const_iterator>(); + QTestPrivate::testEqualityOperatorsCompile<QFuture<QString>::const_iterator>(); +} + void tst_QFuture::resultStore() { int int0 = 0; @@ -166,7 +288,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()); @@ -174,7 +298,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(); @@ -196,7 +322,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(); @@ -219,7 +347,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); @@ -250,7 +380,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); @@ -280,7 +412,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); @@ -292,7 +426,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); @@ -303,7 +439,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); @@ -311,7 +449,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); @@ -345,7 +485,9 @@ void tst_QFuture::resultStore() { // test canceled results - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResult(0, &int0); @@ -382,7 +524,9 @@ void tst_QFuture::resultStore() { // test addResult return value - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResult(0, &int0); @@ -428,7 +572,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); @@ -442,7 +588,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(2, &int0); QCOMPARE(store.count(), 0); @@ -454,7 +602,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResults(2, &vec1); QCOMPARE(store.count(), 0); @@ -466,7 +616,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResults(2, &vec1); QCOMPARE(store.count(), 0); @@ -474,7 +626,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); @@ -486,7 +640,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResults(3, &vec1); QCOMPARE(store.count(), 0); @@ -499,7 +655,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResults(3, &vec1); QCOMPARE(store.count(), 0); @@ -509,7 +667,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResults(3, &vec1); QCOMPARE(store.count(), 0); @@ -522,7 +682,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); @@ -537,7 +699,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResult(1, &int0); store.addResult(3, &int0); @@ -565,7 +729,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); @@ -601,6 +767,19 @@ void tst_QFuture::future() QCOMPARE(intFuture2.isFinished(), true); } +void tst_QFuture::futureToVoid() +{ + QPromise<int> p; + QFuture<int> future = p.future(); + + p.start(); + p.setProgressValue(42); + p.finish(); + + QFuture<void> voidFuture = QFuture<void>(future); + QCOMPARE(voidFuture.progressValue(), 42); +} + class IntResult : public QFutureInterface<int> { public: @@ -645,7 +824,7 @@ void tst_QFuture::futureInterface() { QFutureInterface<int> i; i.reportStarted(); - i.reportResult(10); + QVERIFY(i.reportResult(10)); future = i.future(); i.reportFinished(); } @@ -666,7 +845,7 @@ void tst_QFuture::futureInterface() QCOMPARE(intFuture.isStarted(), true); QCOMPARE(intFuture.isFinished(), false); - result.reportFinished(&value); + QVERIFY(result.reportFinished(&value)); QCOMPARE(intFuture.isStarted(), true); QCOMPARE(intFuture.isFinished(), true); @@ -693,13 +872,33 @@ void tst_QFuture::futureInterface() { QFutureInterface<int> i1; - i1.reportResult(1); + QVERIFY(i1.reportResult(1)); QFutureInterface<int> i2; - i2.reportResult(2); + QVERIFY(i2.reportResult(2)); swap(i1, i2); // ADL must resolve this QCOMPARE(i1.resultReference(0), 2); QCOMPARE(i2.resultReference(0), 1); } + + { + QFutureInterface<int> fi; + fi.reportStarted(); + QVERIFY(!fi.reportResults(QList<int> {})); + fi.reportFinished(); + + QVERIFY(fi.results().empty()); + } + + { + QFutureInterface<int> fi; + fi.reportStarted(); + QList<int> values = { 1, 2, 3 }; + QVERIFY(fi.reportResults(values)); + QVERIFY(!fi.reportResults(QList<int> {})); + fi.reportFinished(); + + QCOMPARE(fi.results(), values); + } } template <typename T> @@ -812,6 +1011,39 @@ void tst_QFuture::cancel() } } +void tst_QFuture::cancelAndFinish() +{ + { + QFutureInterface<void> fi; + + fi.reportStarted(); + fi.cancelAndFinish(); + + QVERIFY(fi.isStarted()); + QVERIFY(!fi.isRunning()); + QVERIFY(!fi.isSuspended()); + QVERIFY(!fi.isSuspending()); + QVERIFY(fi.isCanceled()); + QVERIFY(fi.isFinished()); + } + + // The same with suspended state + { + QFutureInterface<void> fi; + + fi.reportStarted(); + fi.setSuspended(true); + fi.cancelAndFinish(); + + QVERIFY(fi.isStarted()); + QVERIFY(!fi.isRunning()); + QVERIFY(!fi.isSuspended()); + QVERIFY(!fi.isSuspending()); + QVERIFY(fi.isCanceled()); + QVERIFY(fi.isFinished()); + } +} + void tst_QFuture::statePropagation() { QFuture<void> f1; @@ -859,15 +1091,15 @@ void tst_QFuture::multipleResults() int result; result = 1; - a.reportResult(&result); + QVERIFY(a.reportResult(&result)); QCOMPARE(f.resultAt(0), 1); result = 2; - a.reportResult(&result); + QVERIFY(a.reportResult(&result)); QCOMPARE(f.resultAt(1), 2); result = 3; - a.reportResult(&result); + QVERIFY(a.reportResult(&result)); result = 4; a.reportFinished(&result); @@ -878,13 +1110,13 @@ void tst_QFuture::multipleResults() QList<int> fasit = QList<int>() << 1 << 2 << 3 << 4; { QList<int> results; - foreach(int result, f) + for (int result : std::as_const(f)) results.append(result); QCOMPARE(results, fasit); } { QList<int> results; - foreach(int result, copy) + for (int result : std::as_const(copy)) results.append(result); QCOMPARE(results, fasit); } @@ -908,16 +1140,16 @@ void tst_QFuture::indexedResults() QChar result; result = 'B'; - Interface.reportResult(&result, 1); + QVERIFY(Interface.reportResult(&result, 1)); QCOMPARE(f.resultAt(1), result); result = 'A'; - Interface.reportResult(&result, 0); + QVERIFY(Interface.reportResult(&result, 0)); QCOMPARE(f.resultAt(0), result); result = 'C'; - Interface.reportResult(&result); // no index + QVERIFY(Interface.reportResult(&result)); // no index QCOMPARE(f.resultAt(2), result); Interface.reportFinished(); @@ -933,22 +1165,22 @@ void tst_QFuture::indexedResults() int result; result = 0; - Interface.reportResult(&result, 0); + QVERIFY(Interface.reportResult(&result, 0)); QVERIFY(f.isResultReadyAt(0)); QCOMPARE(f.resultAt(0), 0); result = 3; - Interface.reportResult(&result, 3); + QVERIFY(Interface.reportResult(&result, 3)); QVERIFY(f.isResultReadyAt(3)); QCOMPARE(f.resultAt(3), 3); result = 2; - Interface.reportResult(&result, 2); + QVERIFY(Interface.reportResult(&result, 2)); QVERIFY(f.isResultReadyAt(2)); QCOMPARE(f.resultAt(2), 2); result = 4; - Interface.reportResult(&result); // no index + QVERIFY(Interface.reportResult(&result)); // no index QVERIFY(f.isResultReadyAt(4)); QCOMPARE(f.resultAt(4), 4); @@ -979,6 +1211,55 @@ void tst_QFuture::progress() QCOMPARE (f.progressValue(), 50); } +void tst_QFuture::setProgressRange() +{ + QFutureInterface<int> i; + + QCOMPARE(i.progressMinimum(), 0); + QCOMPARE(i.progressMaximum(), 0); + + i.setProgressRange(10, 5); + + QCOMPARE(i.progressMinimum(), 10); + QCOMPARE(i.progressMaximum(), 10); + + i.setProgressRange(5, 10); + + QCOMPARE(i.progressMinimum(), 5); + QCOMPARE(i.progressMaximum(), 10); +} + +void tst_QFuture::progressWithRange() +{ + QFutureInterface<int> i; + QFuture<int> f; + + i.reportStarted(); + f = i.future(); + + QCOMPARE(i.progressValue(), 0); + + i.setProgressRange(5, 10); + + QCOMPARE(i.progressValue(), 5); + + i.setProgressValue(20); + + QCOMPARE(i.progressValue(), 5); + + i.setProgressValue(9); + + QCOMPARE(i.progressValue(), 9); + + i.setProgressRange(5, 7); + + QCOMPARE(i.progressValue(), 5); + + i.reportFinished(); + + QCOMPARE(f.progressValue(), 5); +} + void tst_QFuture::progressText() { QFutureInterface<void> i; @@ -1005,7 +1286,7 @@ void tst_QFuture::resultsAfterFinished() QCOMPARE(f.resultCount(), 0); result = 1; - a.reportResult(&result); + QVERIFY(a.reportResult(&result)); QCOMPARE(f.resultAt(0), 1); a.reportFinished(); @@ -1013,7 +1294,7 @@ void tst_QFuture::resultsAfterFinished() QCOMPARE(f.resultAt(0), 1); QCOMPARE(f.resultCount(), 1); result = 2; - a.reportResult(&result); + QVERIFY(!a.reportResult(&result)); QCOMPARE(f.resultCount(), 1); } // cancel it @@ -1026,7 +1307,7 @@ void tst_QFuture::resultsAfterFinished() QCOMPARE(f.resultCount(), 0); result = 1; - a.reportResult(&result); + QVERIFY(a.reportResult(&result)); QCOMPARE(f.resultAt(0), 1); QCOMPARE(f.resultCount(), 1); @@ -1036,7 +1317,7 @@ void tst_QFuture::resultsAfterFinished() QCOMPARE(f.resultCount(), 1); result = 2; - a.reportResult(&result); + QVERIFY(!a.reportResult(&result)); a.reportFinished(); } } @@ -1049,9 +1330,9 @@ void tst_QFuture::resultsAsList() int result; result = 1; - a.reportResult(&result); + QVERIFY(a.reportResult(&result)); result = 2; - a.reportResult(&result); + QVERIFY(a.reportResult(&result)); a.reportFinished(); @@ -1059,25 +1340,6 @@ void tst_QFuture::resultsAsList() QCOMPARE(results, QList<int>() << 1 << 2); } -/* - Test that QFuture<T> can be implicitly converted to T -*/ -void tst_QFuture::implicitConversions() -{ - QFutureInterface<QString> iface; - iface.reportStarted(); - - QFuture<QString> f(&iface); - - const QString input("FooBar 2000"); - iface.reportFinished(&input); - - const QString result = f; - QCOMPARE(result, input); - QCOMPARE(QString(f), input); - QCOMPARE(static_cast<QString>(f), input); -} - void tst_QFuture::iterators() { { @@ -1105,16 +1367,16 @@ void tst_QFuture::iterators() QFuture<int>::const_iterator i1 = f.begin(), i2 = i1 + 1; QFuture<int>::const_iterator c1 = i1, c2 = c1 + 1; - QCOMPARE(i1, i1); - QCOMPARE(i1, c1); - QCOMPARE(c1, i1); - QCOMPARE(c1, c1); - QCOMPARE(i2, i2); - QCOMPARE(i2, c2); - QCOMPARE(c2, i2); - QCOMPARE(c2, c2); - QCOMPARE(1 + i1, i1 + 1); - QCOMPARE(1 + c1, c1 + 1); + QT_TEST_EQUALITY_OPS(i1, i1, true); + QT_TEST_EQUALITY_OPS(i1, c1, true); + QT_TEST_EQUALITY_OPS(c1, i1, true); + QT_TEST_EQUALITY_OPS(c1, c1, true); + QT_TEST_EQUALITY_OPS(i2, i2, true); + QT_TEST_EQUALITY_OPS(i2, c2, true); + QT_TEST_EQUALITY_OPS(c2, i2, true); + QT_TEST_EQUALITY_OPS(c2, c2, true); + QT_TEST_EQUALITY_OPS(1 + i1, i1 + 1, true); + QT_TEST_EQUALITY_OPS(1 + c1, c1 + 1, true); QVERIFY(i1 != i2); QVERIFY(i1 != c2); @@ -1126,13 +1388,13 @@ void tst_QFuture::iterators() QVERIFY(c2 != c1); int x1 = *i1; - Q_UNUSED(x1); + Q_UNUSED(x1) int x2 = *i2; - Q_UNUSED(x2); + Q_UNUSED(x2) int y1 = *c1; - Q_UNUSED(y1); + Q_UNUSED(y1) int y2 = *c2; - Q_UNUSED(y2); + Q_UNUSED(y2) } { @@ -1184,10 +1446,10 @@ void tst_QFuture::iterators() QCOMPARE(x1, y1); QCOMPARE(x2, y2); - int i1Size = i1->size(); - int i2Size = i2->size(); - int c1Size = c1->size(); - int c2Size = c2->size(); + auto i1Size = i1->size(); + auto i2Size = i2->size(); + auto c1Size = c1->size(); + auto c2Size = c2->size(); QCOMPARE(i1Size, c1Size); QCOMPARE(i2Size, c2Size); @@ -1492,12 +1754,10 @@ void tst_QFuture::voidConversions() QFuture<int> intFuture(&iface); int value = 10; - iface.reportFinished(&value); + QVERIFY(iface.reportFinished(&value)); QFuture<void> voidFuture(intFuture); voidFuture = intFuture; - - QVERIFY(voidFuture == intFuture); } { @@ -1507,7 +1767,7 @@ void tst_QFuture::voidConversions() iface.reportStarted(); QFuture<QList<int> > listFuture(&iface); - iface.reportResult(QList<int>() << 1 << 2 << 3); + QVERIFY(iface.reportResult(QList<int>() << 1 << 2 << 3)); voidFuture = listFuture; } QCOMPARE(voidFuture.resultCount(), 0); @@ -1634,7 +1894,7 @@ void tst_QFuture::exceptions() bool caught = false; try { foreach (int e, f.results()) { - Q_UNUSED(e); + Q_UNUSED(e) QFAIL("did not get exception"); } } catch (QException &) { @@ -1701,7 +1961,7 @@ void tst_QFuture::nestedExceptions() { try { MyClass m; - Q_UNUSED(m); + Q_UNUSED(m) throw 0; } catch (int) {} @@ -1729,7 +1989,7 @@ void tst_QFuture::nonGlobalThreadPool() void run() override { const int ms = 100 + (QRandomGenerator::global()->bounded(100) - 100/2); - QThread::msleep(ms); + QThread::sleep(std::chrono::milliseconds{ms}); reportResult(Answer); reportFinished(); } @@ -2014,6 +2274,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> @@ -2767,6 +3047,396 @@ 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() +{ + QFETCH(bool, inOtherThread); + + auto tstThread = QThread::currentThread(); + QThread *thread = inOtherThread ? new QThread + : tstThread; + auto context = new QObject(); + + const auto cleanupGuard = qScopeGuard([&] { + context->deleteLater(); + if (thread != tstThread) { + thread->quit(); + thread->wait(); + delete thread; + } + }); + + if (inOtherThread) { + thread->start(); + context->moveToThread(thread); + } + + // .then() + { + QPromise<int> promise; + auto future = promise.future() + .then([&](int val) { + if (QThread::currentThread() != tstThread) + return 0; + return val + 1; + }) + .then(context, + [&](int val) { + if (QThread::currentThread() != thread) + return 0; + return val + 1; + }) + .then([&](int val) { + if (QThread::currentThread() != thread) + return 0; + return val + 1; + }); + promise.start(); + promise.addResult(0); + promise.finish(); + QCOMPARE(future.result(), 3); + } + + // .onCanceled + { + QPromise<int> promise; + auto future = promise.future() + .onCanceled(context, + [&] { + if (QThread::currentThread() != thread) + return 0; + return 1; + }) + .then([&](int val) { + if (QThread::currentThread() != thread) + return 0; + return val + 1; + }); + promise.start(); + promise.future().cancel(); + promise.finish(); + 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() + { + QPromise<void> promise; + auto future = promise.future() + .then([&] { + if (QThread::currentThread() != tstThread) + return 0; + throw std::runtime_error("error"); + }) + .onFailed(context, + [&] { + if (QThread::currentThread() != thread) + return 0; + return 1; + }) + .then([&](int val) { + if (QThread::currentThread() != thread) + return 0; + return val + 1; + }); + promise.start(); + promise.finish(); + QCOMPARE(future.result(), 2); + } +#endif // QT_NO_EXCEPTIONS +} + +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; + + 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) { QVERIFY(p.get() != nullptr); @@ -2780,7 +3450,7 @@ void tst_QFuture::testSingleResult(const std::vector<int> &v) template<class T> void tst_QFuture::testSingleResult(const T &unknown) { - Q_UNUSED(unknown); + Q_UNUSED(unknown) } @@ -2789,17 +3459,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); } @@ -2829,6 +3488,7 @@ void tst_QFuture::testTakeResults(QFuture<T> future, size_type resultCount) testFutureTaken(copy); } +#if 0 void tst_QFuture::takeResults() { // Test takeResults() for movable types (whether or not copyable). @@ -2844,8 +3504,8 @@ void tst_QFuture::takeResults() const int expectedCount = 10; for (int i = 0; i < expectedCount; ++i) { - moveIface.reportAndMoveResult(UniquePtr{new int(0b101010)}, i); - copyIface.reportAndMoveResult(std::vector<int>{1,2,3,4,5}, i); + QVERIFY(moveIface.reportAndMoveResult(UniquePtr{new int(0b101010)}, i)); + QVERIFY(copyIface.reportAndMoveResult(std::vector<int>{1,2,3,4,5}, i)); } moveIface.reportFinished(); @@ -2857,12 +3517,13 @@ void tst_QFuture::takeResults() testTakeResults(copyIface.future(), size_type(expectedCount)); } +#endif void tst_QFuture::takeResult() { QFutureInterface<UniquePtr> iface; iface.reportStarted(); - iface.reportAndMoveResult(UniquePtr{new int(0b101010)}, 0); + QVERIFY(iface.reportAndMoveResult(UniquePtr{new int(0b101010)}, 0)); iface.reportFinished(); auto future = iface.future(); @@ -2885,7 +3546,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)); }; @@ -2898,12 +3559,15 @@ 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"); gotcha = watcha.future(); +#if 0 + // TODO: enable when QFuture::takeResults() is enabled testTakeResults(gotcha, size_type(1)); +#endif } void tst_QFuture::resultsReadyAt_data() @@ -2943,9 +3607,9 @@ void tst_QFuture::resultsReadyAt() { int dummyResult = 0b101010; if (testMove) - iface.reportAndMoveResult(std::move(dummyResult), index); + QVERIFY(iface.reportAndMoveResult(std::move(dummyResult), index)); else - iface.reportResult(&dummyResult, index); + QVERIFY(iface.reportResult(&dummyResult, index)); }; const QSignalSpy readyCounter(&watcher, &QFutureWatcher<int>::resultsReadyAt); @@ -2966,14 +3630,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); } @@ -3014,11 +3678,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); } @@ -3047,16 +3721,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(std::get<0>(result), 42); - QCOMPARE(std::get<1>(result), 42.5); - QCOMPARE(std::get<2>(result), "42"); + 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 @@ -3068,13 +3877,60 @@ 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()); QVERIFY(!future.isValid()); } + + // Signal emitted, causing Sender to be destroyed + { + SenderObject *sender = new SenderObject(); + + auto future = QtFuture::connect(sender, &SenderObject::intArgSignal); + future.then([sender](int) { + // Scenario: Sender no longer needed, so it's deleted + delete sender; + }); + + QSignalSpy spy(sender, &SenderObject::destroyed); + emit sender->intArgSignal(5); + spy.wait(); + + QVERIFY(future.isFinished()); + 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() @@ -3101,5 +3957,1409 @@ void tst_QFuture::waitForFinished() QVERIFY(waitingThread->isFinished()); } +void tst_QFuture::rejectResultOverwrite_data() +{ + QTest::addColumn<bool>("filterMode"); + QTest::addColumn<QList<int>>("initResults"); + + QTest::addRow("filter-mode-on-1-result") << true << QList<int>({ 456 }); + QTest::addRow("filter-mode-on-N-results") << true << QList<int>({ 456, 789 }); + QTest::addRow("filter-mode-off-1-result") << false << QList<int>({ 456 }); + QTest::addRow("filter-mode-off-N-results") << false << QList<int>({ 456, 789 }); +} + +void tst_QFuture::rejectResultOverwrite() +{ + QFETCH(bool, filterMode); + QFETCH(QList<int>, initResults); + + QFutureInterface<int> iface; + iface.setFilterMode(filterMode); + auto f = iface.future(); + QFutureWatcher<int> watcher; + watcher.setFuture(f); + + QTestEventLoop eventProcessor; + // control the loop by suspend + connect(&watcher, &QFutureWatcher<int>::suspending, &eventProcessor, &QTestEventLoop::exitLoop); + // internal machinery always emits resultsReadyAt + QSignalSpy resultCounter(&watcher, &QFutureWatcher<int>::resultsReadyAt); + + // init + if (initResults.size() == 1) + QVERIFY(iface.reportResult(initResults[0])); + else + QVERIFY(iface.reportResults(initResults)); + QCOMPARE(f.resultCount(), initResults.size()); + QCOMPARE(f.resultAt(0), initResults[0]); + QCOMPARE(f.results(), initResults); + + QTimer::singleShot(50, [&f]() { + f.suspend(); // should exit the loop + }); + // Run event loop, QCoreApplication::postEvent is in use + // in QFutureInterface: + eventProcessor.enterLoop(DefaultWaitTime); + QVERIFY(!eventProcessor.timeout()); + QCOMPARE(resultCounter.size(), 1); + f.resume(); + + // overwrite with lvalue + { + int result = -1; + const auto originalCount = f.resultCount(); + QVERIFY(!iface.reportResult(result, 0)); + QCOMPARE(f.resultCount(), originalCount); + QCOMPARE(f.resultAt(0), initResults[0]); + } + // overwrite with rvalue + { + const auto originalCount = f.resultCount(); + QVERIFY(!iface.reportResult(-1, 0)); + QCOMPARE(f.resultCount(), originalCount); + QCOMPARE(f.resultAt(0), initResults[0]); + } + // overwrite with array + { + const auto originalCount = f.resultCount(); + QVERIFY(!iface.reportResults(QList<int> { -1, -2, -3 }, 0)); + QCOMPARE(f.resultCount(), originalCount); + QCOMPARE(f.resultAt(0), initResults[0]); + } + + // special case: add result by different index, overlapping with the vector + if (initResults.size() > 1) { + const auto originalCount = f.resultCount(); + QVERIFY(!iface.reportResult(-1, 1)); + QCOMPARE(f.resultCount(), originalCount); + QCOMPARE(f.resultAt(1), initResults[1]); + } + + QTimer::singleShot(50, [&f]() { + f.suspend(); // should exit the loop + }); + eventProcessor.enterLoop(DefaultWaitTime); + QVERIFY(!eventProcessor.timeout()); + QCOMPARE(resultCounter.size(), 1); + f.resume(); + QCOMPARE(f.results(), initResults); +} + +void tst_QFuture::rejectPendingResultOverwrite() +{ + QFETCH(bool, filterMode); + QFETCH(QList<int>, initResults); + + QFutureInterface<int> iface; + iface.setFilterMode(filterMode); + auto f = iface.future(); + QFutureWatcher<int> watcher; + watcher.setFuture(f); + + QTestEventLoop eventProcessor; + // control the loop by suspend + connect(&watcher, &QFutureWatcher<int>::suspending, &eventProcessor, &QTestEventLoop::exitLoop); + // internal machinery always emits resultsReadyAt + QSignalSpy resultCounter(&watcher, &QFutureWatcher<int>::resultsReadyAt); + + // init + if (initResults.size() == 1) + QVERIFY(iface.reportResult(initResults[0], 1)); + else + QVERIFY(iface.reportResults(initResults, 1)); + QCOMPARE(f.resultCount(), 0); // not visible yet + if (!filterMode) { + QCOMPARE(f.resultAt(1), initResults[0]); + QCOMPARE(f.results(), initResults); + + QTimer::singleShot(50, [&f]() { + f.suspend(); // should exit the loop + }); + // Run event loop, QCoreApplication::postEvent is in use + // in QFutureInterface: + eventProcessor.enterLoop(DefaultWaitTime); + QVERIFY(!eventProcessor.timeout()); + QCOMPARE(resultCounter.size(), 1); + f.resume(); + } + + // overwrite with lvalue + { + int result = -1; + const auto originalCount = f.resultCount(); + QVERIFY(!iface.reportResult(result, 1)); + QCOMPARE(f.resultCount(), originalCount); + if (!filterMode) + QCOMPARE(f.resultAt(1), initResults[0]); + } + // overwrite with rvalue + { + const auto originalCount = f.resultCount(); + QVERIFY(!iface.reportResult(-1, 1)); + QCOMPARE(f.resultCount(), originalCount); + if (!filterMode) + QCOMPARE(f.resultAt(1), initResults[0]); + } + // overwrite with array + { + const auto originalCount = f.resultCount(); + QVERIFY(!iface.reportResults(QList<int> { -1, -2 }, 1)); + QCOMPARE(f.resultCount(), originalCount); + if (!filterMode) + QCOMPARE(f.resultAt(1), initResults[0]); + } + // special case: add result by different index, overlapping with the vector + if (initResults.size() > 1) { + const auto originalCount = f.resultCount(); + QVERIFY(!iface.reportResult(-1, 2)); + QCOMPARE(f.resultCount(), originalCount); + if (!filterMode) + QCOMPARE(f.resultAt(2), initResults[1]); + } + + if (!filterMode) { + QTimer::singleShot(50, [&f]() { + f.suspend(); // should exit the loop + }); + eventProcessor.enterLoop(DefaultWaitTime); + QVERIFY(!eventProcessor.timeout()); + QCOMPARE(resultCounter.size(), 1); + f.resume(); + } + + QVERIFY(iface.reportResult(123, 0)); // make results at 0 and 1 accessible + QCOMPARE(f.resultCount(), initResults.size() + 1); + QCOMPARE(f.resultAt(1), initResults[0]); + initResults.prepend(123); + QCOMPARE(f.results(), initResults); +} + +void tst_QFuture::createReadyFutures() +{ +#if QT_DEPRECATED_SINCE(6, 10) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + // using const T & + { + const int val = 42; + QFuture<int> f = QtFuture::makeReadyFuture(val); + QCOMPARE(f.result(), val); + } + + // using T + { + int val = 42; + QFuture<int> f = QtFuture::makeReadyFuture(val); + QCOMPARE(f.result(), val); + } + + // using T && + { + auto f = QtFuture::makeReadyFuture(std::make_unique<int>(42)); + QCOMPARE(*f.takeResult(), 42); + } + + // using void + { + auto f = QtFuture::makeReadyFuture(); + QVERIFY(f.isStarted()); + QVERIFY(!f.isRunning()); + QVERIFY(f.isFinished()); + } + + // using const QList<T> & + { + const QList<int> values { 1, 2, 3 }; + auto f = QtFuture::makeReadyFuture(values); + 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 + { + QException e; + auto f = QtFuture::makeExceptionalFuture<int>(e); + bool caught = false; + try { + f.result(); + } catch (QException &) { + caught = true; + } + QVERIFY(caught); + } + + // using std::exception_ptr and QFuture<void> + { + auto exception = std::make_exception_ptr(TestException()); + auto f = QtFuture::makeExceptionalFuture(exception); + bool caught = false; + try { + f.waitForFinished(); + } catch (TestException &) { + caught = true; + } + 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::makeReadyValueFuture(val); + + auto interface = QFutureInterfaceBase::get(f); + QCOMPARE(interface.resultCount(), 1); +} + +void tst_QFuture::convertQMetaType() +{ + const auto intType = QMetaType::fromType<QFuture<int>>(); + const auto voidType = QMetaType::fromType<QFuture<void>>(); + + QVERIFY(QMetaType::canConvert(intType, voidType)); + + const int val = 42; + QFuture<int> f = QtFuture::makeReadyValueFuture(val); + auto variant = QVariant::fromValue(f); + QVERIFY(variant.convert(voidType)); + + const auto voidFuture = variant.value<QFuture<void>>(); + QVERIFY(voidFuture.isValid()); + 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" |