From 7539437a33e02caf940f6ca3cf7fa143f24e53ff Mon Sep 17 00:00:00 2001 From: Ivailo Monev Date: Mon, 25 Jan 2021 19:54:10 +0200 Subject: [PATCH] add QReadLocker and QReadWriteLock tests Signed-off-by: Ivailo Monev --- tests/auto/qreadlocker/CMakeLists.txt | 3 + tests/auto/qreadlocker/tst_qreadlocker.cpp | 224 +++++ tests/auto/qreadwritelock/CMakeLists.txt | 3 + tests/auto/qreadwritelock/tst_qreadwritelock.cpp | 1119 ++++++++++++++++++++++ 4 files changed, 1349 insertions(+) create mode 100644 tests/auto/qreadlocker/CMakeLists.txt create mode 100644 tests/auto/qreadlocker/tst_qreadlocker.cpp create mode 100644 tests/auto/qreadwritelock/CMakeLists.txt create mode 100644 tests/auto/qreadwritelock/tst_qreadwritelock.cpp diff --git a/tests/auto/qreadlocker/CMakeLists.txt b/tests/auto/qreadlocker/CMakeLists.txt new file mode 100644 index 000000000..be683332c --- /dev/null +++ b/tests/auto/qreadlocker/CMakeLists.txt @@ -0,0 +1,3 @@ +katie_test(tst_qreadlocker + ${CMAKE_CURRENT_SOURCE_DIR}/tst_qreadlocker.cpp +) diff --git a/tests/auto/qreadlocker/tst_qreadlocker.cpp b/tests/auto/qreadlocker/tst_qreadlocker.cpp new file mode 100644 index 000000000..566bf6161 --- /dev/null +++ b/tests/auto/qreadlocker/tst_qreadlocker.cpp @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2016-2021 Ivailo Monev +** +** This file is part of the test suite of the Katie 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. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company 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. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include + +#include +#include +#include +#include + +//TESTED_CLASS= +//TESTED_FILES= + +class tst_QReadLockerThread : public QThread +{ +public: + QReadWriteLock lock; + QSemaphore semaphore, testSemaphore; + + void waitForTest() + { + semaphore.release(); + testSemaphore.acquire(); + } +}; + +class tst_QReadLocker : public QObject +{ + Q_OBJECT + +public: + tst_QReadLocker(); + ~tst_QReadLocker(); + + tst_QReadLockerThread *thread; + + void waitForThread() + { + thread->semaphore.acquire(); + } + void releaseThread() + { + thread->testSemaphore.release(); + } + +private slots: + void scopeTest(); + void unlockAndRelockTest(); + void lockerStateTest(); +}; + +tst_QReadLocker::tst_QReadLocker() +{ +} + +tst_QReadLocker::~tst_QReadLocker() +{ +} + +void tst_QReadLocker::scopeTest() +{ + class ScopeTestThread : public tst_QReadLockerThread + { + public: + void run() + { + waitForTest(); + + { + QReadLocker locker(&lock); + waitForTest(); + } + + waitForTest(); + } + }; + + thread = new ScopeTestThread; + thread->start(); + + waitForThread(); + // lock should be unlocked before entering the scope that creates the QReadLocker + QVERIFY(thread->lock.tryLockForWrite()); + thread->lock.unlock(); + releaseThread(); + + waitForThread(); + // lock should be locked by the QReadLocker + QVERIFY(!thread->lock.tryLockForWrite()); + releaseThread(); + + waitForThread(); + // lock should be unlocked when the QReadLocker goes out of scope + QVERIFY(thread->lock.tryLockForWrite()); + thread->lock.unlock(); + releaseThread(); + + QVERIFY(thread->wait()); + + delete thread; + thread = 0; +} + + +void tst_QReadLocker::unlockAndRelockTest() +{ + class UnlockAndRelockThread : public tst_QReadLockerThread + { + public: + void run() + { + QReadLocker locker(&lock); + + waitForTest(); + + locker.unlock(); + + waitForTest(); + + locker.relock(); + + waitForTest(); + } + }; + + thread = new UnlockAndRelockThread; + thread->start(); + + waitForThread(); + // lock should be locked by the QReadLocker + QVERIFY(!thread->lock.tryLockForWrite()); + releaseThread(); + + waitForThread(); + // lock has been explicitly unlocked via QReadLocker + QVERIFY(thread->lock.tryLockForWrite()); + thread->lock.unlock(); + releaseThread(); + + waitForThread(); + // lock has been explicitly relocked via QReadLocker + QVERIFY(!thread->lock.tryLockForWrite()); + releaseThread(); + + QVERIFY(thread->wait()); + + delete thread; + thread = 0; +} + +void tst_QReadLocker::lockerStateTest() +{ + class LockerStateThread : public tst_QReadLockerThread + { + public: + void run() + { + { + QReadLocker locker(&lock); + locker.relock(); + locker.unlock(); + + waitForTest(); + } + + waitForTest(); + } + }; + + thread = new LockerStateThread; + thread->start(); + + waitForThread(); + // even though we relock() after creating the QReadLocker, it shouldn't lock the lock more than once + QVERIFY(thread->lock.tryLockForWrite()); + thread->lock.unlock(); + releaseThread(); + + waitForThread(); + // if we call QReadLocker::unlock(), its destructor should do nothing + QVERIFY(thread->lock.tryLockForWrite()); + thread->lock.unlock(); + releaseThread(); + + QVERIFY(thread->wait()); + + delete thread; + thread = 0; +} + +QTEST_MAIN(tst_QReadLocker) + +#include "moc_tst_qreadlocker.cpp" diff --git a/tests/auto/qreadwritelock/CMakeLists.txt b/tests/auto/qreadwritelock/CMakeLists.txt new file mode 100644 index 000000000..0174bc716 --- /dev/null +++ b/tests/auto/qreadwritelock/CMakeLists.txt @@ -0,0 +1,3 @@ +katie_test(tst_qreadwritelock + ${CMAKE_CURRENT_SOURCE_DIR}/tst_qreadwritelock.cpp +) diff --git a/tests/auto/qreadwritelock/tst_qreadwritelock.cpp b/tests/auto/qreadwritelock/tst_qreadwritelock.cpp new file mode 100644 index 000000000..10de459e0 --- /dev/null +++ b/tests/auto/qreadwritelock/tst_qreadwritelock.cpp @@ -0,0 +1,1119 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2016-2021 Ivailo Monev +** +** This file is part of the test suite of the Katie 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. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company 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. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include +#include +#include +#include +#include +#include +#include + +#include + +//on solaris, threads that loop one the release bool variable +//needs to sleep more than 1 usec. +#ifdef Q_OS_SOLARIS +# define RWTESTSLEEP usleep(10); +#else +# define RWTESTSLEEP usleep(1); +#endif + +#include + +//TESTED_CLASS= +//TESTED_FILES= + +class tst_QReadWriteLock : public QObject +{ + Q_OBJECT +public: + tst_QReadWriteLock(); + virtual ~tst_QReadWriteLock(); + + + /* + Singlethreaded tests + */ +private slots: + void constructDestruct(); + void readLockUnlock(); + void writeLockUnlock(); + void readLockUnlockLoop(); + void writeLockUnlockLoop(); + void readLockLoop(); + void writeLockLoop(); + void readWriteLockUnlockLoop(); + void tryReadLock(); + void tryWriteLock(); + /* + Multithreaded tests + */ +private slots: + + void readLockBlockRelease(); + void writeLockBlockRelease(); + void multipleReadersBlockRelease(); + void multipleReadersLoop(); + void multipleWritersLoop(); + void multipleReadersWritersLoop(); + void countingTest(); + void limitedReaders(); + void deleteOnUnlock(); + + /* + Performance tests + */ +private slots: + void uncontendedLocks(); + + // recursive locking tests + void recursiveReadLock(); + void recursiveWriteLock(); +}; + +tst_QReadWriteLock::tst_QReadWriteLock() +{ + +} + +tst_QReadWriteLock::~tst_QReadWriteLock() +{ + +} + +void tst_QReadWriteLock::constructDestruct() +{ + { + QReadWriteLock rwlock; + } +} + +void tst_QReadWriteLock::readLockUnlock() +{ + QReadWriteLock rwlock; + rwlock.lockForRead(); + rwlock.unlock(); +} + +void tst_QReadWriteLock::writeLockUnlock() +{ + QReadWriteLock rwlock; + rwlock.lockForWrite(); + rwlock.unlock(); +} + +void tst_QReadWriteLock::readLockUnlockLoop() +{ + QReadWriteLock rwlock; + int runs=10000; + int i; + for (i=0; i= 1000); + testsTurn.release(); + + threadsTurn.acquire(); + timer.start(); + QVERIFY(readWriteLock.tryLockForRead(1000)); + QVERIFY(timer.elapsed() <= 1000); + lockCount.ref(); + QVERIFY(readWriteLock.tryLockForRead(1000)); + lockCount.ref(); + lockCount.deref(); + readWriteLock.unlock(); + lockCount.deref(); + readWriteLock.unlock(); + testsTurn.release(); + + threadsTurn.acquire(); + } + }; + + Thread thread; + thread.start(); + + testsTurn.acquire(); + readWriteLock.lockForWrite(); + QVERIFY(lockCount.testAndSetRelaxed(0, 1)); + threadsTurn.release(); + + testsTurn.acquire(); + QVERIFY(lockCount.testAndSetRelaxed(1, 0)); + readWriteLock.unlock(); + threadsTurn.release(); + + testsTurn.acquire(); + readWriteLock.lockForWrite(); + QVERIFY(lockCount.testAndSetRelaxed(0, 1)); + threadsTurn.release(); + + testsTurn.acquire(); + QVERIFY(lockCount.testAndSetRelaxed(1, 0)); + readWriteLock.unlock(); + threadsTurn.release(); + + // stop thread + testsTurn.acquire(); + threadsTurn.release(); + thread.wait(); + } +} + +void tst_QReadWriteLock::tryWriteLock() +{ + { + QReadWriteLock rwlock; + QVERIFY(rwlock.tryLockForWrite()); + rwlock.unlock(); + QVERIFY(rwlock.tryLockForWrite()); + rwlock.unlock(); + + rwlock.lockForWrite(); + QVERIFY(!rwlock.tryLockForWrite()); + QVERIFY(!rwlock.tryLockForWrite()); + rwlock.unlock(); + + rwlock.lockForRead(); + QVERIFY(!rwlock.tryLockForWrite()); + rwlock.unlock(); + } + + { + QReadWriteLock rwlock(QReadWriteLock::Recursive); + QVERIFY(rwlock.tryLockForWrite()); + rwlock.unlock(); + QVERIFY(rwlock.tryLockForWrite()); + rwlock.unlock(); + + rwlock.lockForWrite(); + QVERIFY(rwlock.tryLockForWrite()); + QVERIFY(rwlock.tryLockForWrite()); + rwlock.unlock(); + rwlock.unlock(); + rwlock.unlock(); + + rwlock.lockForRead(); + QVERIFY(!rwlock.tryLockForWrite()); + rwlock.unlock(); + } + + // functionality test + { + class Thread : public QThread + { + public: + Thread() : failureCount(0) { } + void run() + { + testsTurn.release(); + + threadsTurn.acquire(); + if (readWriteLock.tryLockForWrite()) + failureCount++; + testsTurn.release(); + + threadsTurn.acquire(); + if (!readWriteLock.tryLockForWrite()) + failureCount++; + if (!lockCount.testAndSetRelaxed(0, 1)) + failureCount++; + if (!lockCount.testAndSetRelaxed(1, 0)) + failureCount++; + readWriteLock.unlock(); + testsTurn.release(); + + threadsTurn.acquire(); + if (readWriteLock.tryLockForWrite(1000)) + failureCount++; + testsTurn.release(); + + threadsTurn.acquire(); + if (!readWriteLock.tryLockForWrite(1000)) + failureCount++; + if (!lockCount.testAndSetRelaxed(0, 1)) + failureCount++; + if (!lockCount.testAndSetRelaxed(1, 0)) + failureCount++; + readWriteLock.unlock(); + testsTurn.release(); + + threadsTurn.acquire(); + } + + int failureCount; + }; + + Thread thread; + thread.start(); + + testsTurn.acquire(); + readWriteLock.lockForRead(); + lockCount.ref(); + threadsTurn.release(); + + testsTurn.acquire(); + lockCount.deref(); + readWriteLock.unlock(); + threadsTurn.release(); + + testsTurn.acquire(); + readWriteLock.lockForRead(); + lockCount.ref(); + threadsTurn.release(); + + testsTurn.acquire(); + lockCount.deref(); + readWriteLock.unlock(); + threadsTurn.release(); + + // stop thread + testsTurn.acquire(); + threadsTurn.release(); + thread.wait(); + + QCOMPARE(thread.failureCount, 0); + } +} + +bool threadDone; +volatile bool release; + +/* + write-lock + unlock + set threadone +*/ +class WriteLockThread : public QThread +{ +public: + QReadWriteLock &testRwlock; + inline WriteLockThread(QReadWriteLock &l) : testRwlock(l) { } + void run() + { + testRwlock.lockForWrite(); + testRwlock.unlock(); + threadDone=true; + } +}; + +/* + read-lock + unlock + set threadone +*/ +class ReadLockThread : public QThread +{ +public: + QReadWriteLock &testRwlock; + inline ReadLockThread(QReadWriteLock &l) : testRwlock(l) { } + void run() + { + testRwlock.lockForRead(); + testRwlock.unlock(); + threadDone=true; + } +}; +/* + write-lock + wait for release==true + unlock +*/ +class WriteLockReleasableThread : public QThread +{ +public: + QReadWriteLock &testRwlock; + inline WriteLockReleasableThread(QReadWriteLock &l) : testRwlock(l) { } + void run() + { + testRwlock.lockForWrite(); + while(release==false) { + RWTESTSLEEP + } + testRwlock.unlock(); + } +}; + +/* + read-lock + wait for release==true + unlock +*/ +class ReadLockReleasableThread : public QThread +{ +public: + QReadWriteLock &testRwlock; + inline ReadLockReleasableThread(QReadWriteLock &l) : testRwlock(l) { } + void run() + { + testRwlock.lockForRead(); + while(release==false) { + RWTESTSLEEP + } + testRwlock.unlock(); + } +}; + + +/* + for(runTime msecs) + read-lock + msleep(holdTime msecs) + release lock + msleep(waitTime msecs) +*/ +class ReadLockLoopThread : public QThread +{ +public: + QReadWriteLock &testRwlock; + int runTime; + int holdTime; + int waitTime; + bool print; + QTime t; + inline ReadLockLoopThread(QReadWriteLock &l, int runTime, int holdTime=0, int waitTime=0, bool print=false) + :testRwlock(l) + ,runTime(runTime) + ,holdTime(holdTime) + ,waitTime(waitTime) + ,print(print) + { } + void run() + { + t.start(); + while (t.elapsed()start(); + for (i=0; iwait(); + for (i=0; istart(); + for (i=0; iwait(); + for (i=0; istart(QThread::NormalPriority); + for (i=0; istart(QThread::IdlePriority); + + for (i=0; iwait(); + for (i=0; iwait(); + + for (i=0; istart(QThread::NormalPriority); + for (i=0; istart(QThread::LowestPriority); + + for (i=0; iwait(); + for (i=0; iwait(); + + for (i=0; ilock(); + m_startup->wakeAll(); + m_waitMutex->unlock(); + + // DeleteOnUnlockThread and the main thread will race from this point + (*m_lock)->lockForWrite(); + (*m_lock)->unlock(); + delete *m_lock; + } +private: + QReadWriteLock **m_lock; + QWaitCondition *m_startup; + QMutex *m_waitMutex; +}; + +void tst_QReadWriteLock::deleteOnUnlock() +{ + QReadWriteLock *lock = 0; + QWaitCondition startup; + QMutex waitMutex; + + DeleteOnUnlockThread thread2(&lock, &startup, &waitMutex); + + QTime t; + t.start(); + while(t.elapsed() < 4000) { + lock = new QReadWriteLock(); + waitMutex.lock(); + lock->lockForWrite(); + thread2.start(); + startup.wait(&waitMutex); + waitMutex.unlock(); + + // DeleteOnUnlockThread and the main thread will race from this point + lock->unlock(); + + thread2.wait(); + } +} + + +void tst_QReadWriteLock::uncontendedLocks() +{ + + uint read=0; + uint write=0; + uint count=0; + int millisecs=1000; + { + QTime t; + t.start(); + while(t.elapsed() tryLockForWrite(); + testsTurn.release(); + } + + // test is releasing recursive write lock + for (int i = 0; i < RecursiveLockCount - 1; ++i) { + threadsTurn.acquire(); + tryLockForWriteResult = lock->tryLockForWrite(); + testsTurn.release(); + } + + // after final unlock in test, we should get the lock + threadsTurn.acquire(); + tryLockForWriteResult = lock->tryLockForWrite(); + testsTurn.release(); + + // cleanup + threadsTurn.acquire(); + lock->unlock(); + testsTurn.release(); + + // test will lockForRead(), then we will lockForWrite() + // (and block), purpose is to ensure that the test can + // recursive lockForRead() even with a waiting writer + threadsTurn.acquire(); + // testsTurn.release(); // ### do not release here, the test uses tryAcquire() + lock->lockForWrite(); + lock->unlock(); + } + }; + + // init + QReadWriteLock lock(QReadWriteLock::Recursive); + RecursiveReadLockThread thread; + thread.lock = &lock; + thread.start(); + + testsTurn.acquire(); + + // verify that we can get multiple read locks in the same thread + for (int i = 0; i < RecursiveLockCount; ++i) { + QVERIFY(lock.tryLockForRead()); + threadsTurn.release(); + + testsTurn.acquire(); + QVERIFY(!thread.tryLockForWriteResult); + } + + // have to unlock the same number of times that we locked + for (int i = 0;i < RecursiveLockCount - 1; ++i) { + lock.unlock(); + threadsTurn.release(); + + testsTurn.acquire(); + QVERIFY(!thread.tryLockForWriteResult); + } + + // after the final unlock, we should be able to get the write lock + lock.unlock(); + threadsTurn.release(); + + testsTurn.acquire(); + QVERIFY(thread.tryLockForWriteResult); + threadsTurn.release(); + + // check that recursive read locking works even when we have a waiting writer + testsTurn.acquire(); + QVERIFY(lock.tryLockForRead()); + threadsTurn.release(); + + testsTurn.tryAcquire(1, 1000); + QVERIFY(lock.tryLockForRead()); + lock.unlock(); + lock.unlock(); + + // cleanup + QVERIFY(thread.wait()); +} + +void tst_QReadWriteLock::recursiveWriteLock() +{ + // thread to attempt locking for reading while the test recursively locks for writing + class RecursiveWriteLockThread : public QThread + { + public: + QReadWriteLock *lock; + bool tryLockForReadResult; + + void run() + { + testsTurn.release(); + + // test is recursively locking for writing + for (int i = 0; i < RecursiveLockCount; ++i) { + threadsTurn.acquire(); + tryLockForReadResult = lock->tryLockForRead(); + testsTurn.release(); + } + + // test is releasing recursive write lock + for (int i = 0; i < RecursiveLockCount - 1; ++i) { + threadsTurn.acquire(); + tryLockForReadResult = lock->tryLockForRead(); + testsTurn.release(); + } + + // after final unlock in test, we should get the lock + threadsTurn.acquire(); + tryLockForReadResult = lock->tryLockForRead(); + testsTurn.release(); + + // cleanup + lock->unlock(); + } + }; + + // init + QReadWriteLock lock(QReadWriteLock::Recursive); + RecursiveWriteLockThread thread; + thread.lock = &lock; + thread.start(); + + testsTurn.acquire(); + + // verify that we can get multiple read locks in the same thread + for (int i = 0; i < RecursiveLockCount; ++i) { + QVERIFY(lock.tryLockForWrite()); + threadsTurn.release(); + + testsTurn.acquire(); + QVERIFY(!thread.tryLockForReadResult); + } + + // have to unlock the same number of times that we locked + for (int i = 0;i < RecursiveLockCount - 1; ++i) { + lock.unlock(); + threadsTurn.release(); + + testsTurn.acquire(); + QVERIFY(!thread.tryLockForReadResult); + } + + // after the final unlock, thread should be able to get the read lock + lock.unlock(); + threadsTurn.release(); + + testsTurn.acquire(); + QVERIFY(thread.tryLockForReadResult); + + // cleanup + QVERIFY(thread.wait()); +} + +QTEST_MAIN(tst_QReadWriteLock) + +#include "moc_tst_qreadwritelock.cpp" -- 2.11.0