/**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Commercial License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include "../qfuture/versioncheck.h" #include #include #include #ifndef QT_NO_CONCURRENT_TEST #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 toMuchProgress(); void progressText(); void sharedFutureInterface(); void changeFuture(); void cancelEvents(); void pauseEvents(); void finishedState(); void throttling(); void incrementalMapResults(); void incrementalFilterResults(); void qfutureSynchornizer(); }; QTEST_MAIN(tst_QFutureWatcher) void sleeper() { QTest::qSleep(100); } void tst_QFutureWatcher::startFinish() { QFutureWatcher futureWatcher; QSignalSpy started(&futureWatcher, SIGNAL(started())); QSignalSpy finished(&futureWatcher, SIGNAL(finished())); futureWatcher.setFuture(QtConcurrent::run(sleeper)); QTest::qWait(10); // spin the event loop to deliver queued signals. QCOMPARE(started.count(), 1); QCOMPARE(finished.count(), 0); futureWatcher.future().waitForFinished(); QTest::qWait(10); QCOMPARE(started.count(), 1); QCOMPARE(finished.count(), 1); } void mapSleeper(int &) { QTest::qSleep(100); } QSet progressValues; QSet progressTexts; 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() { 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; QObject::connect(&futureWatcher, SIGNAL(resultReadyAt(int)), &QTestEventLoop::instance(), SLOT(exitLoop()), Qt::QueuedConnection); QFuture future = (new IntTask())->start(); futureWatcher.setFuture(future); QTestEventLoop::instance().enterLoop(1); QVERIFY(!QTestEventLoop::instance().timeout()); // 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); QTestEventLoop::instance().enterLoop(1); QVERIFY(!QTestEventLoop::instance().timeout()); } 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(); f.setFuture(a.future()); QSignalSpy progressSpy(&f, SIGNAL(progressValueChanged(int))); const int progress = 1; a.setProgressValue(progress); QTest::qWait(10); QCOMPARE(progressSpy.count(), 2); QCOMPARE(progressSpy.takeFirst().at(0).toInt(), 0); QCOMPARE(progressSpy.takeFirst().at(0).toInt(), 1); QSignalSpy finishedSpy(&f, SIGNAL(finished())); QSignalSpy resultReadySpy(&f, SIGNAL(resultReadyAt(int))); const int result = 10; a.reportResult(&result); QTest::qWait(10); QCOMPARE(resultReadySpy.count(), 1); a.reportFinished(&result); QTest::qWait(10); QCOMPARE(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, SIGNAL(started())); QSignalSpy finishedSpy(&watcher, SIGNAL(finished())); QSignalSpy resultReadySpy(&watcher, SIGNAL(resultReadyAt(int))); QSignalSpy canceledSpy(&watcher, SIGNAL(canceled())); watcher.setFuture(f); QTest::qWait(10); 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, SIGNAL(started())); QSignalSpy finishedSpy(&watcher, SIGNAL(finished())); QSignalSpy resultReadySpy(&watcher, SIGNAL(resultReadyAt(int))); QSignalSpy canceledSpy(&watcher, SIGNAL(canceled())); watcher.setFuture(f); QTest::qWait(10); 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(); watcher->setFuture(f); SignalSlotObject object; connect(watcher, SIGNAL(resultReadyAt(int)), &object, SLOT(resultReadyAt(int))); QSignalSpy finishedSpy(watcher, SIGNAL(finished())); QSignalSpy resultReadySpy(watcher, SIGNAL(resultReadyAt(int))); const int result = 10; a.reportResult(&result); QTest::qWait(10); 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() { setProgressRange(0, maxProgress); for (int p = 0; p <= maxProgress; ++p) setProgressValue(p); } }; void tst_QFutureWatcher::toMuchProgress() { progressValues.clear(); ProgressObject o; QFutureWatcher f; f.setFuture((new ProgressEmitterTask())->start()); 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))); QTestEventLoop::instance().enterLoop(5); QVERIFY(!QTestEventLoop::instance().timeout()); QVERIFY(progressValues.contains(maxProgress)); } template class ProgressTextTask : public RunFunctionTask { public: void runFunctor() { while (this->isProgressUpdateNeeded() == false) QTest::qSleep(1); this->setProgressValueAndText(1, QLatin1String("Foo 1")); while (this->isProgressUpdateNeeded() == false) QTest::qSleep(1); this->setProgressValueAndText(2, QLatin1String("Foo 2")); while (this->isProgressUpdateNeeded() == false) QTest::qSleep(1); this->setProgressValueAndText(3, QLatin1String("Foo 3")); } }; 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(const QString &)), &o, SLOT(printText(const QString &))); #endif QObject::connect(&watcher, SIGNAL(progressValueChanged(int)), &o, SLOT(registerProgress(int))); QObject::connect(&watcher, SIGNAL(progressTextChanged(const QString &)), &o, SLOT(registerText(const QString &))); watcher.setFuture(f); QTestEventLoop::instance().enterLoop(5); QVERIFY(!QTestEventLoop::instance().timeout()); QCOMPARE(f.progressText(), QLatin1String("Foo 3")); QCOMPARE(f.progressValue(), 3); QVERIFY(progressValues.contains(1)); QVERIFY(progressValues.contains(2)); QVERIFY(progressValues.contains(3)); QVERIFY(progressTexts.contains(QLatin1String("Foo 1"))); QVERIFY(progressTexts.contains(QLatin1String("Foo 2"))); QVERIFY(progressTexts.contains(QLatin1String("Foo 3"))); } } template void callInterface(T &obj) { obj.progressValue(); obj.progressMinimum(); obj.progressMaximum(); obj.progressText(); obj.isStarted(); obj.isFinished(); obj.isRunning(); obj.isCanceled(); obj.isPaused(); obj.cancel(); obj.pause(); obj.resume(); obj.togglePaused(); obj.waitForFinished(); const T& objConst = obj; objConst.progressValue(); objConst.progressMinimum(); objConst.progressMaximum(); objConst.progressText(); objConst.isStarted(); objConst.isFinished(); objConst.isRunning(); objConst.isCanceled(); objConst.isPaused(); } 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, SIGNAL(resultReadyAt(int))); watcher.setFuture(a); // Watch 'a' which will genere 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. QTest::qWait(10); 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 resultReadySpy(&watcher, SIGNAL(resultReadyAt(int))); watcher.setFuture(a); watcher.cancel(); QTest::qWait(10); QCOMPARE(resultReadySpy.count(), 0); } // Tests that events from paused futures are saved and // delivered on resume. void tst_QFutureWatcher::pauseEvents() { { 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 resultReadySpy(&watcher, SIGNAL(resultReadyAt(int))); watcher.setFuture(a); watcher.pause(); QTest::qWait(10); QCOMPARE(resultReadySpy.count(), 0); watcher.resume(); QTest::qWait(10); QCOMPARE(resultReadySpy.count(), 1); } { 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 resultReadySpy(&watcher, SIGNAL(resultReadyAt(int))); watcher.setFuture(a); a.pause(); 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); } } // 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; watcher.setFuture(future); QTest::qWait(10); iface.reportFinished(); QVERIFY(future.isFinished()); QVERIFY(watcher.isFinished() == false); QTest::qWait(10); 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; watcher.setFuture(future); QVERIFY(iface.isThrottled() == false); for (int i = 0; i < 1000; ++i) { int result = 0; iface.reportResult(result); } QVERIFY(iface.isThrottled() == true); QTest::qWait(100); // process events. QVERIFY(iface.isThrottled() == false); 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::qfutureSynchornizer() { int taskCount = 1000; QTime 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); } #include "tst_qfuturewatcher.moc" #else QTEST_NOOP_MAIN #endif