summaryrefslogtreecommitdiffstats
path: root/tests/auto/corelib/thread/qfuture
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/corelib/thread/qfuture')
-rw-r--r--tests/auto/corelib/thread/qfuture/.prev_CMakeLists.txt14
-rw-r--r--tests/auto/corelib/thread/qfuture/CMakeLists.txt14
-rw-r--r--tests/auto/corelib/thread/qfuture/qfuture.pro1
-rw-r--r--tests/auto/corelib/thread/qfuture/tst_qfuture.cpp1132
4 files changed, 1159 insertions, 2 deletions
diff --git a/tests/auto/corelib/thread/qfuture/.prev_CMakeLists.txt b/tests/auto/corelib/thread/qfuture/.prev_CMakeLists.txt
new file mode 100644
index 0000000000..6908bd678c
--- /dev/null
+++ b/tests/auto/corelib/thread/qfuture/.prev_CMakeLists.txt
@@ -0,0 +1,14 @@
+# 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
new file mode 100644
index 0000000000..6fdfc977e9
--- /dev/null
+++ b/tests/auto/corelib/thread/qfuture/CMakeLists.txt
@@ -0,0 +1,14 @@
+# 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/qfuture.pro b/tests/auto/corelib/thread/qfuture/qfuture.pro
index 1f21130af7..fe097edf20 100644
--- a/tests/auto/corelib/thread/qfuture/qfuture.pro
+++ b/tests/auto/corelib/thread/qfuture/qfuture.pro
@@ -2,5 +2,4 @@ CONFIG += testcase
TARGET = tst_qfuture
QT = core core-private testlib
SOURCES = tst_qfuture.cpp
-DEFINES += QT_STRICT_ITERATORS
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 a42454124e..1caa386638 100644
--- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp
+++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp
@@ -37,8 +37,12 @@
#include <qthreadpool.h>
#include <qexception.h>
#include <qrandom.h>
+#include <QtConcurrent/qtconcurrentrun.h>
#include <private/qfutureinterface_p.h>
+#include <vector>
+#include <memory>
+
// COM interface macro.
#if defined(Q_OS_WIN) && defined(interface)
# undef interface
@@ -94,6 +98,32 @@ private slots:
void nestedExceptions();
#endif
void nonGlobalThreadPool();
+
+ void then();
+ void thenOnCanceledFuture();
+#ifndef QT_NO_EXCEPTIONS
+ void thenOnExceptionFuture();
+ void thenThrows();
+ void onFailed();
+ void onFailedTestCallables();
+#endif
+ void takeResults();
+ void takeResult();
+ void runAndTake();
+ void resultsReadyAt_data();
+ void resultsReadyAt();
+private:
+ using size_type = std::vector<int>::size_type;
+ using UniquePtr = std::unique_ptr<int>;
+
+ static void testSingleResult(const UniquePtr &p);
+ static void testSingleResult(const std::vector<int> &v);
+ template<class T>
+ static void testSingleResult(const T &unknown);
+ template<class T>
+ static void testFutureTaken(QFuture<T> &noMoreFuture);
+ template<class T>
+ static void testTakeResults(QFuture<T> future, size_type resultCount);
};
void tst_QFuture::resultStore()
@@ -1166,7 +1196,6 @@ void tst_QFuture::iterators()
void tst_QFuture::iteratorsThread()
{
const int expectedResultCount = 10;
- const int delay = 10;
QFutureInterface<int> futureInterface;
// Create result producer thread. The results are
@@ -1391,6 +1420,23 @@ QFuture<void> createDerivedExceptionFuture()
return f;
}
+struct TestException
+{
+};
+
+QFuture<int> createCustomExceptionFuture()
+{
+ QFutureInterface<int> i;
+ i.reportStarted();
+ QFuture<int> f = i.future();
+ int r = 0;
+ i.reportResult(r);
+ auto exception = std::make_exception_ptr(TestException());
+ i.reportException(exception);
+ i.reportFinished();
+ return f;
+}
+
void tst_QFuture::exceptions()
{
// test throwing from waitForFinished
@@ -1475,6 +1521,18 @@ void tst_QFuture::exceptions()
}
QVERIFY(caught);
}
+
+ // Custom exceptions
+ {
+ QFuture<int> f = createCustomExceptionFuture();
+ bool caught = false;
+ try {
+ f.result();
+ } catch (const TestException &) {
+ caught = true;
+ }
+ QVERIFY(caught);
+ }
}
class MyClass
@@ -1557,5 +1615,1077 @@ void tst_QFuture::nonGlobalThreadPool()
}
}
+void tst_QFuture::then()
+{
+ {
+ struct Add
+ {
+
+ static int addTwo(int arg) { return arg + 2; }
+
+ int operator()(int arg) const { return arg + 3; }
+ };
+
+ QFutureInterface<int> promise;
+ QFuture<int> then = promise.future()
+ .then([](int res) { return res + 1; }) // lambda
+ .then(Add::addTwo) // function
+ .then(Add()); // functor
+
+ promise.reportStarted();
+ QVERIFY(!then.isStarted());
+ QVERIFY(!then.isFinished());
+
+ const int result = 0;
+ promise.reportResult(result);
+ promise.reportFinished();
+
+ then.waitForFinished();
+
+ QVERIFY(then.isStarted());
+ QVERIFY(then.isFinished());
+ QCOMPARE(then.result(), result + 6);
+ }
+
+ // then() on a ready future
+ {
+ QFutureInterface<int> promise;
+ promise.reportStarted();
+
+ const int result = 0;
+ promise.reportResult(result);
+ promise.reportFinished();
+
+ QFuture<int> then = promise.future()
+ .then([](int res1) { return res1 + 1; })
+ .then([](int res2) { return res2 + 2; })
+ .then([](int res3) { return res3 + 3; });
+
+ then.waitForFinished();
+
+ QVERIFY(then.isStarted());
+ QVERIFY(then.isFinished());
+ QCOMPARE(then.result(), result + 6);
+ }
+
+ // Continuation of QFuture<void>
+ {
+ int result = 0;
+ QFutureInterface<void> promise;
+ QFuture<void> then = promise.future()
+ .then([&]() { result += 1; })
+ .then([&]() { result += 2; })
+ .then([&]() { result += 3; });
+
+ promise.reportStarted();
+ QVERIFY(!then.isStarted());
+ QVERIFY(!then.isFinished());
+ promise.reportFinished();
+
+ then.waitForFinished();
+
+ QVERIFY(then.isStarted());
+ QVERIFY(then.isFinished());
+ QCOMPARE(result, 6);
+ }
+
+ // Continuation returns QFuture<void>
+ {
+ QFutureInterface<int> promise;
+ int value;
+ QFuture<void> then =
+ promise.future().then([](int res) { return res * 2; }).then([&](int prevResult) {
+ value = prevResult;
+ });
+
+ promise.reportStarted();
+ QVERIFY(!then.isStarted());
+ QVERIFY(!then.isFinished());
+
+ const int result = 5;
+ promise.reportResult(result);
+ promise.reportFinished();
+
+ then.waitForFinished();
+
+ QVERIFY(then.isStarted());
+ QVERIFY(then.isFinished());
+ QCOMPARE(value, result * 2);
+ }
+
+ // Continuations taking a QFuture argument.
+ {
+ int value = 0;
+ QFutureInterface<int> promise;
+ QFuture<void> then = promise.future()
+ .then([](QFuture<int> f1) { return f1.result() + 1; })
+ .then([&](QFuture<int> f2) { value = f2.result() + 2; })
+ .then([&](QFuture<void> f3) {
+ QVERIFY(f3.isFinished());
+ value += 3;
+ });
+
+ promise.reportStarted();
+ QVERIFY(!then.isStarted());
+ QVERIFY(!then.isFinished());
+
+ const int result = 0;
+ promise.reportResult(result);
+ promise.reportFinished();
+
+ then.waitForFinished();
+
+ QVERIFY(then.isStarted());
+ QVERIFY(then.isFinished());
+ QCOMPARE(value, 6);
+ }
+
+ // Continuations use a new thread
+ {
+ Qt::HANDLE threadId1 = nullptr;
+ Qt::HANDLE threadId2 = nullptr;
+ QFutureInterface<void> promise;
+ QFuture<void> then = promise.future()
+ .then(QtFuture::Launch::Async,
+ [&]() { threadId1 = QThread::currentThreadId(); })
+ .then([&]() { threadId2 = QThread::currentThreadId(); });
+
+ promise.reportStarted();
+ QVERIFY(!then.isStarted());
+ QVERIFY(!then.isFinished());
+
+ promise.reportFinished();
+
+ then.waitForFinished();
+
+ QVERIFY(then.isStarted());
+ QVERIFY(then.isFinished());
+ QVERIFY(threadId1 != QThread::currentThreadId());
+ QVERIFY(threadId2 != QThread::currentThreadId());
+ QVERIFY(threadId1 == threadId2);
+ }
+
+ // Continuation inherits the launch policy of its parent (QtFuture::Launch::Sync)
+ {
+ Qt::HANDLE threadId1 = nullptr;
+ Qt::HANDLE threadId2 = nullptr;
+ QFutureInterface<void> promise;
+ QFuture<void> then = promise.future()
+ .then(QtFuture::Launch::Sync,
+ [&]() { threadId1 = QThread::currentThreadId(); })
+ .then(QtFuture::Launch::Inherit,
+ [&]() { threadId2 = QThread::currentThreadId(); });
+
+ promise.reportStarted();
+ QVERIFY(!then.isStarted());
+ QVERIFY(!then.isFinished());
+
+ promise.reportFinished();
+
+ then.waitForFinished();
+
+ QVERIFY(then.isStarted());
+ QVERIFY(then.isFinished());
+ QVERIFY(threadId1 == QThread::currentThreadId());
+ QVERIFY(threadId2 == QThread::currentThreadId());
+ QVERIFY(threadId1 == threadId2);
+ }
+
+ // Continuation inherits the launch policy of its parent (QtFuture::Launch::Async)
+ {
+ Qt::HANDLE threadId1 = nullptr;
+ Qt::HANDLE threadId2 = nullptr;
+ QFutureInterface<void> promise;
+ QFuture<void> then = promise.future()
+ .then(QtFuture::Launch::Async,
+ [&]() { threadId1 = QThread::currentThreadId(); })
+ .then(QtFuture::Launch::Inherit,
+ [&]() { threadId2 = QThread::currentThreadId(); });
+
+ promise.reportStarted();
+ QVERIFY(!then.isStarted());
+ QVERIFY(!then.isFinished());
+
+ promise.reportFinished();
+
+ then.waitForFinished();
+
+ QVERIFY(then.isStarted());
+ QVERIFY(then.isFinished());
+ QVERIFY(threadId1 != QThread::currentThreadId());
+ QVERIFY(threadId2 != QThread::currentThreadId());
+ }
+
+ // Continuations use a custom thread pool
+ {
+ QFutureInterface<void> promise;
+ QThreadPool pool;
+ QVERIFY(pool.waitForDone(0)); // pool is not busy yet
+ QSemaphore semaphore;
+ QFuture<void> then = promise.future().then(&pool, [&]() { semaphore.acquire(); });
+
+ promise.reportStarted();
+ promise.reportFinished();
+
+ // Make sure the custom thread pool is busy on running the continuation
+ QVERIFY(!pool.waitForDone(0));
+ semaphore.release();
+ then.waitForFinished();
+
+ QVERIFY(then.isStarted());
+ QVERIFY(then.isFinished());
+ QCOMPARE(then.d.threadPool(), &pool);
+ }
+
+ // Continuation inherits parent's thread pool
+ {
+ Qt::HANDLE threadId1 = nullptr;
+ Qt::HANDLE threadId2 = nullptr;
+ QFutureInterface<void> promise;
+
+ QThreadPool pool;
+ QFuture<void> then1 = promise.future().then(&pool, [&]() {
+ threadId1 = QThread::currentThreadId();
+ });
+
+ promise.reportStarted();
+ promise.reportFinished();
+
+ then1.waitForFinished();
+ QVERIFY(pool.waitForDone()); // The pool is not busy after the first continuation is done
+
+ QSemaphore semaphore;
+ QFuture<void> then2 = then1.then(QtFuture::Launch::Inherit, [&]() {
+ semaphore.acquire();
+ threadId2 = QThread::currentThreadId();
+ });
+
+ QVERIFY(!pool.waitForDone(0)); // The pool is busy running the 2nd continuation
+
+ semaphore.release();
+ then2.waitForFinished();
+
+ QVERIFY(then2.isStarted());
+ QVERIFY(then2.isFinished());
+ QCOMPARE(then1.d.threadPool(), then2.d.threadPool());
+ QCOMPARE(then2.d.threadPool(), &pool);
+ QVERIFY(threadId1 != QThread::currentThreadId());
+ QVERIFY(threadId2 != QThread::currentThreadId());
+ }
+}
+
+void tst_QFuture::thenOnCanceledFuture()
+{
+ // Continuations on a canceled future
+ {
+ QFutureInterface<void> promise;
+ promise.reportStarted();
+ promise.reportCanceled();
+ promise.reportFinished();
+
+ int thenResult = 0;
+ QFuture<void> then =
+ promise.future().then([&]() { ++thenResult; }).then([&]() { ++thenResult; });
+
+ QVERIFY(then.isCanceled());
+ QCOMPARE(thenResult, 0);
+ }
+
+ // QFuture gets canceled after continuations are set
+ {
+ QFutureInterface<void> promise;
+
+ int thenResult = 0;
+ QFuture<void> then =
+ promise.future().then([&]() { ++thenResult; }).then([&]() { ++thenResult; });
+
+ promise.reportStarted();
+ promise.reportCanceled();
+ promise.reportFinished();
+
+ QVERIFY(then.isCanceled());
+ QCOMPARE(thenResult, 0);
+ }
+
+ // Same with QtFuture::Launch::Async
+
+ // Continuations on a canceled future
+ {
+ QFutureInterface<void> promise;
+ promise.reportStarted();
+ promise.reportCanceled();
+ promise.reportFinished();
+
+ int thenResult = 0;
+ QFuture<void> then =
+ promise.future().then(QtFuture::Launch::Async, [&]() { ++thenResult; }).then([&]() {
+ ++thenResult;
+ });
+
+ QVERIFY(then.isCanceled());
+ QCOMPARE(thenResult, 0);
+ }
+
+ // QFuture gets canceled after continuations are set
+ {
+ QFutureInterface<void> promise;
+
+ int thenResult = 0;
+ QFuture<void> then =
+ promise.future().then(QtFuture::Launch::Async, [&]() { ++thenResult; }).then([&]() {
+ ++thenResult;
+ });
+
+ promise.reportStarted();
+ promise.reportCanceled();
+ promise.reportFinished();
+
+ QVERIFY(then.isCanceled());
+ QCOMPARE(thenResult, 0);
+ }
+}
+
+#ifndef QT_NO_EXCEPTIONS
+void tst_QFuture::thenOnExceptionFuture()
+{
+ {
+ QFutureInterface<int> promise;
+
+ int thenResult = 0;
+ QFuture<void> then = promise.future().then([&](int res) { thenResult = res; });
+
+ promise.reportStarted();
+ QException e;
+ promise.reportException(e);
+ promise.reportFinished();
+
+ bool caught = false;
+ try {
+ then.waitForFinished();
+ } catch (QException &) {
+ caught = true;
+ }
+ QVERIFY(caught);
+ QCOMPARE(thenResult, 0);
+ }
+
+ // Exception handled inside the continuation
+ {
+ QFutureInterface<int> promise;
+
+ bool caught = false;
+ bool caughtByContinuation = false;
+ bool success = false;
+ int thenResult = 0;
+ QFuture<void> then = promise.future()
+ .then([&](QFuture<int> res) {
+ try {
+ thenResult = res.result();
+ } catch (QException &) {
+ caughtByContinuation = true;
+ }
+ })
+ .then([&]() { success = true; });
+
+ promise.reportStarted();
+ QException e;
+ promise.reportException(e);
+ promise.reportFinished();
+
+ try {
+ then.waitForFinished();
+ } catch (QException &) {
+ caught = true;
+ }
+
+ QCOMPARE(thenResult, 0);
+ QVERIFY(!caught);
+ QVERIFY(caughtByContinuation);
+ QVERIFY(success);
+ }
+
+ // Exception future
+ {
+ QFutureInterface<int> promise;
+ promise.reportStarted();
+ QException e;
+ promise.reportException(e);
+ promise.reportFinished();
+
+ int thenResult = 0;
+ QFuture<void> then = promise.future().then([&](int res) { thenResult = res; });
+
+ bool caught = false;
+ try {
+ then.waitForFinished();
+ } catch (QException &) {
+ caught = true;
+ }
+ QVERIFY(caught);
+ QCOMPARE(thenResult, 0);
+ }
+
+ // Same with QtFuture::Launch::Async
+ {
+ QFutureInterface<int> promise;
+
+ int thenResult = 0;
+ QFuture<void> then =
+ promise.future().then(QtFuture::Launch::Async, [&](int res) { thenResult = res; });
+
+ promise.reportStarted();
+ QException e;
+ promise.reportException(e);
+ promise.reportFinished();
+
+ bool caught = false;
+ try {
+ then.waitForFinished();
+ } catch (QException &) {
+ caught = true;
+ }
+ QVERIFY(caught);
+ QCOMPARE(thenResult, 0);
+ }
+
+ // Exception future
+ {
+ QFutureInterface<int> promise;
+ promise.reportStarted();
+ QException e;
+ promise.reportException(e);
+ promise.reportFinished();
+
+ int thenResult = 0;
+ QFuture<void> then =
+ promise.future().then(QtFuture::Launch::Async, [&](int res) { thenResult = res; });
+
+ bool caught = false;
+ try {
+ then.waitForFinished();
+ } catch (QException &) {
+ caught = true;
+ }
+ QVERIFY(caught);
+ QCOMPARE(thenResult, 0);
+ }
+}
+
+template<class Exception, bool hasTestMsg = false>
+QFuture<void> createExceptionContinuation(QtFuture::Launch policy = QtFuture::Launch::Sync)
+{
+ QFutureInterface<void> promise;
+
+ auto then = promise.future().then(policy, [] {
+ if constexpr (hasTestMsg)
+ throw Exception("TEST");
+ else
+ throw Exception();
+ });
+
+ promise.reportStarted();
+ promise.reportFinished();
+
+ return then;
+}
+
+void tst_QFuture::thenThrows()
+{
+ // Continuation throws a QException
+ {
+ auto future = createExceptionContinuation<QException>();
+
+ bool caught = false;
+ try {
+ future.waitForFinished();
+ } catch (const QException &) {
+ caught = true;
+ }
+ QVERIFY(caught);
+ }
+
+ // Continuation throws an exception derived from QException
+ {
+ auto future = createExceptionContinuation<DerivedException>();
+
+ bool caught = false;
+ try {
+ future.waitForFinished();
+ } catch (const QException &) {
+ caught = true;
+ } catch (const std::exception &) {
+ QFAIL("The exception should be caught by the above catch block.");
+ }
+
+ QVERIFY(caught);
+ }
+
+ // Continuation throws std::exception
+ {
+ auto future = createExceptionContinuation<std::runtime_error, true>();
+
+ bool caught = false;
+ try {
+ future.waitForFinished();
+ } catch (const QException &) {
+ QFAIL("The exception should be caught by the below catch block.");
+ } catch (const std::exception &e) {
+ QCOMPARE(e.what(), "TEST");
+ caught = true;
+ }
+
+ QVERIFY(caught);
+ }
+
+ // Same with QtFuture::Launch::Async
+ {
+ auto future = createExceptionContinuation<QException>(QtFuture::Launch::Async);
+
+ bool caught = false;
+ try {
+ future.waitForFinished();
+ } catch (const QException &) {
+ caught = true;
+ }
+ QVERIFY(caught);
+ }
+}
+
+void tst_QFuture::onFailed()
+{
+ // Ready exception void future
+ {
+ int checkpoint = 0;
+ auto future = createExceptionFuture().then([&] { checkpoint = 1; }).onFailed([&] {
+ checkpoint = 2;
+ });
+
+ try {
+ future.waitForFinished();
+ } catch (...) {
+ checkpoint = 3;
+ }
+ QCOMPARE(checkpoint, 2);
+ }
+
+ // std::exception handler
+ {
+ QFutureInterface<int> promise;
+
+ int checkpoint = 0;
+ auto then = promise.future()
+ .then([&](int res) {
+ throw std::exception();
+ return res;
+ })
+ .then([&](int res) { return res + 1; })
+ .onFailed([&](const QException &) {
+ checkpoint = 1;
+ return -1;
+ })
+ .onFailed([&](const std::exception &) {
+ checkpoint = 2;
+ return -1;
+ })
+ .onFailed([&] {
+ checkpoint = 3;
+ return -1;
+ });
+
+ promise.reportStarted();
+ promise.reportResult(1);
+ promise.reportFinished();
+
+ int res = 0;
+ try {
+ res = then.result();
+ } catch (...) {
+ checkpoint = 4;
+ }
+ QCOMPARE(checkpoint, 2);
+ QCOMPARE(res, -1);
+ }
+
+ // then() throws an exception derived from QException
+ {
+ QFutureInterface<int> promise;
+
+ int checkpoint = 0;
+ auto then = promise.future()
+ .then([&](int res) {
+ throw DerivedException();
+ return res;
+ })
+ .then([&](int res) { return res + 1; })
+ .onFailed([&](const QException &) {
+ checkpoint = 1;
+ return -1;
+ })
+ .onFailed([&](const std::exception &) {
+ checkpoint = 2;
+ return -1;
+ })
+ .onFailed([&] {
+ checkpoint = 3;
+ return -1;
+ });
+
+ promise.reportStarted();
+ promise.reportResult(1);
+ promise.reportFinished();
+
+ int res = 0;
+ try {
+ res = then.result();
+ } catch (...) {
+ checkpoint = 4;
+ }
+ QCOMPARE(checkpoint, 1);
+ QCOMPARE(res, -1);
+ }
+
+ // then() throws a custom exception
+ {
+ QFutureInterface<int> promise;
+
+ int checkpoint = 0;
+ auto then = promise.future()
+ .then([&](int res) {
+ throw TestException();
+ return res;
+ })
+ .then([&](int res) { return res + 1; })
+ .onFailed([&](const QException &) {
+ checkpoint = 1;
+ return -1;
+ })
+ .onFailed([&](const std::exception &) {
+ checkpoint = 2;
+ return -1;
+ })
+ .onFailed([&] {
+ checkpoint = 3;
+ return -1;
+ });
+
+ promise.reportStarted();
+ promise.reportResult(1);
+ promise.reportFinished();
+
+ int res = 0;
+ try {
+ res = then.result();
+ } catch (...) {
+ checkpoint = 4;
+ }
+ QCOMPARE(checkpoint, 3);
+ QCOMPARE(res, -1);
+ }
+
+ // Custom exception handler
+ {
+ struct TestException
+ {
+ };
+
+ QFutureInterface<int> promise;
+
+ int checkpoint = 0;
+ auto then = promise.future()
+ .then([&](int res) {
+ throw TestException();
+ return res;
+ })
+ .then([&](int res) { return res + 1; })
+ .onFailed([&](const QException &) {
+ checkpoint = 1;
+ return -1;
+ })
+ .onFailed([&](const TestException &) {
+ checkpoint = 2;
+ return -1;
+ })
+ .onFailed([&] {
+ checkpoint = 3;
+ return -1;
+ });
+
+ promise.reportStarted();
+ promise.reportResult(1);
+ promise.reportFinished();
+
+ int res = 0;
+ try {
+ res = then.result();
+ } catch (...) {
+ checkpoint = 4;
+ }
+ QCOMPARE(checkpoint, 2);
+ QCOMPARE(res, -1);
+ }
+
+ // Handle all exceptions
+ {
+ QFutureInterface<int> promise;
+
+ int checkpoint = 0;
+ auto then = promise.future()
+ .then([&](int res) {
+ throw QException();
+ return res;
+ })
+ .then([&](int res) { return res + 1; })
+ .onFailed([&] {
+ checkpoint = 1;
+ return -1;
+ })
+ .onFailed([&](const QException &) {
+ checkpoint = 2;
+ return -1;
+ });
+
+ promise.reportStarted();
+ promise.reportResult(1);
+ promise.reportFinished();
+
+ int res = 0;
+ try {
+ res = then.result();
+ } catch (...) {
+ checkpoint = 3;
+ }
+ QCOMPARE(checkpoint, 1);
+ QCOMPARE(res, -1);
+ }
+
+ // Handler throws exception
+ {
+ QFutureInterface<int> promise;
+
+ int checkpoint = 0;
+ auto then = promise.future()
+ .then([&](int res) {
+ throw QException();
+ return res;
+ })
+ .then([&](int res) { return res + 1; })
+ .onFailed([&](const QException &) {
+ checkpoint = 1;
+ throw QException();
+ return -1;
+ })
+ .onFailed([&] {
+ checkpoint = 2;
+ return -1;
+ });
+
+ promise.reportStarted();
+ promise.reportResult(1);
+ promise.reportFinished();
+
+ int res = 0;
+ try {
+ res = then.result();
+ } catch (...) {
+ checkpoint = 3;
+ }
+ QCOMPARE(checkpoint, 2);
+ QCOMPARE(res, -1);
+ }
+
+ // No handler for exception
+ {
+ QFutureInterface<int> promise;
+
+ int checkpoint = 0;
+ auto then = promise.future()
+ .then([&](int res) {
+ throw QException();
+ return res;
+ })
+ .then([&](int res) { return res + 1; })
+ .onFailed([&](const std::exception &) {
+ checkpoint = 1;
+ throw std::exception();
+ return -1;
+ })
+ .onFailed([&](QException &) {
+ checkpoint = 2;
+ return -1;
+ });
+
+ promise.reportStarted();
+ promise.reportResult(1);
+ promise.reportFinished();
+
+ int res = 0;
+ try {
+ res = then.result();
+ } catch (...) {
+ checkpoint = 3;
+ }
+ QCOMPARE(checkpoint, 3);
+ QCOMPARE(res, 0);
+ }
+}
+
+template<class Callable>
+bool runForCallable(Callable &&handler)
+{
+ QFuture<int> future = createExceptionResultFuture()
+ .then([&](int) { return 1; })
+ .onFailed(std::forward<Callable>(handler));
+
+ int res = 0;
+ try {
+ res = future.result();
+ } catch (...) {
+ return false;
+ }
+ return res == -1;
+}
+
+int foo()
+{
+ return -1;
+}
+
+void tst_QFuture::onFailedTestCallables()
+{
+ QVERIFY(runForCallable([&] { return -1; }));
+ QVERIFY(runForCallable(foo));
+ QVERIFY(runForCallable(&foo));
+
+ std::function<int()> func = foo;
+ QVERIFY(runForCallable(func));
+
+ struct Functor1
+ {
+ int operator()() { return -1; }
+ static int foo() { return -1; }
+ };
+ QVERIFY(runForCallable(Functor1()));
+ QVERIFY(runForCallable(Functor1::foo));
+
+ struct Functor2
+ {
+ int operator()() const { return -1; }
+ static int foo() { return -1; }
+ };
+ QVERIFY(runForCallable(Functor2()));
+
+ struct Functor3
+ {
+ int operator()() const noexcept { return -1; }
+ static int foo() { return -1; }
+ };
+ QVERIFY(runForCallable(Functor3()));
+}
+
+#endif // QT_NO_EXCEPTIONS
+
+void tst_QFuture::testSingleResult(const UniquePtr &p)
+{
+ QVERIFY(p.get() != nullptr);
+}
+
+void tst_QFuture::testSingleResult(const std::vector<int> &v)
+{
+ QVERIFY(!v.empty());
+}
+
+template<class T>
+void tst_QFuture::testSingleResult(const T &unknown)
+{
+ Q_UNUSED(unknown);
+}
+
+
+template<class T>
+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.isPaused(), false);
+ QCOMPARE(noMoreFuture.isFinished(), false);
+ QCOMPARE(noMoreFuture.progressValue(), 0);
+}
+
+template<class T>
+void tst_QFuture::testTakeResults(QFuture<T> future, size_type resultCount)
+{
+ auto copy = future;
+ QVERIFY(future.isFinished());
+ QVERIFY(future.isValid());
+ QCOMPARE(size_type(future.resultCount()), resultCount);
+ QVERIFY(copy.isFinished());
+ QVERIFY(copy.isValid());
+ QCOMPARE(size_type(copy.resultCount()), resultCount);
+
+ auto vec = future.takeResults();
+ QCOMPARE(vec.size(), resultCount);
+
+ for (const auto &r : vec) {
+ testSingleResult(r);
+ if (QTest::currentTestFailed())
+ return;
+ }
+
+ testFutureTaken(future);
+ if (QTest::currentTestFailed())
+ return;
+ testFutureTaken(copy);
+}
+
+void tst_QFuture::takeResults()
+{
+ // Test takeResults() for movable types (whether or not copyable).
+
+ // std::unique_ptr<int> supports only move semantics:
+ QFutureInterface<UniquePtr> moveIface;
+ moveIface.reportStarted();
+
+ // std::vector<int> supports both copy and move:
+ QFutureInterface<std::vector<int>> copyIface;
+ copyIface.reportStarted();
+
+ 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);
+ }
+
+ moveIface.reportFinished();
+ copyIface.reportFinished();
+
+ testTakeResults(moveIface.future(), size_type(expectedCount));
+ if (QTest::currentTestFailed())
+ return;
+
+ testTakeResults(copyIface.future(), size_type(expectedCount));
+}
+
+void tst_QFuture::takeResult()
+{
+ QFutureInterface<UniquePtr> iface;
+ iface.reportStarted();
+ iface.reportAndMoveResult(UniquePtr{new int(0b101010)}, 0);
+ iface.reportFinished();
+
+ auto future = iface.future();
+ QVERIFY(future.isFinished());
+ QVERIFY(future.isValid());
+ QCOMPARE(future.resultCount(), 1);
+
+ auto result = future.takeResult();
+ testFutureTaken(future);
+ if (QTest::currentTestFailed())
+ return;
+ testSingleResult(result);
+}
+
+void tst_QFuture::runAndTake()
+{
+ // Test if a 'moving' future can be used by
+ // QtConcurrent::run.
+
+ 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);
+ return UniquePtr(new int(10));
+ };
+
+ QTestEventLoop loop;
+ QFutureWatcher<UniquePtr> watcha;
+ connect(&watcha, &QFutureWatcher<UniquePtr>::finished, [&loop](){
+ loop.exitLoop();
+ });
+
+ auto gotcha = QtConcurrent::run(rabbit);
+ watcha.setFuture(gotcha);
+
+ loop.enterLoopMSecs(500);
+ if (loop.timeout())
+ QSKIP("Failed to run the task, nothing to test");
+
+ gotcha = watcha.future();
+ testTakeResults(gotcha, size_type(1));
+}
+
+void tst_QFuture::resultsReadyAt_data()
+{
+ QTest::addColumn<bool>("testMove");
+
+ QTest::addRow("reportResult") << false;
+ QTest::addRow("reportAndMoveResult") << true;
+}
+
+void tst_QFuture::resultsReadyAt()
+{
+ QFETCH(const bool, testMove);
+
+ QFutureInterface<int> iface;
+ QFutureWatcher<int> watcher;
+ watcher.setFuture(iface.future());
+
+ QTestEventLoop eventProcessor;
+ connect(&watcher, &QFutureWatcher<int>::finished, &eventProcessor, &QTestEventLoop::exitLoop);
+
+ const int nExpectedResults = 4;
+ int reported = 0;
+ int taken = 0;
+ connect(&watcher, &QFutureWatcher<int>::resultsReadyAt,
+ [&iface, &reported, &taken](int begin, int end)
+ {
+ auto future = iface.future();
+ QVERIFY(end - begin > 0);
+ for (int i = begin; i < end; ++i, ++reported) {
+ QVERIFY(future.isResultReadyAt(i));
+ taken |= 1 << i;
+ }
+ });
+
+ auto report = [&iface, testMove](int index)
+ {
+ int dummyResult = 0b101010;
+ if (testMove)
+ iface.reportAndMoveResult(std::move(dummyResult), index);
+ else
+ iface.reportResult(&dummyResult, index);
+ };
+
+ const QSignalSpy readyCounter(&watcher, &QFutureWatcher<int>::resultsReadyAt);
+ QTimer::singleShot(0, [&iface, &report]{
+ // With filter mode == true, the result may go into the pending results.
+ // Reporting it as ready will allow an application to try and access the
+ // result, crashing on invalid (store.end()) iterator dereferenced.
+ iface.setFilterMode(true);
+ iface.reportStarted();
+ report(0);
+ report(1);
+ // This one - should not be reported (it goes into pending):
+ report(3);
+ // Let's close the 'gap' and make them all ready:
+ report(-1);
+ iface.reportFinished();
+ });
+
+ // Run event loop, QCoreApplication::postEvent is in use
+ // in QFutureInterface:
+ eventProcessor.enterLoopMSecs(2000);
+ 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(taken, 0b1111);
+}
+
QTEST_MAIN(tst_QFuture)
#include "tst_qfuture.moc"