/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** 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. ** ** 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" #ifndef QT_NO_CONCURRENT_TEST using namespace QtConcurrent; class tst_threadengine: public QObject { Q_OBJECT public: void threadCount(); private slots: void runDirectly(); void result(); void runThroughStarter(); void cancel(); void throttle(); void multipleResults(); void stresstest(); void cancelQueuedSlowUser(); #ifndef QT_NO_EXCEPTIONS void exceptions(); #endif }; class PrintUser : public ThreadEngine { public: ThreadFunctionResult threadFunction() { QTest::qSleep(50); QTest::qSleep(100); return ThreadFinished; } }; void tst_threadengine::runDirectly() { { PrintUser engine; engine.startSingleThreaded(); engine.startBlocking(); } { PrintUser *engine = new PrintUser(); QFuture f = engine->startAsynchronously(); f.waitForFinished(); } } class StringResultUser : public ThreadEngine { public: typedef QString ResultType; StringResultUser() : done(false) { } bool shouldStartThread() { return !done; } ThreadFunctionResult threadFunction() { done = true; return ThreadFinished; } QString *result() { foo = "Foo"; return &foo; } QString foo; bool done; }; void tst_threadengine::result() { StringResultUser engine; QCOMPARE(*engine.startBlocking(), QString("Foo")); } class VoidResultUser : public ThreadEngine { public: bool shouldStartThread() { return !done; } ThreadFunctionResult threadFunction() { done = true; return ThreadFinished; } void *result() { return 0; } bool done; }; void tst_threadengine::runThroughStarter() { { ThreadEngineStarter starter = startThreadEngine(new StringResultUser()); QFuture f = starter.startAsynchronously(); QCOMPARE(f.result(), QString("Foo")); } { ThreadEngineStarter starter = startThreadEngine(new StringResultUser()); QString str = starter.startBlocking(); QCOMPARE(str, QString("Foo")); } } class CancelUser : public ThreadEngine { public: void *result() { return 0; } ThreadFunctionResult threadFunction() { while (this->isCanceled() == false) { QTest::qSleep(10); } return ThreadFinished; } }; void tst_threadengine::cancel() { { CancelUser *engine = new CancelUser(); QFuture f = engine->startAsynchronously(); f.cancel(); f.waitForFinished(); } { CancelUser *engine = new CancelUser(); QFuture f = engine->startAsynchronously(); QTest::qSleep(10); f.cancel(); f.waitForFinished(); } } QAtomicInt count; class ThrottleAlwaysUser : public ThreadEngine { public: ThrottleAlwaysUser() { count = initialCount = 100; finishing = false; } bool shouldStartThread() { return !finishing; } ThreadFunctionResult threadFunction() { forever { const int local = count; if (local == 0) { finishing = true; return ThreadFinished; } if (count.testAndSetOrdered(local, local - 1)) break; } return ThrottleThread; } bool finishing; int initialCount; }; // Test that a user task with a thread function that always // want to be throttled still completes. The thread engine // should make keep one thread running at all times. void tst_threadengine::throttle() { const int repeats = 10; for (int i = 0; i < repeats; ++i) { QFuture f = (new ThrottleAlwaysUser())->startAsynchronously(); f.waitForFinished(); QCOMPARE(int(count), 0); } for (int i = 0; i < repeats; ++i) { ThrottleAlwaysUser t; t.startBlocking(); QCOMPARE(int(count), 0); } } QSet threads; QMutex mutex; class ThreadCountUser : public ThreadEngine { public: ThreadCountUser(bool finishImmediately = false) { threads.clear(); finishing = finishImmediately; } bool shouldStartThread() { return !finishing; } ThreadFunctionResult threadFunction() { { QMutexLocker lock(&mutex); threads.insert(QThread::currentThread()); } QTest::qSleep(10); finishing = true; return ThreadFinished; } bool finishing; }; void tst_threadengine::threadCount() { const int repeats = 10; for (int i = 0; i < repeats; ++i) { ThreadCountUser t; t.startBlocking(); QCOMPARE(threads.count(), QThreadPool::globalInstance()->maxThreadCount() + 1); // +1 for the main thread. (new ThreadCountUser())->startAsynchronously().waitForFinished(); QCOMPARE(threads.count(), QThreadPool::globalInstance()->maxThreadCount()); } // Set the finish flag immediately, this should give us one thread only. for (int i = 0; i < repeats; ++i) { ThreadCountUser t(true /*finishImmediately*/); t.startBlocking(); QCOMPARE(threads.count(), 1); (new ThreadCountUser(true /*finishImmediately*/))->startAsynchronously().waitForFinished(); QCOMPARE(threads.count(), 1); } } class MultipleResultsUser : public ThreadEngine { public: bool shouldStartThread() { return false; } ThreadFunctionResult threadFunction() { for (int i = 0; i < 10; ++i) this->reportResult(&i); return ThreadFinished; } }; void tst_threadengine::multipleResults() { MultipleResultsUser *engine = new MultipleResultsUser(); QFuture f = engine->startAsynchronously(); QCOMPARE(f.results().count() , 10); QCOMPARE(f.resultAt(0), 0); QCOMPARE(f.resultAt(5), 5); QCOMPARE(f.resultAt(9), 9); f.waitForFinished(); } class NoThreadsUser : public ThreadEngine { public: bool shouldStartThread() { return false; } ThreadFunctionResult threadFunction() { return ThreadFinished; } void *result() { return 0; } }; void tst_threadengine::stresstest() { const int times = 20000; for (int i = 0; i < times; ++i) { VoidResultUser *engine = new VoidResultUser(); engine->startAsynchronously().waitForFinished(); } for (int i = 0; i < times; ++i) { VoidResultUser *engine = new VoidResultUser(); engine->startAsynchronously(); } for (int i = 0; i < times; ++i) { VoidResultUser *engine = new VoidResultUser(); engine->startAsynchronously().waitForFinished(); } } const int sleepTime = 20; class SlowUser : public ThreadEngine { public: bool shouldStartThread() { return false; } ThreadFunctionResult threadFunction() { QTest::qSleep(sleepTime); return ThreadFinished; } }; void tst_threadengine::cancelQueuedSlowUser() { const int times = 100; QTime t; t.start(); { QList > futures; for (int i = 0; i < times; ++i) { SlowUser *engine = new SlowUser(); futures.append(engine->startAsynchronously()); } foreach(QFuture future, futures) future.cancel(); } QVERIFY(t.elapsed() < (sleepTime * times) / 2); } #ifndef QT_NO_EXCEPTIONS class QtConcurrentExceptionThrower : public ThreadEngine { public: QtConcurrentExceptionThrower(QThread *blockThread = 0) { this->blockThread = blockThread; } ThreadFunctionResult threadFunction() { QTest::qSleep(50); throw QtConcurrent::Exception(); return ThreadFinished; } QThread *blockThread; }; class UnrelatedExceptionThrower : public ThreadEngine { public: UnrelatedExceptionThrower(QThread *blockThread = 0) { this->blockThread = blockThread; } ThreadFunctionResult threadFunction() { QTest::qSleep(50); throw int(); return ThreadFinished; } QThread *blockThread; }; void tst_threadengine::exceptions() { // Asynchronous mode: { bool caught = false; try { QtConcurrentExceptionThrower *e = new QtConcurrentExceptionThrower(); QFuture f = e->startAsynchronously(); f.waitForFinished(); } catch (Exception &e) { caught = true; } if (!caught) QFAIL("did not get exception"); } // Blocking mode: // test throwing the exception from a worker thread. { bool caught = false; try { QtConcurrentExceptionThrower e(QThread::currentThread()); e.startBlocking(); } catch (Exception &e) { caught = true; } if (!caught) QFAIL("did not get exception"); } // test throwing the exception from the main thread (different code path) { bool caught = false; try { QtConcurrentExceptionThrower e(0); e.startBlocking(); } catch (Exception &e) { caught = true; } if (!caught) QFAIL("did not get exception"); } // Asynchronous mode: { bool caught = false; try { UnrelatedExceptionThrower *e = new UnrelatedExceptionThrower(); QFuture f = e->startAsynchronously(); f.waitForFinished(); } catch (QtConcurrent::UnhandledException &e) { caught = true; } if (!caught) QFAIL("did not get exception"); } // Blocking mode: // test throwing the exception from a worker thread. { bool caught = false; try { UnrelatedExceptionThrower e(QThread::currentThread()); e.startBlocking(); } catch (QtConcurrent::UnhandledException &e) { caught = true; } if (!caught) QFAIL("did not get exception"); } // test throwing the exception from the main thread (different code path) { bool caught = false; try { UnrelatedExceptionThrower e(0); e.startBlocking(); } catch (QtConcurrent::UnhandledException &e) { caught = true; } if (!caught) QFAIL("did not get exception"); } } #endif QTEST_MAIN(tst_threadengine) #include "tst_qtconcurrentthreadengine.moc" #else QTEST_NOOP_MAIN #endif