From 82e715b2770258fa2c536aeae1f46c7fccdcdecf Mon Sep 17 00:00:00 2001 From: Holger Ihrig Date: Fri, 26 Aug 2011 12:56:14 +0200 Subject: Moving relevant tests to corelib/thread Task-number: QTBUG-21066 Change-Id: Ia16fa8961f1a73f4da6709197b5dd9929c16583f Reviewed-on: http://codereview.qt.nokia.com/3663 Reviewed-by: Qt Sanity Bot Reviewed-by: Rohan McGovern Reviewed-by: Jason McDonald --- .../thread/qwaitcondition/tst_qwaitcondition.cpp | 845 +++++++++++++++++++++ 1 file changed, 845 insertions(+) create mode 100644 tests/auto/corelib/thread/qwaitcondition/tst_qwaitcondition.cpp (limited to 'tests/auto/corelib/thread/qwaitcondition/tst_qwaitcondition.cpp') diff --git a/tests/auto/corelib/thread/qwaitcondition/tst_qwaitcondition.cpp b/tests/auto/corelib/thread/qwaitcondition/tst_qwaitcondition.cpp new file mode 100644 index 0000000000..6545df9189 --- /dev/null +++ b/tests/auto/corelib/thread/qwaitcondition/tst_qwaitcondition.cpp @@ -0,0 +1,845 @@ +/**************************************************************************** +** +** Copyright (C) 2011 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$ +** GNU Lesser General Public License Usage +** 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include + +#include +#include +#include +#include + +#if defined(Q_OS_SYMBIAN) +// Symbian Open C has a bug that causes very short waits to fail sometimes +#define COND_WAIT_TIME 50 +#else +#define COND_WAIT_TIME 1 +#endif + + +//TESTED_CLASS= +//TESTED_FILES= + +class tst_QWaitCondition : public QObject +{ + Q_OBJECT + +public: + tst_QWaitCondition(); + +private slots: + void wait_QMutex(); + void wait_QReadWriteLock(); + void wakeOne(); + void wakeAll(); + void wait_RaceCondition(); +}; + +static const int iterations = 10; + +// Note: some tests rely on ThreadCount being multiple of 2 +#if defined(Q_OS_SOLARIS) || ( defined(Q_OS_LINUX) && defined(QT_ARCH_ARMV6) ) +static const int ThreadCount = 4; +#else +static const int ThreadCount = 10; +#endif + +tst_QWaitCondition::tst_QWaitCondition() + +{ +} + +class wait_QMutex_Thread_1 : public QThread +{ +public: + QMutex mutex; + QWaitCondition cond; + + inline wait_QMutex_Thread_1() + { } + + void run() + { + mutex.lock(); + cond.wakeOne(); + cond.wait(&mutex); + mutex.unlock(); + } +}; + +class wait_QMutex_Thread_2 : public QThread +{ +public: + QWaitCondition started; + + QMutex *mutex; + QWaitCondition *cond; + + inline wait_QMutex_Thread_2() + : mutex(0), cond(0) + { } + + void run() + { + mutex->lock(); + started.wakeOne(); + cond->wait(mutex); + mutex->unlock(); + } +}; + +class wait_QReadWriteLock_Thread_1 : public QThread +{ +public: + QReadWriteLock readWriteLock; + QWaitCondition cond; + + inline wait_QReadWriteLock_Thread_1() + { } + + void run() + { + readWriteLock.lockForWrite(); + cond.wakeOne(); + cond.wait(&readWriteLock); + readWriteLock.unlock(); + } +}; + +class wait_QReadWriteLock_Thread_2 : public QThread +{ +public: + QWaitCondition started; + + QReadWriteLock *readWriteLock; + QWaitCondition *cond; + + inline wait_QReadWriteLock_Thread_2() + : readWriteLock(0), cond(0) + { } + + void run() + { + readWriteLock->lockForRead(); + started.wakeOne(); + cond->wait(readWriteLock); + readWriteLock->unlock(); + } +}; + +void tst_QWaitCondition::wait_QMutex() +{ + int x; + for (int i = 0; i < iterations; ++i) { + { + QMutex mutex; + QWaitCondition cond; + + mutex.lock(); + + cond.wakeOne(); + QVERIFY(!cond.wait(&mutex, 1)); + + cond.wakeAll(); + QVERIFY(!cond.wait(&mutex, 1)); + + mutex.unlock(); + } + + { + // test multiple threads waiting on separate wait conditions + wait_QMutex_Thread_1 thread[ThreadCount]; + + for (x = 0; x < ThreadCount; ++x) { + thread[x].mutex.lock(); + thread[x].start(); + // wait for thread to start + QVERIFY(thread[x].cond.wait(&thread[x].mutex, 1000)); + thread[x].mutex.unlock(); + } + + for (x = 0; x < ThreadCount; ++x) { + QVERIFY(thread[x].isRunning()); + QVERIFY(!thread[x].isFinished()); + } + + for (x = 0; x < ThreadCount; ++x) { + thread[x].mutex.lock(); + thread[x].cond.wakeOne(); + thread[x].mutex.unlock(); + } + + for (x = 0; x < ThreadCount; ++x) { + QVERIFY(thread[x].wait(1000)); + } + } + + { + // test multiple threads waiting on a wait condition + QMutex mutex; + QWaitCondition cond1, cond2; + wait_QMutex_Thread_2 thread[ThreadCount]; + + mutex.lock(); + for (x = 0; x < ThreadCount; ++x) { + thread[x].mutex = &mutex; + thread[x].cond = (x < ThreadCount / 2) ? &cond1 : &cond2; + thread[x].start(); + // wait for thread to start + QVERIFY(thread[x].started.wait(&mutex, 1000)); + } + mutex.unlock(); + + for (x = 0; x < ThreadCount; ++x) { + QVERIFY(thread[x].isRunning()); + QVERIFY(!thread[x].isFinished()); + } + + mutex.lock(); + cond1.wakeAll(); + cond2.wakeAll(); + mutex.unlock(); + + for (x = 0; x < ThreadCount; ++x) { + QVERIFY(thread[x].wait(1000)); + } + } + } +} + +void tst_QWaitCondition::wait_QReadWriteLock() +{ + { + QReadWriteLock readWriteLock(QReadWriteLock::Recursive); + QWaitCondition waitCondition; + + // ensure that the lockForRead is correctly restored + readWriteLock.lockForRead(); + + QVERIFY(!waitCondition.wait(&readWriteLock, 1)); + + QVERIFY(!readWriteLock.tryLockForWrite()); + QVERIFY(readWriteLock.tryLockForRead()); + readWriteLock.unlock(); + QVERIFY(!readWriteLock.tryLockForWrite()); + readWriteLock.unlock(); + + QVERIFY(readWriteLock.tryLockForWrite()); + readWriteLock.unlock(); + } + + { + QReadWriteLock readWriteLock(QReadWriteLock::Recursive); + QWaitCondition waitCondition; + + // ensure that the lockForWrite is correctly restored + readWriteLock.lockForWrite(); + + QVERIFY(!waitCondition.wait(&readWriteLock, 1)); + + QVERIFY(!readWriteLock.tryLockForRead()); + QVERIFY(readWriteLock.tryLockForWrite()); + readWriteLock.unlock(); + QVERIFY(!readWriteLock.tryLockForRead()); + readWriteLock.unlock(); + + QVERIFY(readWriteLock.tryLockForRead()); + readWriteLock.unlock(); + } + + + int x; + for (int i = 0; i < iterations; ++i) { + { + QReadWriteLock readWriteLock; + QWaitCondition waitCondition; + + readWriteLock.lockForRead(); + + waitCondition.wakeOne(); + QVERIFY(!waitCondition.wait(&readWriteLock, 1)); + + waitCondition.wakeAll(); + QVERIFY(!waitCondition.wait(&readWriteLock, 1)); + + readWriteLock.unlock(); + } + + { + QReadWriteLock readWriteLock; + QWaitCondition waitCondition; + + readWriteLock.lockForWrite(); + + waitCondition.wakeOne(); + QVERIFY(!waitCondition.wait(&readWriteLock, 1)); + + waitCondition.wakeAll(); + QVERIFY(!waitCondition.wait(&readWriteLock, 1)); + + readWriteLock.unlock(); + } + + { + // test multiple threads waiting on separate wait conditions + wait_QReadWriteLock_Thread_1 thread[ThreadCount]; + + for (x = 0; x < ThreadCount; ++x) { + thread[x].readWriteLock.lockForRead(); + thread[x].start(); + // wait for thread to start +#if defined(Q_OS_SYMBIAN) && defined(Q_CC_WINSCW) + // Symbian emulator startup simultaneously with this thread causes additional delay + QVERIFY(thread[x].cond.wait(&thread[x].readWriteLock, 10000)); +#else + QVERIFY(thread[x].cond.wait(&thread[x].readWriteLock, 1000)); +#endif + thread[x].readWriteLock.unlock(); + } + + for (x = 0; x < ThreadCount; ++x) { + QVERIFY(thread[x].isRunning()); + QVERIFY(!thread[x].isFinished()); + } + + for (x = 0; x < ThreadCount; ++x) { + thread[x].readWriteLock.lockForRead(); + thread[x].cond.wakeOne(); + thread[x].readWriteLock.unlock(); + } + + for (x = 0; x < ThreadCount; ++x) { + QVERIFY(thread[x].wait(1000)); + } + } + + { + // test multiple threads waiting on a wait condition + QReadWriteLock readWriteLock; + QWaitCondition cond1, cond2; + wait_QReadWriteLock_Thread_2 thread[ThreadCount]; + + readWriteLock.lockForWrite(); + for (x = 0; x < ThreadCount; ++x) { + thread[x].readWriteLock = &readWriteLock; + thread[x].cond = (x < ThreadCount / 2) ? &cond1 : &cond2; + thread[x].start(); + // wait for thread to start + QVERIFY(thread[x].started.wait(&readWriteLock, 1000)); + } + readWriteLock.unlock(); + + for (x = 0; x < ThreadCount; ++x) { + QVERIFY(thread[x].isRunning()); + QVERIFY(!thread[x].isFinished()); + } + + readWriteLock.lockForWrite(); + cond1.wakeAll(); + cond2.wakeAll(); + readWriteLock.unlock(); + + for (x = 0; x < ThreadCount; ++x) { + QVERIFY(thread[x].wait(1000)); + } + } + } + +} + +class wake_Thread : public QThread +{ +public: + static int count; + + QWaitCondition started; + QWaitCondition dummy; + + QMutex *mutex; + QWaitCondition *cond; + + inline wake_Thread() + : mutex(0), cond(0) + { } + + static inline void sleep(ulong s) + { QThread::sleep(s); } + + void run() + { + mutex->lock(); + ++count; + dummy.wakeOne(); // this wakeup should be lost + started.wakeOne(); + dummy.wakeAll(); // this one too + cond->wait(mutex); + --count; + mutex->unlock(); + } +}; + +int wake_Thread::count = 0; + +class wake_Thread_2 : public QThread +{ +public: + static int count; + + QWaitCondition started; + QWaitCondition dummy; + + QReadWriteLock *readWriteLock; + QWaitCondition *cond; + + inline wake_Thread_2() + : readWriteLock(0), cond(0) + { } + + static inline void sleep(ulong s) + { QThread::sleep(s); } + + void run() + { + readWriteLock->lockForWrite(); + ++count; + dummy.wakeOne(); // this wakeup should be lost + started.wakeOne(); + dummy.wakeAll(); // this one too + cond->wait(readWriteLock); + --count; + readWriteLock->unlock(); + } +}; + +int wake_Thread_2::count = 0; + +void tst_QWaitCondition::wakeOne() +{ + int x; + // wake up threads, one at a time + for (int i = 0; i < iterations; ++i) { + QMutex mutex; + QWaitCondition cond; + + // QMutex + wake_Thread thread[ThreadCount]; + bool thread_exited[ThreadCount]; + + mutex.lock(); + for (x = 0; x < ThreadCount; ++x) { + thread[x].mutex = &mutex; + thread[x].cond = &cond; + thread_exited[x] = FALSE; + thread[x].start(); + // wait for thread to start + QVERIFY(thread[x].started.wait(&mutex, 1000)); + // make sure wakeups are not queued... if nothing is + // waiting at the time of the wakeup, nothing happens + QVERIFY(!thread[x].dummy.wait(&mutex, 1)); + } + mutex.unlock(); + + QCOMPARE(wake_Thread::count, ThreadCount); + + // wake up threads one at a time + for (x = 0; x < ThreadCount; ++x) { + mutex.lock(); + cond.wakeOne(); + QVERIFY(!cond.wait(&mutex, COND_WAIT_TIME)); + QVERIFY(!thread[x].dummy.wait(&mutex, 1)); + mutex.unlock(); + + int exited = 0; + for (int y = 0; y < ThreadCount; ++y) { + if (thread_exited[y]) + continue; + if (thread[y].wait(exited > 0 ? 10 : 1000)) { + thread_exited[y] = TRUE; + ++exited; + } + } + + QCOMPARE(exited, 1); + QCOMPARE(wake_Thread::count, ThreadCount - (x + 1)); + } + + QCOMPARE(wake_Thread::count, 0); + + // QReadWriteLock + QReadWriteLock readWriteLock; + wake_Thread_2 rwthread[ThreadCount]; + + readWriteLock.lockForWrite(); + for (x = 0; x < ThreadCount; ++x) { + rwthread[x].readWriteLock = &readWriteLock; + rwthread[x].cond = &cond; + thread_exited[x] = FALSE; + rwthread[x].start(); + // wait for thread to start + QVERIFY(rwthread[x].started.wait(&readWriteLock, 1000)); + // make sure wakeups are not queued... if nothing is + // waiting at the time of the wakeup, nothing happens + QVERIFY(!rwthread[x].dummy.wait(&readWriteLock, 1)); + } + readWriteLock.unlock(); + + QCOMPARE(wake_Thread_2::count, ThreadCount); + + // wake up threads one at a time + for (x = 0; x < ThreadCount; ++x) { + readWriteLock.lockForWrite(); + cond.wakeOne(); + QVERIFY(!cond.wait(&readWriteLock, COND_WAIT_TIME)); + QVERIFY(!rwthread[x].dummy.wait(&readWriteLock, 1)); + readWriteLock.unlock(); + + int exited = 0; + for (int y = 0; y < ThreadCount; ++y) { + if (thread_exited[y]) + continue; + if (rwthread[y].wait(exited > 0 ? 10 : 1000)) { + thread_exited[y] = TRUE; + ++exited; + } + } + + QCOMPARE(exited, 1); + QCOMPARE(wake_Thread_2::count, ThreadCount - (x + 1)); + } + + QCOMPARE(wake_Thread_2::count, 0); + } + + // wake up threads, two at a time + for (int i = 0; i < iterations; ++i) { + QMutex mutex; + QWaitCondition cond; + + // QMutex + wake_Thread thread[ThreadCount]; + bool thread_exited[ThreadCount]; + + mutex.lock(); + for (x = 0; x < ThreadCount; ++x) { + thread[x].mutex = &mutex; + thread[x].cond = &cond; + thread_exited[x] = FALSE; + thread[x].start(); + // wait for thread to start + QVERIFY(thread[x].started.wait(&mutex, 1000)); + // make sure wakeups are not queued... if nothing is + // waiting at the time of the wakeup, nothing happens + QVERIFY(!thread[x].dummy.wait(&mutex, 1)); + } + mutex.unlock(); + + QCOMPARE(wake_Thread::count, ThreadCount); + + // wake up threads one at a time + for (x = 0; x < ThreadCount; x += 2) { + mutex.lock(); + cond.wakeOne(); + cond.wakeOne(); + QVERIFY(!cond.wait(&mutex, COND_WAIT_TIME)); + QVERIFY(!thread[x].dummy.wait(&mutex, 1)); + QVERIFY(!thread[x + 1].dummy.wait(&mutex, 1)); + mutex.unlock(); + + int exited = 0; + for (int y = 0; y < ThreadCount; ++y) { + if (thread_exited[y]) + continue; + if (thread[y].wait(exited > 0 ? 10 : 1000)) { + thread_exited[y] = TRUE; + ++exited; + } + } + + QCOMPARE(exited, 2); + QCOMPARE(wake_Thread::count, ThreadCount - (x + 2)); + } + + QCOMPARE(wake_Thread::count, 0); + + // QReadWriteLock + QReadWriteLock readWriteLock; + wake_Thread_2 rwthread[ThreadCount]; + + readWriteLock.lockForWrite(); + for (x = 0; x < ThreadCount; ++x) { + rwthread[x].readWriteLock = &readWriteLock; + rwthread[x].cond = &cond; + thread_exited[x] = FALSE; + rwthread[x].start(); + // wait for thread to start + QVERIFY(rwthread[x].started.wait(&readWriteLock, 1000)); + // make sure wakeups are not queued... if nothing is + // waiting at the time of the wakeup, nothing happens + QVERIFY(!rwthread[x].dummy.wait(&readWriteLock, 1)); + } + readWriteLock.unlock(); + + QCOMPARE(wake_Thread_2::count, ThreadCount); + + // wake up threads one at a time + for (x = 0; x < ThreadCount; x += 2) { + readWriteLock.lockForWrite(); + cond.wakeOne(); + cond.wakeOne(); + QVERIFY(!cond.wait(&readWriteLock, COND_WAIT_TIME)); + QVERIFY(!rwthread[x].dummy.wait(&readWriteLock, 1)); + QVERIFY(!rwthread[x + 1].dummy.wait(&readWriteLock, 1)); + readWriteLock.unlock(); + + int exited = 0; + for (int y = 0; y < ThreadCount; ++y) { + if (thread_exited[y]) + continue; + if (rwthread[y].wait(exited > 0 ? 10 : 1000)) { + thread_exited[y] = TRUE; + ++exited; + } + } + + QCOMPARE(exited, 2); + QCOMPARE(wake_Thread_2::count, ThreadCount - (x + 2)); + } + + QCOMPARE(wake_Thread_2::count, 0); +} +} + +void tst_QWaitCondition::wakeAll() +{ + int x; + for (int i = 0; i < iterations; ++i) { + QMutex mutex; + QWaitCondition cond; + + // QMutex + wake_Thread thread[ThreadCount]; + + mutex.lock(); + for (x = 0; x < ThreadCount; ++x) { + thread[x].mutex = &mutex; + thread[x].cond = &cond; + thread[x].start(); + // wait for thread to start + QVERIFY(thread[x].started.wait(&mutex, 1000)); + } + mutex.unlock(); + + QCOMPARE(wake_Thread::count, ThreadCount); + + // wake up all threads at once + mutex.lock(); + cond.wakeAll(); + QVERIFY(!cond.wait(&mutex, COND_WAIT_TIME)); + mutex.unlock(); + + int exited = 0; + for (x = 0; x < ThreadCount; ++x) { + if (thread[x].wait(1000)) + ++exited; + } + + QCOMPARE(exited, ThreadCount); + QCOMPARE(wake_Thread::count, 0); + + // QReadWriteLock + QReadWriteLock readWriteLock; + wake_Thread_2 rwthread[ThreadCount]; + + readWriteLock.lockForWrite(); + for (x = 0; x < ThreadCount; ++x) { + rwthread[x].readWriteLock = &readWriteLock; + rwthread[x].cond = &cond; + rwthread[x].start(); + // wait for thread to start + QVERIFY(rwthread[x].started.wait(&readWriteLock, 1000)); + } + readWriteLock.unlock(); + + QCOMPARE(wake_Thread_2::count, ThreadCount); + + // wake up all threads at once + readWriteLock.lockForWrite(); + cond.wakeAll(); + QVERIFY(!cond.wait(&readWriteLock, COND_WAIT_TIME)); + readWriteLock.unlock(); + + exited = 0; + for (x = 0; x < ThreadCount; ++x) { + if (rwthread[x].wait(1000)) + ++exited; + } + + QCOMPARE(exited, ThreadCount); + QCOMPARE(wake_Thread_2::count, 0); + } +} + +class wait_RaceConditionThread : public QThread +{ +public: + wait_RaceConditionThread(QMutex *mutex, QWaitCondition *startup, QWaitCondition *waitCondition, + ulong timeout = ULONG_MAX) + : timeout(timeout), returnValue(false), ready(false), + mutex(mutex), startup(startup), waitCondition(waitCondition) {} + + unsigned long timeout; + bool returnValue; + + bool ready; + + QMutex *mutex; + QWaitCondition *startup; + QWaitCondition *waitCondition; + + void run() { + mutex->lock(); + + ready = true; + startup->wakeOne(); + + returnValue = waitCondition->wait(mutex, timeout); + + mutex->unlock(); + } +}; + +class wait_RaceConditionThread_2 : public QThread +{ +public: + wait_RaceConditionThread_2(QReadWriteLock *readWriteLock, + QWaitCondition *startup, + QWaitCondition *waitCondition, + ulong timeout = ULONG_MAX) + : timeout(timeout), returnValue(false), ready(false), + readWriteLock(readWriteLock), startup(startup), waitCondition(waitCondition) + { } + + unsigned long timeout; + bool returnValue; + + bool ready; + + QReadWriteLock *readWriteLock; + QWaitCondition *startup; + QWaitCondition *waitCondition; + + void run() { + readWriteLock->lockForWrite(); + + ready = true; + startup->wakeOne(); + + returnValue = waitCondition->wait(readWriteLock, timeout); + + readWriteLock->unlock(); + } +}; + +void tst_QWaitCondition::wait_RaceCondition() +{ + { + QMutex mutex; + QWaitCondition startup; + QWaitCondition waitCondition; + + wait_RaceConditionThread timeoutThread(&mutex, &startup, &waitCondition, 1000), + waitingThread1(&mutex, &startup, &waitCondition); + + timeoutThread.start(); + waitingThread1.start(); + mutex.lock(); + + // wait for the threads to start up + while (!timeoutThread.ready + || !waitingThread1.ready) { + startup.wait(&mutex); + } + + QTest::qWait(2000); + + waitCondition.wakeOne(); + + mutex.unlock(); + + QVERIFY(timeoutThread.wait(5000)); + QVERIFY(!timeoutThread.returnValue); + QVERIFY(waitingThread1.wait(5000)); + QVERIFY(waitingThread1.returnValue); + } + + { + QReadWriteLock readWriteLock; + QWaitCondition startup; + QWaitCondition waitCondition; + + wait_RaceConditionThread_2 timeoutThread(&readWriteLock, &startup, &waitCondition, 1000), + waitingThread1(&readWriteLock, &startup, &waitCondition); + + timeoutThread.start(); + waitingThread1.start(); + readWriteLock.lockForRead(); + + // wait for the threads to start up + while (!timeoutThread.ready + || !waitingThread1.ready) { + startup.wait(&readWriteLock); + } + + QTest::qWait(2000); + + waitCondition.wakeOne(); + + readWriteLock.unlock(); + + QVERIFY(timeoutThread.wait(5000)); + QVERIFY(!timeoutThread.returnValue); + QVERIFY(waitingThread1.wait(5000)); + QVERIFY(waitingThread1.returnValue); + } +} + +QTEST_MAIN(tst_QWaitCondition) +#include "tst_qwaitcondition.moc" -- cgit v1.2.3