/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include using namespace QtConcurrent; #include //#define PRINT class tst_QFutureWatcher: public QObject { Q_OBJECT private slots: void startFinish(); void progressValueChanged(); void canceled(); void resultAt(); void resultReadyAt(); void futureSignals(); void watchFinishedFuture(); void watchCanceledFuture(); void disconnectRunningFuture(); void tooMuchProgress(); void progressText(); void sharedFutureInterface(); void changeFuture(); void cancelEvents(); #if QT_DEPRECATED_SINCE(6, 0) void pauseEvents(); void pausedSuspendedOrder(); #endif void suspendEvents(); void suspended(); void suspendedEventsOrder(); void finishedState(); void throttling(); void incrementalMapResults(); void incrementalFilterResults(); void qfutureSynchronizer(); void warnRace(); void matchFlags(); }; void sleeper() { QTest::qSleep(100); } void tst_QFutureWatcher::startFinish() { QFutureWatcher futureWatcher; int startedCount = 0; int finishedCount = 0; QObject::connect(&futureWatcher, &QFutureWatcher::started, [&startedCount, &finishedCount](){ ++startedCount; QCOMPARE(startedCount, 1); QCOMPARE(finishedCount, 0); }); QObject::connect(&futureWatcher, &QFutureWatcher::finished, [&startedCount, &finishedCount](){ ++finishedCount; QCOMPARE(startedCount, 1); QCOMPARE(finishedCount, 1); }); futureWatcher.setFuture(QtConcurrent::run(sleeper)); futureWatcher.future().waitForFinished(); // waitForFinished() may unblock before asynchronous // started() and finished() signals are delivered to the main thread. // prosessEvents() should empty the pending queue. qApp->processEvents(); QCOMPARE(startedCount, 1); QCOMPARE(finishedCount, 1); } void mapSleeper(int &) { QTest::qSleep(100); } static QSet progressValues; static QSet progressTexts; static QMutex mutex; class ProgressObject : public QObject { Q_OBJECT public slots: void printProgress(int); void printText(const QString &text); void registerProgress(int); void registerText(const QString &text); }; void ProgressObject::printProgress(int progress) { qDebug() << "thread" << QThread::currentThread() << "reports progress" << progress; } void ProgressObject::printText(const QString &text) { qDebug() << "thread" << QThread::currentThread() << "reports progress text" << text; } void ProgressObject::registerProgress(int progress) { QTest::qSleep(1); progressValues.insert(progress); } void ProgressObject::registerText(const QString &text) { QTest::qSleep(1); progressTexts.insert(text); } QList createList(int listSize) { QList list; for (int i = 0; i < listSize; ++i) { list.append(i); } return list; } void tst_QFutureWatcher::progressValueChanged() { #ifdef PRINT qDebug() << "main thread" << QThread::currentThread(); #endif progressValues.clear(); const int listSize = 20; QList list = createList(listSize); QFutureWatcher futureWatcher; ProgressObject progressObject; QObject::connect(&futureWatcher, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); #ifdef PRINT QObject::connect(&futureWatcher, SIGNAL(progressValueChanged(int)), &progressObject, SLOT(printProgress(int)), Qt::DirectConnection ); #endif QObject::connect(&futureWatcher, SIGNAL(progressValueChanged(int)), &progressObject, SLOT(registerProgress(int))); futureWatcher.setFuture(QtConcurrent::map(list, mapSleeper)); QTestEventLoop::instance().enterLoop(5); QVERIFY(!QTestEventLoop::instance().timeout()); futureWatcher.disconnect(); QVERIFY(progressValues.contains(0)); QVERIFY(progressValues.contains(listSize)); } class CancelObject : public QObject { Q_OBJECT public: bool wasCanceled; CancelObject() : wasCanceled(false) {} public slots: void cancel(); }; void CancelObject::cancel() { #ifdef PRINT qDebug() << "thread" << QThread::currentThread() << "reports canceled"; #endif wasCanceled = true; } void tst_QFutureWatcher::canceled() { const int listSize = 20; QList list = createList(listSize); QFutureWatcher futureWatcher; QFuture future; CancelObject cancelObject; QObject::connect(&futureWatcher, SIGNAL(canceled()), &cancelObject, SLOT(cancel())); QObject::connect(&futureWatcher, SIGNAL(canceled()), &QTestEventLoop::instance(), SLOT(exitLoop()), Qt::QueuedConnection); future = QtConcurrent::map(list, mapSleeper); futureWatcher.setFuture(future); futureWatcher.cancel(); QTestEventLoop::instance().enterLoop(5); QVERIFY(!QTestEventLoop::instance().timeout()); QVERIFY(future.isCanceled()); QVERIFY(cancelObject.wasCanceled); futureWatcher.disconnect(); future.waitForFinished(); } class IntTask : public RunFunctionTask { public: void runFunctor() override { result = 10; } }; void tst_QFutureWatcher::resultAt() { QFutureWatcher futureWatcher; futureWatcher.setFuture((new IntTask())->start()); futureWatcher.waitForFinished(); QCOMPARE(futureWatcher.result(), 10); QCOMPARE(futureWatcher.resultAt(0), 10); } void tst_QFutureWatcher::resultReadyAt() { QFutureWatcher futureWatcher; QSignalSpy resultSpy(&futureWatcher, &QFutureWatcher::resultReadyAt); QFuture future = (new IntTask())->start(); futureWatcher.setFuture(future); QVERIFY(resultSpy.wait()); // Setting the future again should give us another signal. // (this is to prevent the race where the task associated // with the future finishes before setFuture is called.) futureWatcher.setFuture(QFuture()); futureWatcher.setFuture(future); QVERIFY(resultSpy.wait()); } class SignalSlotObject : public QObject { Q_OBJECT signals: void cancel(); public slots: void started() { qDebug() << "started called"; } void finished() { qDebug() << "finished called"; } void canceled() { qDebug() << "canceled called"; } #ifdef PRINT void resultReadyAt(int index) { qDebug() << "result" << index << "ready"; } #else void resultReadyAt(int) { } #endif void progressValueChanged(int progress) { qDebug() << "progress" << progress; } void progressRangeChanged(int min, int max) { qDebug() << "progress range" << min << max; } }; void tst_QFutureWatcher::futureSignals() { { QFutureInterface a; QFutureWatcher f; SignalSlotObject object; #ifdef PRINT connect(&f, SIGNAL(finished()), &object, SLOT(finished())); connect(&f, SIGNAL(progressValueChanged(int)), &object, SLOT(progressValueChanged(int))); #endif // must connect to resultReadyAt so that the watcher can detect the connection // (QSignalSpy does not trigger it.) connect(&f, SIGNAL(resultReadyAt(int)), &object, SLOT(resultReadyAt(int))); a.reportStarted(); QSignalSpy progressSpy(&f, &QFutureWatcher::progressValueChanged); QSignalSpy finishedSpy(&f, &QFutureWatcher::finished); QSignalSpy resultReadySpy(&f, &QFutureWatcher::resultReadyAt); QVERIFY(progressSpy.isValid()); QVERIFY(finishedSpy.isValid()); QVERIFY(resultReadySpy.isValid()); f.setFuture(a.future()); const int progress = 1; a.setProgressValue(progress); QTRY_COMPARE(progressSpy.count(), 2); QCOMPARE(progressSpy.takeFirst().at(0).toInt(), 0); QCOMPARE(progressSpy.takeFirst().at(0).toInt(), 1); const int result = 10; a.reportResult(&result); QVERIFY(resultReadySpy.wait()); QCOMPARE(resultReadySpy.count(), 1); a.reportFinished(&result); QTRY_COMPARE(resultReadySpy.count(), 2); QCOMPARE(resultReadySpy.takeFirst().at(0).toInt(), 0); // check the index QCOMPARE(resultReadySpy.takeFirst().at(0).toInt(), 1); QCOMPARE(finishedSpy.count(), 1); } } void tst_QFutureWatcher::watchFinishedFuture() { QFutureInterface iface; iface.reportStarted(); QFuture f = iface.future(); int value = 100; iface.reportFinished(&value); QFutureWatcher watcher; SignalSlotObject object; #ifdef PRINT connect(&watcher, SIGNAL(started()), &object, SLOT(started())); connect(&watcher, SIGNAL(canceled()), &object, SLOT(canceled())); connect(&watcher, SIGNAL(finished()), &object, SLOT(finished())); connect(&watcher, SIGNAL(progressValueChanged(int)), &object, SLOT(progressValueChanged(int))); connect(&watcher, SIGNAL(progressRangeChanged(int,int)), &object, SLOT(progressRangeChanged(int,int))); #endif connect(&watcher, SIGNAL(resultReadyAt(int)), &object, SLOT(resultReadyAt(int))); QSignalSpy startedSpy(&watcher, &QFutureWatcher::started); QSignalSpy finishedSpy(&watcher, &QFutureWatcher::finished); QSignalSpy resultReadySpy(&watcher, &QFutureWatcher::resultReadyAt); QSignalSpy canceledSpy(&watcher, &QFutureWatcher::canceled); QVERIFY(startedSpy.isValid()); QVERIFY(finishedSpy.isValid()); QVERIFY(resultReadySpy.isValid()); QVERIFY(canceledSpy.isValid()); watcher.setFuture(f); QVERIFY(finishedSpy.wait()); QCOMPARE(startedSpy.count(), 1); QCOMPARE(finishedSpy.count(), 1); QCOMPARE(resultReadySpy.count(), 1); QCOMPARE(canceledSpy.count(), 0); } void tst_QFutureWatcher::watchCanceledFuture() { QFuture f; QFutureWatcher watcher; SignalSlotObject object; #ifdef PRINT connect(&watcher, SIGNAL(started()), &object, SLOT(started())); connect(&watcher, SIGNAL(canceled()), &object, SLOT(canceled())); connect(&watcher, SIGNAL(finished()), &object, SLOT(finished())); connect(&watcher, SIGNAL(progressValueChanged(int)), &object, SLOT(progressValueChanged(int))); connect(&watcher, SIGNAL(progressRangeChanged(int,int)), &object, SLOT(progressRangeChanged(int,int))); #endif connect(&watcher, SIGNAL(resultReadyAt(int)), &object, SLOT(resultReadyAt(int))); QSignalSpy startedSpy(&watcher, &QFutureWatcher::started); QSignalSpy finishedSpy(&watcher, &QFutureWatcher::finished); QSignalSpy resultReadySpy(&watcher, &QFutureWatcher::resultReadyAt); QSignalSpy canceledSpy(&watcher, &QFutureWatcher::canceled); QVERIFY(startedSpy.isValid()); QVERIFY(finishedSpy.isValid()); QVERIFY(resultReadySpy.isValid()); QVERIFY(canceledSpy.isValid()); watcher.setFuture(f); QVERIFY(finishedSpy.wait()); QCOMPARE(startedSpy.count(), 1); QCOMPARE(finishedSpy.count(), 1); QCOMPARE(resultReadySpy.count(), 0); QCOMPARE(canceledSpy.count(), 1); } void tst_QFutureWatcher::disconnectRunningFuture() { QFutureInterface a; a.reportStarted(); QFuture f = a.future(); QFutureWatcher *watcher = new QFutureWatcher(); QSignalSpy finishedSpy(watcher, &QFutureWatcher::finished); QSignalSpy resultReadySpy(watcher, &QFutureWatcher::resultReadyAt); QVERIFY(finishedSpy.isValid()); QVERIFY(resultReadySpy.isValid()); watcher->setFuture(f); SignalSlotObject object; connect(watcher, SIGNAL(resultReadyAt(int)), &object, SLOT(resultReadyAt(int))); const int result = 10; a.reportResult(&result); QVERIFY(resultReadySpy.wait()); QCOMPARE(resultReadySpy.count(), 1); delete watcher; a.reportResult(&result); QTest::qWait(10); QCOMPARE(resultReadySpy.count(), 1); a.reportFinished(&result); QTest::qWait(10); QCOMPARE(finishedSpy.count(), 0); } const int maxProgress = 100000; class ProgressEmitterTask : public RunFunctionTask { public: void runFunctor() override { promise.setProgressRange(0, maxProgress); for (int p = 0; p <= maxProgress; ++p) promise.setProgressValue(p); } }; void tst_QFutureWatcher::tooMuchProgress() { progressValues.clear(); ProgressObject o; QFutureWatcher f; QObject::connect(&f, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); #ifdef PRINT QObject::connect(&f, SIGNAL(progressValueChanged(int)), &o, SLOT(printProgress(int))); #endif QObject::connect(&f, SIGNAL(progressValueChanged(int)), &o, SLOT(registerProgress(int))); f.setFuture((new ProgressEmitterTask())->start()); QTestEventLoop::instance().enterLoop(5); QVERIFY(!QTestEventLoop::instance().timeout()); QVERIFY(progressValues.contains(maxProgress)); } template class ProgressTextTask : public RunFunctionTask { public: void runFunctor() override { this->promise.setProgressValueAndText(1, QLatin1String("Foo 1")); while (this->promise.isProgressUpdateNeeded() == false) QTest::qSleep(1); this->promise.setProgressValueAndText(2, QLatin1String("Foo 2")); while (this->promise.isProgressUpdateNeeded() == false) QTest::qSleep(1); this->promise.setProgressValueAndText(3, QLatin1String("Foo 3")); while (this->promise.isProgressUpdateNeeded() == false) QTest::qSleep(1); this->promise.setProgressValueAndText(4, QLatin1String("Foo 4")); } }; void tst_QFutureWatcher::progressText() { { // instantiate API for T=int and T=void. ProgressTextTask a; ProgressTextTask b; } { progressValues.clear(); progressTexts.clear(); QFuture f = ((new ProgressTextTask())->start()); QFutureWatcher watcher; ProgressObject o; QObject::connect(&watcher, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); #ifdef PRINT QObject::connect(&watcher, SIGNAL(progressValueChanged(int)), &o, SLOT(printProgress(int))); QObject::connect(&watcher, SIGNAL(progressTextChanged(QString)), &o, SLOT(printText(QString))); #endif QObject::connect(&watcher, SIGNAL(progressValueChanged(int)), &o, SLOT(registerProgress(int))); QObject::connect(&watcher, SIGNAL(progressTextChanged(QString)), &o, SLOT(registerText(QString))); watcher.setFuture(f); QTestEventLoop::instance().enterLoop(5); QVERIFY(!QTestEventLoop::instance().timeout()); QCOMPARE(f.progressText(), QLatin1String("Foo 4")); QCOMPARE(f.progressValue(), 4); QVERIFY(progressValues.contains(1)); QVERIFY(progressValues.contains(2)); QVERIFY(progressValues.contains(3)); QVERIFY(progressValues.contains(4)); QVERIFY(progressTexts.contains(QLatin1String("Foo 1"))); QVERIFY(progressTexts.contains(QLatin1String("Foo 2"))); QVERIFY(progressTexts.contains(QLatin1String("Foo 3"))); QVERIFY(progressTexts.contains(QLatin1String("Foo 4"))); } } template void callInterface(T &obj) { obj.progressValue(); obj.progressMinimum(); obj.progressMaximum(); obj.progressText(); obj.isStarted(); obj.isFinished(); obj.isRunning(); obj.isCanceled(); obj.isSuspended(); obj.isSuspending(); obj.cancel(); obj.suspend(); obj.resume(); obj.toggleSuspended(); #if QT_DEPRECATED_SINCE(6, 0) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED obj.isPaused(); obj.pause(); obj.togglePaused(); QT_WARNING_POP #endif obj.waitForFinished(); const T& objConst = obj; objConst.progressValue(); objConst.progressMinimum(); objConst.progressMaximum(); objConst.progressText(); objConst.isStarted(); objConst.isFinished(); objConst.isRunning(); objConst.isCanceled(); objConst.isSuspending(); objConst.isSuspended(); #if QT_DEPRECATED_SINCE(6, 0) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED objConst.isPaused(); QT_WARNING_POP #endif } template void callInterface(const T &obj) { obj.result(); obj.resultAt(0); } // QFutureWatcher and QFuture has a similar interface. Test // that the functions we want ot have in both are actually // there. void tst_QFutureWatcher::sharedFutureInterface() { QFutureInterface iface; iface.reportStarted(); QFuture intFuture = iface.future(); int value = 0; iface.reportFinished(&value); QFuture voidFuture; QFutureWatcher intWatcher; intWatcher.setFuture(intFuture); QFutureWatcher voidWatcher; callInterface(intFuture); callInterface(voidFuture); callInterface(intWatcher); callInterface(voidWatcher); callInterface(intFuture); callInterface(intWatcher); } void tst_QFutureWatcher::changeFuture() { QFutureInterface iface; iface.reportStarted(); QFuture a = iface.future(); int value = 0; iface.reportFinished(&value); QFuture b; QFutureWatcher watcher; SignalSlotObject object; connect(&watcher, SIGNAL(resultReadyAt(int)), &object, SLOT(resultReadyAt(int))); QSignalSpy resultReadySpy(&watcher, &QFutureWatcher::resultReadyAt); QVERIFY(resultReadySpy.isValid()); watcher.setFuture(a); // Watch 'a' which will generate a resultReady event. watcher.setFuture(b); // But oh no! we're switching to another future QTest::qWait(10); // before the event gets delivered. QCOMPARE(resultReadySpy.count(), 0); watcher.setFuture(a); watcher.setFuture(b); watcher.setFuture(a); // setting it back gets us one event, not two. QVERIFY(resultReadySpy.wait()); QCOMPARE(resultReadySpy.count(), 1); } // Test that events aren't delivered from canceled futures void tst_QFutureWatcher::cancelEvents() { QFutureInterface iface; iface.reportStarted(); QFuture a = iface.future(); int value = 0; iface.reportFinished(&value); QFutureWatcher watcher; SignalSlotObject object; connect(&watcher, SIGNAL(resultReadyAt(int)), &object, SLOT(resultReadyAt(int))); QSignalSpy finishedSpy(&watcher, &QFutureWatcher::finished); QSignalSpy resultReadySpy(&watcher, &QFutureWatcher::resultReadyAt); QVERIFY(finishedSpy.isValid()); QVERIFY(resultReadySpy.isValid()); watcher.setFuture(a); watcher.cancel(); QVERIFY(finishedSpy.wait()); QCOMPARE(resultReadySpy.count(), 0); } #if QT_DEPRECATED_SINCE(6, 0) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED // Tests that events from paused futures are saved and // delivered on resume. void tst_QFutureWatcher::pauseEvents() { { QFutureInterface iface; iface.reportStarted(); QFutureWatcher watcher; SignalSlotObject object; connect(&watcher, SIGNAL(resultReadyAt(int)), &object, SLOT(resultReadyAt(int))); QSignalSpy resultReadySpy(&watcher, &QFutureWatcher::resultReadyAt); QVERIFY(resultReadySpy.isValid()); QSignalSpy pauseSpy(&watcher, &QFutureWatcher::paused); QVERIFY(pauseSpy.isValid()); watcher.setFuture(iface.future()); watcher.pause(); QTRY_COMPARE(pauseSpy.count(), 1); int value = 0; iface.reportFinished(&value); // A result is reported, although the watcher is paused. // The corresponding event should be also reported. QTRY_COMPARE(resultReadySpy.count(), 1); watcher.resume(); } { QFutureInterface iface; iface.reportStarted(); QFuture a = iface.future(); QFutureWatcher watcher; SignalSlotObject object; connect(&watcher, SIGNAL(resultReadyAt(int)), &object, SLOT(resultReadyAt(int))); QSignalSpy resultReadySpy(&watcher, &QFutureWatcher::resultReadyAt); QVERIFY(resultReadySpy.isValid()); watcher.setFuture(a); a.pause(); int value = 0; iface.reportFinished(&value); QFuture b; watcher.setFuture(b); // If we watch b instead, resuming a a.resume(); // should give us no results. QTest::qWait(10); QCOMPARE(resultReadySpy.count(), 0); } } void tst_QFutureWatcher::pausedSuspendedOrder() { QFutureInterface iface; iface.reportStarted(); QFutureWatcher watcher; QSignalSpy pausedSpy(&watcher, &QFutureWatcher::paused); QVERIFY(pausedSpy.isValid()); QSignalSpy suspendedSpy(&watcher, &QFutureWatcher::suspended); QVERIFY(suspendedSpy.isValid()); bool pausedBeforeSuspended = false; bool notSuspendedBeforePaused = false; connect(&watcher, &QFutureWatcher::paused, [&] { notSuspendedBeforePaused = (suspendedSpy.count() == 0); }); connect(&watcher, &QFutureWatcher::suspended, [&] { pausedBeforeSuspended = (pausedSpy.count() == 1); }); watcher.setFuture(iface.future()); iface.reportSuspended(); // Make sure reportPaused() is ignored if the state is not paused pausedSpy.wait(100); QCOMPARE(pausedSpy.count(), 0); QCOMPARE(suspendedSpy.count(), 0); iface.setPaused(true); iface.reportSuspended(); QTRY_COMPARE(suspendedSpy.count(), 1); QCOMPARE(pausedSpy.count(), 1); QVERIFY(notSuspendedBeforePaused); QVERIFY(pausedBeforeSuspended); iface.reportFinished(); } QT_WARNING_POP #endif // QT_DEPRECATED_SINCE(6, 0) // Tests that events from suspended futures are saved and // delivered on resume. void tst_QFutureWatcher::suspendEvents() { { QFutureInterface iface; iface.reportStarted(); QFutureWatcher watcher; SignalSlotObject object; connect(&watcher, &QFutureWatcher::resultReadyAt, &object, &SignalSlotObject::resultReadyAt); QSignalSpy resultReadySpy(&watcher, &QFutureWatcher::resultReadyAt); QVERIFY(resultReadySpy.isValid()); QSignalSpy suspendingSpy(&watcher, &QFutureWatcher::suspending); QVERIFY(suspendingSpy.isValid()); watcher.setFuture(iface.future()); watcher.suspend(); QTRY_COMPARE(suspendingSpy.count(), 1); int value = 0; iface.reportFinished(&value); // A result is reported, although the watcher is paused. // The corresponding event should be also reported. QTRY_COMPARE(resultReadySpy.count(), 1); watcher.resume(); } { QFutureInterface iface; iface.reportStarted(); QFuture a = iface.future(); QFutureWatcher watcher; SignalSlotObject object; connect(&watcher, &QFutureWatcher::resultReadyAt, &object, &SignalSlotObject::resultReadyAt); QSignalSpy resultReadySpy(&watcher, &QFutureWatcher::resultReadyAt); QVERIFY(resultReadySpy.isValid()); watcher.setFuture(a); a.suspend(); int value = 0; iface.reportFinished(&value); QFuture b; watcher.setFuture(b); // If we watch b instead, resuming a a.resume(); // should give us no results. QTest::qWait(10); QCOMPARE(resultReadySpy.count(), 0); } } void tst_QFutureWatcher::suspended() { QFutureWatcher watcher; QSignalSpy resultReadySpy(&watcher, &QFutureWatcher::resultReadyAt); #if QT_DEPRECATED_SINCE(6, 0) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED QSignalSpy pausedSpy(&watcher, &QFutureWatcher::paused); QT_WARNING_POP #endif QSignalSpy suspendingSpy(&watcher, &QFutureWatcher::suspending); QSignalSpy suspendedSpy(&watcher, &QFutureWatcher::suspended); QSignalSpy finishedSpy(&watcher, &QFutureWatcher::finished); const int numValues = 25; std::vector values(numValues, 0); std::atomic_int count = 0; QThreadPool pool; pool.setMaxThreadCount(3); QFuture future = QtConcurrent::mapped(&pool, values, [&](int value) { ++count; // Sleep, to make sure not all threads will start at once. QThread::msleep(50); return value; }); watcher.setFuture(future); // Allow some threads to start before suspending. QThread::msleep(200); watcher.suspend(); watcher.suspend(); QTRY_COMPARE(suspendedSpy.count(), 1); // suspended() should be emitted only once QCOMPARE(suspendingSpy.count(), 2); // suspending() is emitted as many times as requested #if QT_DEPRECATED_SINCE(6, 0) QCOMPARE(pausedSpy.count(), 2); // paused() is emitted as many times as requested #endif // Make sure QFutureWatcher::resultReadyAt() is emitted only for already started threads. const auto resultReadyAfterPaused = resultReadySpy.count(); QCOMPARE(resultReadyAfterPaused, count); // Make sure no more results are reported before resuming. QThread::msleep(200); QCOMPARE(resultReadyAfterPaused, resultReadySpy.count()); resultReadySpy.clear(); watcher.resume(); QTRY_COMPARE(finishedSpy.count(), 1); // Make sure that no more suspended() signals have been emitted. QCOMPARE(suspendedSpy.count(), 1); // Make sure the rest of results were reported after resume. QCOMPARE(resultReadySpy.count(), numValues - resultReadyAfterPaused); } void tst_QFutureWatcher::suspendedEventsOrder() { QFutureInterface iface; iface.reportStarted(); QFutureWatcher watcher; QSignalSpy suspendingSpy(&watcher, &QFutureWatcher::suspending); QVERIFY(suspendingSpy.isValid()); QSignalSpy suspendedSpy(&watcher, &QFutureWatcher::suspended); QVERIFY(suspendedSpy.isValid()); bool suspendingBeforeSuspended = false; bool notSuspendedBeforeSuspending = false; connect(&watcher, &QFutureWatcher::suspending, [&] { notSuspendedBeforeSuspending = (suspendedSpy.count() == 0); }); connect(&watcher, &QFutureWatcher::suspended, [&] { suspendingBeforeSuspended = (suspendingSpy.count() == 1); }); watcher.setFuture(iface.future()); iface.reportSuspended(); // Make sure reportPaused() is ignored if the state is not paused suspendingSpy.wait(100); QCOMPARE(suspendingSpy.count(), 0); QCOMPARE(suspendedSpy.count(), 0); iface.setSuspended(true); iface.reportSuspended(); QTRY_COMPARE(suspendedSpy.count(), 1); QCOMPARE(suspendingSpy.count(), 1); QVERIFY(notSuspendedBeforeSuspending); QVERIFY(suspendingBeforeSuspended); iface.reportFinished(); } // Test that the finished state for the watcher gets // set when the finished event is delivered. // This means it will lag the finished state for the future, // but makes it more useful. void tst_QFutureWatcher::finishedState() { QFutureInterface iface; iface.reportStarted(); QFuture future = iface.future(); QFutureWatcher watcher; QSignalSpy startedSpy(&watcher, &QFutureWatcher::started); QSignalSpy finishedSpy(&watcher, &QFutureWatcher::finished); watcher.setFuture(future); QVERIFY(startedSpy.wait()); iface.reportFinished(); QVERIFY(future.isFinished()); QVERIFY(!watcher.isFinished()); QVERIFY(finishedSpy.wait()); QVERIFY(watcher.isFinished()); } /* Verify that throttling kicks in if you report a lot of results, and that it clears when the result events are processed. */ void tst_QFutureWatcher::throttling() { QFutureInterface iface; iface.reportStarted(); QFuture future = iface.future(); QFutureWatcher watcher; QSignalSpy resultSpy(&watcher, &QFutureWatcher::resultReadyAt); watcher.setFuture(future); QVERIFY(!iface.isThrottled()); const int resultCount = 1000; for (int i = 0; i < resultCount; ++i) { int result = 0; iface.reportResult(result); } QVERIFY(iface.isThrottled()); QTRY_COMPARE(resultSpy.count(), resultCount); // Process the results QVERIFY(!iface.isThrottled()); iface.reportFinished(); } int mapper(const int &i) { return i; } class ResultReadyTester : public QObject { Q_OBJECT public: ResultReadyTester(QFutureWatcher *watcher) :m_watcher(watcher), filter(false), ok(true), count(0) { } public slots: void resultReadyAt(int index) { ++count; if (m_watcher->future().isResultReadyAt(index) == false) ok = false; if (!filter && m_watcher->future().resultAt(index) != index) ok = false; if (filter && m_watcher->future().resultAt(index) != index * 2 + 1) ok = false; } public: QFutureWatcher *m_watcher; bool filter; bool ok; int count; }; void tst_QFutureWatcher::incrementalMapResults() { QFutureWatcher watcher; SignalSlotObject object; #ifdef PRINT connect(&watcher, SIGNAL(finished()), &object, SLOT(finished())); connect(&watcher, SIGNAL(progressValueChanged(int)), &object, SLOT(progressValueChanged(int))); connect(&watcher, SIGNAL(resultReadyAt(int)), &object, SLOT(resultReadyAt(int))); #endif QObject::connect(&watcher, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); ResultReadyTester resultReadyTester(&watcher); connect(&watcher, SIGNAL(resultReadyAt(int)), &resultReadyTester, SLOT(resultReadyAt(int))); const int count = 10000; QList ints; for (int i = 0; i < count; ++i) ints << i; QFuture future = QtConcurrent::mapped(ints, mapper); watcher.setFuture(future); QTestEventLoop::instance().enterLoop(10); QVERIFY(!QTestEventLoop::instance().timeout()); QCOMPARE(resultReadyTester.count, count); QVERIFY(resultReadyTester.ok); QVERIFY(watcher.isFinished()); future.waitForFinished(); } bool filterer(int i) { return (i % 2); } void tst_QFutureWatcher::incrementalFilterResults() { QFutureWatcher watcher; SignalSlotObject object; #ifdef PRINT connect(&watcher, SIGNAL(finished()), &object, SLOT(finished())); connect(&watcher, SIGNAL(progressValueChanged(int)), &object, SLOT(progressValueChanged(int))); connect(&watcher, SIGNAL(resultReadyAt(int)), &object, SLOT(resultReadyAt(int))); #endif QObject::connect(&watcher, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); ResultReadyTester resultReadyTester(&watcher); resultReadyTester.filter = true; connect(&watcher, SIGNAL(resultReadyAt(int)), &resultReadyTester, SLOT(resultReadyAt(int))); const int count = 10000; QList ints; for (int i = 0; i < count; ++i) ints << i; QFuture future = QtConcurrent::filtered(ints, filterer); watcher.setFuture(future); QTestEventLoop::instance().enterLoop(10); QVERIFY(!QTestEventLoop::instance().timeout()); QCOMPARE(resultReadyTester.count, count / 2); QVERIFY(resultReadyTester.ok); QVERIFY(watcher.isFinished()); future.waitForFinished(); } void tst_QFutureWatcher::qfutureSynchronizer() { int taskCount = 1000; QElapsedTimer t; t.start(); { QFutureSynchronizer sync; sync.setCancelOnWait(true); for (int i = 0; i < taskCount; ++i) { sync.addFuture(run(sleeper)); } } // Test that we're not running each task. QVERIFY(t.elapsed() < taskCount * 10); } class DummyObject : public QObject { Q_OBJECT public slots: void dummySlot() {} public: static void function(QMutex *m) { QMutexLocker lock(m); } }; void tst_QFutureWatcher::warnRace() { #ifndef Q_OS_MAC //I don't know why it is not working on mac #ifndef QT_NO_DEBUG QTest::ignoreMessage(QtWarningMsg, "QFutureWatcher::connect: connecting after calling setFuture() is likely to produce race"); #endif #endif QFutureWatcher watcher; DummyObject object; QMutex mutex; mutex.lock(); QFuture future = QtConcurrent::run(DummyObject::function, &mutex); watcher.setFuture(future); QTRY_VERIFY(future.isStarted()); connect(&watcher, SIGNAL(finished()), &object, SLOT(dummySlot())); mutex.unlock(); future.waitForFinished(); } void tst_QFutureWatcher::matchFlags() { /* Regression test: expect a default watcher to be in the same state as a * default future. */ QFutureWatcher watcher; QFuture future; QCOMPARE(watcher.isStarted(), future.isStarted()); QCOMPARE(watcher.isCanceled(), future.isCanceled()); QCOMPARE(watcher.isFinished(), future.isFinished()); } QTEST_MAIN(tst_QFutureWatcher) #include "tst_qfuturewatcher.moc"