From 102f7d31c469a546f52c930a047bd294fb198186 Mon Sep 17 00:00:00 2001 From: Sona Kurazyan Date: Thu, 4 Nov 2021 17:01:54 +0100 Subject: Add support for combining multiple QFutures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ChangeLog][QtCore] Added QtFuture::whenAll() and QtFuture::whenAny() functions, returning a QFuture that becomes ready when all or any of the supplied futures complete. Task-number: QTBUG-86714 Change-Id: I2bb7dbb4cdc4f79a7a4fd494142df6a0f93a2b39 Reviewed-by: Edward Welbourne Reviewed-by: MÃ¥rten Nordheim --- tests/auto/corelib/thread/qfuture/tst_qfuture.cpp | 514 ++++++++++++++++++++++ 1 file changed, 514 insertions(+) (limited to 'tests/auto/corelib/thread') diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp index 9c8bd232d5..385fbe7fca 100644 --- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp +++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp @@ -168,6 +168,19 @@ private slots: 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(); + private: using size_type = std::vector::size_type; @@ -3730,5 +3743,506 @@ void tst_QFuture::convertQMetaType() QVERIFY(voidFuture.isFinished()); } +template +void testWhenAllIterators() +{ + QPromise p0; + QPromise p1; + QPromise p2; + QList> futures = { p0.future(), p1.future(), p2.future() }; + + bool finished = false; + QFuture whenAll; + if constexpr (std::is_same_v>, OutputContainer>) + whenAll = QtFuture::whenAll(futures.begin(), futures.end()); + else + whenAll = QtFuture::whenAll(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 whenAllEmpty; + if constexpr (std::is_same_v>, OutputContainer>) + whenAllEmpty = QtFuture::whenAll(futures.end(), futures.end()); + else + whenAllEmpty = QtFuture::whenAll(futures.end(), futures.end()); + QVERIFY(whenAllEmpty.isStarted()); + QVERIFY(whenAllEmpty.isFinished()); + QVERIFY(whenAllEmpty.result().empty()); +} + +void tst_QFuture::whenAllIterators() +{ + // Try with different output containers + testWhenAllIterators>>(); + if (QTest::currentTestFailed()) + QSKIP("testWhenAllIterators() with QList failed!"); + + testWhenAllIterators>>(); + if (QTest::currentTestFailed()) + QSKIP("testWhenAllIterators() with std::vector failed!"); + + testWhenAllIterators>>(); + if (QTest::currentTestFailed()) + QSKIP("testWhenAllIterators() with QVarLengthArray failed!"); +} + +void tst_QFuture::whenAllIteratorsWithCanceled() +{ + QPromise p0; + QPromise p1; + QList> futures = { p0.future(), p1.future() }; + bool finished = false; + auto whenAll = QtFuture::whenAll(futures.begin(), futures.end()) + .then([&](const QList> &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 p0; + QPromise p1; + QList> futures = { p0.future(), p1.future() }; + bool finished = false; + auto whenAll = QtFuture::whenAll(futures.begin(), futures.end()) + .then([&](QList> 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 +struct overloaded : public Ts... +{ + using Ts::operator()...; +}; + +// explicit deduction guide +template +overloaded(Ts...)->overloaded; + +template +void testWhenAllDifferentTypes() +{ + QPromise pInt1; + QPromise pInt2; + QPromise pVoid; + + using Futures = std::variant, QFuture, QFuture>; + + QFuture whenAll; + if constexpr (std::is_same_v, OutputContainer>) { + whenAll = QtFuture::whenAll(pInt1.future(), pInt2.future(), pVoid.future()); + } else { + whenAll = + QtFuture::whenAll(pInt1.future(), pInt2.future(), pVoid.future()); + } + + int sumOfInts = 0; + whenAll.then([&](const OutputContainer &results) { + for (auto future : results) { + std::visit(overloaded { + [&](const QFuture &f) { + QVERIFY(f.isFinished()); + sumOfInts += f.result(); + }, + [](const QFuture &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, QFuture>; + testWhenAllDifferentTypes>(); + if (QTest::currentTestFailed()) + QSKIP("testWhenAllDifferentTypes() with QList failed!"); + + testWhenAllDifferentTypes>(); + if (QTest::currentTestFailed()) + QSKIP("testWhenAllDifferentTypes() with std::vector failed!"); + + testWhenAllDifferentTypes>(); + if (QTest::currentTestFailed()) + QSKIP("testWhenAllDifferentTypes() with QVarLengthArray failed!"); +} + +void tst_QFuture::whenAllDifferentTypesWithCanceled() +{ + QPromise pInt; + QPromise pString; + + const QString someValue = u"some value"_qs; + + bool finished = false; + using Futures = std::variant, QFuture>; + auto whenAll = QtFuture::whenAll(pInt.future(), pString.future()) + .then([&](const QList &results) { + finished = true; + for (auto future : results) { + std::visit(overloaded { + [](const QFuture &f) { + QVERIFY(f.isFinished()); + QVERIFY(f.isCanceled()); + }, + [&](const QFuture &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 pInt; + QPromise pString; + + const QString someValue = u"some value"_qs; + + bool finished = false; + using Futures = std::variant, QFuture>; + auto whenAll = QtFuture::whenAll(pInt.future(), pString.future()) + .then([&](const QList &results) { + finished = true; + for (auto future : results) { + std::visit(overloaded { + [](QFuture 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 &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 p0; + QPromise p1; + QPromise p2; + QList> futures = { p0.future(), p1.future(), p2.future() }; + + auto whenAny = QtFuture::whenAny(futures.begin(), futures.end()); + int count = 0; + whenAny.then([&](const QtFuture::WhenAnyResult &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 p0; + QPromise p1; + QList> futures = { p0.future(), p1.future() }; + int count = 0; + auto whenAny = QtFuture::whenAny(futures.begin(), futures.end()) + .then([&](const QtFuture::WhenAnyResult &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 p0; + QPromise p1; + QList> futures = { p0.future(), p1.future() }; + int count = 0; + auto whenAny = QtFuture::whenAny(futures.begin(), futures.end()) + .then([&](QtFuture::WhenAnyResult 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 pInt1; + QPromise pInt2; + QPromise pVoid; + + auto whenAny = QtFuture::whenAny(pInt1.future(), pInt2.future(), pVoid.future()); + int count = 0; + whenAny.then([&](const std::variant, QFuture, QFuture> &result) { + QCOMPARE(result.index(), 1u); + std::visit(overloaded { [&](const QFuture &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 pInt; + QPromise pVoid; + + int count = 0; + auto whenAny = QtFuture::whenAny(pInt.future(), pVoid.future()) + .then([&](const std::variant, QFuture> &result) { + QCOMPARE(result.index(), 0u); + std::visit(overloaded { [&](const QFuture &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 pInt; + QPromise pVoid; + + int count = 0; + auto whenAny = QtFuture::whenAny(pInt.future(), pVoid.future()) + .then([&](const std::variant, QFuture> &result) { + QCOMPARE(result.index(), 0u); + std::visit(overloaded { [&](QFuture 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 +} + QTEST_MAIN(tst_QFuture) #include "tst_qfuture.moc" -- cgit v1.2.3