summaryrefslogtreecommitdiffstats
path: root/tests/auto/corelib
diff options
context:
space:
mode:
authorSona Kurazyan <sona.kurazyan@qt.io>2021-11-04 17:01:54 +0100
committerSona Kurazyan <sona.kurazyan@qt.io>2021-11-20 10:28:29 +0100
commit102f7d31c469a546f52c930a047bd294fb198186 (patch)
tree45fe0355a7195e385b6b2457f79861bb7f3597bb /tests/auto/corelib
parent3b49aa72fe6ec0dd0aa0c1c41fb81e874dc789fa (diff)
Add support for combining multiple QFutures
[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 <edward.welbourne@qt.io> Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
Diffstat (limited to 'tests/auto/corelib')
-rw-r--r--tests/auto/corelib/thread/qfuture/tst_qfuture.cpp514
1 files changed, 514 insertions, 0 deletions
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<int>::size_type;
@@ -3730,5 +3743,506 @@ void tst_QFuture::convertQMetaType()
QVERIFY(voidFuture.isFinished());
}
+template<class OutputContainer>
+void testWhenAllIterators()
+{
+ QPromise<int> p0;
+ QPromise<int> p1;
+ QPromise<int> p2;
+ QList<QFuture<int>> futures = { p0.future(), p1.future(), p2.future() };
+
+ bool finished = false;
+ QFuture<OutputContainer> whenAll;
+ if constexpr (std::is_same_v<QList<QFuture<int>>, OutputContainer>)
+ whenAll = QtFuture::whenAll(futures.begin(), futures.end());
+ else
+ whenAll = QtFuture::whenAll<OutputContainer>(futures.begin(), futures.end());
+ whenAll.then([&](const OutputContainer &output) {
+ QCOMPARE(output.size(), 3u);
+ QCOMPARE(output[0].result(), 0);
+ QCOMPARE(output[1].result(), 1);
+ QCOMPARE(output[2].result(), 2);
+ finished = true;
+ });
+ QVERIFY(whenAll.isRunning());
+
+ p0.start();
+ p0.addResult(0);
+ p0.finish();
+ QVERIFY(whenAll.isRunning());
+
+ p2.start();
+ p2.addResult(2);
+ p2.finish();
+ QVERIFY(whenAll.isRunning());
+
+ p1.start();
+ p1.addResult(1);
+ p1.finish();
+ QVERIFY(!whenAll.isRunning());
+ QVERIFY(finished);
+
+ // Try with empty sequence
+ QFuture<OutputContainer> whenAllEmpty;
+ if constexpr (std::is_same_v<QList<QFuture<int>>, OutputContainer>)
+ whenAllEmpty = QtFuture::whenAll(futures.end(), futures.end());
+ else
+ whenAllEmpty = QtFuture::whenAll<OutputContainer>(futures.end(), futures.end());
+ QVERIFY(whenAllEmpty.isStarted());
+ QVERIFY(whenAllEmpty.isFinished());
+ QVERIFY(whenAllEmpty.result().empty());
+}
+
+void tst_QFuture::whenAllIterators()
+{
+ // Try with different output containers
+ testWhenAllIterators<QList<QFuture<int>>>();
+ if (QTest::currentTestFailed())
+ QSKIP("testWhenAllIterators() with QList failed!");
+
+ testWhenAllIterators<std::vector<QFuture<int>>>();
+ if (QTest::currentTestFailed())
+ QSKIP("testWhenAllIterators() with std::vector failed!");
+
+ testWhenAllIterators<QVarLengthArray<QFuture<int>>>();
+ if (QTest::currentTestFailed())
+ QSKIP("testWhenAllIterators() with QVarLengthArray failed!");
+}
+
+void tst_QFuture::whenAllIteratorsWithCanceled()
+{
+ QPromise<int> p0;
+ QPromise<int> p1;
+ QList<QFuture<int>> futures = { p0.future(), p1.future() };
+ bool finished = false;
+ auto whenAll = QtFuture::whenAll(futures.begin(), futures.end())
+ .then([&](const QList<QFuture<int>> &results) {
+ QCOMPARE(results.size(), 2);
+ QVERIFY(results[0].isCanceled());
+ QVERIFY(!results[1].isCanceled());
+ QCOMPARE(results[1].result(), 1);
+ finished = true;
+ });
+
+ p0.start();
+ p0.future().cancel();
+ p0.finish();
+ QVERIFY(!finished);
+
+ p1.start();
+ p1.addResult(1);
+ p1.finish();
+ QVERIFY(finished);
+}
+
+void tst_QFuture::whenAllIteratorsWithFailed()
+{
+#ifndef QT_NO_EXCEPTIONS
+ QPromise<int> p0;
+ QPromise<int> p1;
+ QList<QFuture<int>> futures = { p0.future(), p1.future() };
+ bool finished = false;
+ auto whenAll = QtFuture::whenAll(futures.begin(), futures.end())
+ .then([&](QList<QFuture<int>> results) {
+ QCOMPARE(results.size(), 2);
+ QCOMPARE(results[1].result(), 1);
+ // A shorter way of handling the exception
+ results[0].onFailed([&](const QException &) {
+ finished = true;
+ return 0;
+ });
+ });
+
+ p0.start();
+ p0.setException(QException());
+ p0.finish();
+ QVERIFY(!finished);
+
+ p1.start();
+ p1.addResult(1);
+ p1.finish();
+ QVERIFY(finished);
+#else
+ QSKIP("Exceptions are disabled, skipping the test")
+#endif
+}
+
+// A helper for std::visit, see https://en.cppreference.com/w/cpp/utility/variant/visit
+template<class... Ts>
+struct overloaded : public Ts...
+{
+ using Ts::operator()...;
+};
+
+// explicit deduction guide
+template<class... Ts>
+overloaded(Ts...)->overloaded<Ts...>;
+
+template<class OutputContainer>
+void testWhenAllDifferentTypes()
+{
+ QPromise<int> pInt1;
+ QPromise<int> pInt2;
+ QPromise<void> pVoid;
+
+ using Futures = std::variant<QFuture<int>, QFuture<int>, QFuture<void>>;
+
+ QFuture<OutputContainer> whenAll;
+ if constexpr (std::is_same_v<QList<Futures>, OutputContainer>) {
+ whenAll = QtFuture::whenAll(pInt1.future(), pInt2.future(), pVoid.future());
+ } else {
+ whenAll =
+ QtFuture::whenAll<OutputContainer>(pInt1.future(), pInt2.future(), pVoid.future());
+ }
+
+ int sumOfInts = 0;
+ whenAll.then([&](const OutputContainer &results) {
+ for (auto future : results) {
+ std::visit(overloaded {
+ [&](const QFuture<int> &f) {
+ QVERIFY(f.isFinished());
+ sumOfInts += f.result();
+ },
+ [](const QFuture<void> &f) { QVERIFY(f.isFinished()); },
+ },
+ future);
+ }
+ });
+
+ pVoid.start();
+ pVoid.finish();
+ QVERIFY(whenAll.isRunning());
+
+ pInt2.start();
+ pInt2.addResult(2);
+ pInt2.finish();
+ QVERIFY(whenAll.isRunning());
+ QCOMPARE(sumOfInts, 0);
+
+ pInt1.start();
+ pInt1.addResult(1);
+ pInt1.finish();
+ QVERIFY(!whenAll.isRunning());
+ QCOMPARE(sumOfInts, 3);
+}
+
+void tst_QFuture::whenAllDifferentTypes()
+{
+ using Futures = std::variant<QFuture<int>, QFuture<int>, QFuture<void>>;
+ testWhenAllDifferentTypes<QList<Futures>>();
+ if (QTest::currentTestFailed())
+ QSKIP("testWhenAllDifferentTypes() with QList failed!");
+
+ testWhenAllDifferentTypes<std::vector<Futures>>();
+ if (QTest::currentTestFailed())
+ QSKIP("testWhenAllDifferentTypes() with std::vector failed!");
+
+ testWhenAllDifferentTypes<QVarLengthArray<Futures>>();
+ if (QTest::currentTestFailed())
+ QSKIP("testWhenAllDifferentTypes() with QVarLengthArray failed!");
+}
+
+void tst_QFuture::whenAllDifferentTypesWithCanceled()
+{
+ QPromise<int> pInt;
+ QPromise<QString> pString;
+
+ const QString someValue = u"some value"_qs;
+
+ 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"_qs;
+
+ 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
+}
+
QTEST_MAIN(tst_QFuture)
#include "tst_qfuture.moc"