diff options
Diffstat (limited to 'tests/auto/corelib/thread/qfuture/tst_qfuture.cpp')
-rw-r--r-- | tests/auto/corelib/thread/qfuture/tst_qfuture.cpp | 597 |
1 files changed, 529 insertions, 68 deletions
diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp index 638dc380f7..7a8cd707d7 100644 --- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp +++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp @@ -1,5 +1,8 @@ // Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// 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> @@ -11,8 +14,10 @@ #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> @@ -22,14 +27,20 @@ #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 +using namespace std::chrono_literals; +static constexpr auto DefaultWaitTime = 2s; + using namespace Qt::StringLiterals; class SenderObject : public QObject @@ -133,12 +144,25 @@ 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(); @@ -182,6 +206,7 @@ private slots: #endif void onCanceled(); void cancelContinuations(); + void continuationsWithContext_data(); void continuationsWithContext(); void continuationsWithMoveOnlyLambda(); #if 0 @@ -203,6 +228,7 @@ private slots: void rejectPendingResultOverwrite(); void createReadyFutures(); + void continuationsAfterReadyFutures(); void getFutureInterface(); void convertQMetaType(); @@ -220,7 +246,9 @@ private slots: void whenAnyDifferentTypesWithCanceled(); void whenAnyDifferentTypesWithFailed(); + void continuationOverride(); void continuationsDontLeak(); + void cancelAfterFinishWithContinuations(); void unwrap(); @@ -247,6 +275,12 @@ 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; @@ -1076,13 +1110,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); } @@ -1333,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); @@ -1955,7 +1989,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(); } @@ -2240,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> @@ -3024,7 +3078,7 @@ void tst_QFuture::cancelContinuations() // The chain is cancelled before the execution of continuations { - auto f = QtFuture::makeReadyFuture(42); + auto f = QtFuture::makeReadyValueFuture(42); f.cancel(); int checkpoint = 0; @@ -3116,17 +3170,97 @@ void tst_QFuture::cancelContinuations() QCOMPARE(checkpoint, 3); } #endif // QT_NO_EXCEPTIONS + + // Check notifications from QFutureWatcher + { + QPromise<void> p; + auto f = p.future(); + + auto f1 = f.then([] {}); + auto f2 = f1.then([] {}); + + QFutureWatcher<void> watcher1, watcher2; + int state = 0; + QObject::connect(&watcher1, &QFutureWatcher<void>::started, [&] { + QCOMPARE(state, 0); + ++state; + }); + QObject::connect(&watcher1, &QFutureWatcher<void>::canceled, [&] { + QCOMPARE(state, 1); + ++state; + }); + QObject::connect(&watcher1, &QFutureWatcher<void>::finished, [&] { + QCOMPARE(state, 2); + ++state; + }); + QObject::connect(&watcher2, &QFutureWatcher<void>::started, [&] { + QCOMPARE(state, 3); + ++state; + }); + QObject::connect(&watcher2, &QFutureWatcher<void>::canceled, [&] { + QCOMPARE(state, 4); + ++state; + }); + QObject::connect(&watcher2, &QFutureWatcher<int>::finished, [&] { + QCOMPARE(state, 5); + ++state; + }); + + watcher1.setFuture(f1); + watcher2.setFuture(f2); + + p.start(); + f.cancel(); + p.finish(); + + qApp->processEvents(); + + QCOMPARE(state, 6); + QVERIFY(watcher1.isFinished()); + QVERIFY(watcher1.isCanceled()); + QVERIFY(watcher2.isFinished()); + QVERIFY(watcher2.isCanceled()); + } + + // Cancel continuations with context (QTBUG-108790) + { + // This test should pass with ASan + auto future = QtConcurrent::run([] {}); + future.then(this, [] {}); + future.waitForFinished(); + future.cancel(); + } +} + +void tst_QFuture::continuationsWithContext_data() +{ + QTest::addColumn<bool>("inOtherThread"); + QTest::addRow("in-other-thread") << true; + QTest::addRow("in-main-thread-qtbug119406") << false; } void tst_QFuture::continuationsWithContext() { - QThread thread; - thread.start(); + QFETCH(bool, inOtherThread); + auto tstThread = QThread::currentThread(); + QThread *thread = inOtherThread ? new QThread + : tstThread; auto context = new QObject(); - context->moveToThread(&thread); - auto tstThread = QThread::currentThread(); + const auto cleanupGuard = qScopeGuard([&] { + context->deleteLater(); + if (thread != tstThread) { + thread->quit(); + thread->wait(); + delete thread; + } + }); + + if (inOtherThread) { + thread->start(); + context->moveToThread(thread); + } // .then() { @@ -3139,12 +3273,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; }); @@ -3160,12 +3294,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; }); @@ -3175,6 +3309,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() { @@ -3187,12 +3355,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; }); @@ -3201,11 +3369,6 @@ void tst_QFuture::continuationsWithContext() QCOMPARE(future.result(), 2); } #endif // QT_NO_EXCEPTIONS - - context->deleteLater(); - - thread.quit(); - thread.wait(); } void tst_QFuture::continuationsWithMoveOnlyLambda() @@ -3213,7 +3376,8 @@ void tst_QFuture::continuationsWithMoveOnlyLambda() // .then() { std::unique_ptr<int> uniquePtr(new int(42)); - auto future = QtFuture::makeReadyFuture().then([p = std::move(uniquePtr)] { return *p; }); + auto future = QtFuture::makeReadyVoidFuture() + .then([p = std::move(uniquePtr)] { return *p; }); QCOMPARE(future.result(), 42); } // .then() with thread pool @@ -3221,8 +3385,8 @@ void tst_QFuture::continuationsWithMoveOnlyLambda() QThreadPool pool; std::unique_ptr<int> uniquePtr(new int(42)); - auto future = - QtFuture::makeReadyFuture().then(&pool, [p = std::move(uniquePtr)] { return *p; }); + auto future = QtFuture::makeReadyVoidFuture() + .then(&pool, [p = std::move(uniquePtr)] { return *p; }); QCOMPARE(future.result(), 42); } // .then() with context @@ -3230,8 +3394,8 @@ void tst_QFuture::continuationsWithMoveOnlyLambda() QObject object; std::unique_ptr<int> uniquePtr(new int(42)); - auto future = QtFuture::makeReadyFuture().then(&object, - [p = std::move(uniquePtr)] { return *p; }); + auto future = QtFuture::makeReadyVoidFuture() + .then(&object, [p = std::move(uniquePtr)] { return *p; }); QCOMPARE(future.result(), 42); } @@ -3295,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); } @@ -3393,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)); }; @@ -3406,7 +3559,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"); @@ -3477,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); } @@ -3724,8 +3877,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()); @@ -3764,7 +3915,7 @@ void tst_QFuture::signalConnect() { SenderObject sender; -#if defined(Q_CC_MSVC) && !defined(Q_CC_CLANG) +#if defined(Q_CC_MSVC_ONLY) && (Q_CC_MSVC < 1940 || !defined(_DEBUG)) #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"); @@ -3784,9 +3935,6 @@ void tst_QFuture::signalConnect() void tst_QFuture::waitForFinished() { -#if !QT_CONFIG(cxx11_future) - QSKIP("This test requires QThread::create"); -#else QFutureInterface<void> fi; auto future = fi.future(); @@ -3807,7 +3955,6 @@ void tst_QFuture::waitForFinished() QVERIFY(waitingThread->wait()); QVERIFY(waitingThread->isFinished()); -#endif } void tst_QFuture::rejectResultOverwrite_data() @@ -3852,9 +3999,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 @@ -3891,9 +4038,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); } @@ -3930,9 +4077,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(); } @@ -3974,9 +4121,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(); } @@ -3989,6 +4136,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; @@ -4024,6 +4174,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 @@ -4052,12 +4226,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); @@ -4071,7 +4438,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)); @@ -4200,7 +4567,7 @@ void tst_QFuture::whenAllIteratorsWithFailed() p1.finish(); QVERIFY(finished); #else - QSKIP("Exceptions are disabled, skipping the test") + QSKIP("Exceptions are disabled, skipping the test"); #endif } @@ -4265,6 +4632,9 @@ void testWhenAllDifferentTypes() void tst_QFuture::whenAllDifferentTypes() { +#ifdef Q_OS_VXWORKS + QSKIP("std::variant implementation on VxWorks 24.03 is broken and doesn't work with duplicated types"); +#endif using Futures = std::variant<QFuture<int>, QFuture<int>, QFuture<void>>; testWhenAllDifferentTypes<QList<Futures>>(); if (QTest::currentTestFailed()) @@ -4474,6 +4844,9 @@ void tst_QFuture::whenAnyIteratorsWithFailed() void tst_QFuture::whenAnyDifferentTypes() { +#ifdef Q_OS_VXWORKS + QSKIP("std::variant implementation on VxWorks 24.03 is broken and doesn't work with duplicated types"); +#endif QPromise<int> pInt1; QPromise<int> pInt2; QPromise<void> pVoid; @@ -4581,6 +4954,35 @@ void tst_QFuture::whenAnyDifferentTypesWithFailed() #endif } +void tst_QFuture::continuationOverride() +{ + QPromise<int> p; + bool firstExecuted = false; + bool secondExecuted = false; + + QTest::ignoreMessage(QtWarningMsg, + "Adding a continuation to a future which already has a continuation. " + "The existing continuation is overwritten."); + + QFuture<int> f1 = p.future(); + f1.then([&firstExecuted](int) { + firstExecuted = true; + }); + + QFuture<int> f2 = p.future(); + f2.then([&secondExecuted](int) { + secondExecuted = true; + }); + + p.start(); + p.addResult(42); + p.finish(); + + QVERIFY(p.future().isFinished()); + QVERIFY(!firstExecuted); + QVERIFY(secondExecuted); +} + struct InstanceCounter { InstanceCounter() { ++count; } @@ -4639,6 +5041,65 @@ void tst_QFuture::continuationsDontLeak() 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() |