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 | 14 | ||||
-rw-r--r-- | tests/auto/corelib/thread/qfuture/qfuture.pro | 1 | ||||
-rw-r--r-- | tests/auto/corelib/thread/qfuture/tst_qfuture.cpp | 1132 |
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" |