blob: b7134d0454366ab155ef421b3f57d0152f7e4789 [file] [log] [blame]
/****************************************************************************
**
** 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 <QtTest/QtTest>
#include <qcoreapplication.h>
#include <qthread.h>
#include <qsemaphore.h>
class tst_QSemaphore : public QObject
{
Q_OBJECT
private slots:
void acquire();
void multiRelease();
void multiAcquireRelease();
void tryAcquire();
void tryAcquireWithTimeout_data();
void tryAcquireWithTimeout();
void tryAcquireWithTimeoutStarvation();
void tryAcquireWithTimeoutForever_data();
void tryAcquireWithTimeoutForever();
void producerConsumer();
void raii();
};
static QSemaphore *semaphore = 0;
class ThreadOne : public QThread
{
public:
ThreadOne() {}
protected:
void run()
{
int i = 0;
while ( i < 100 ) {
semaphore->acquire();
i++;
semaphore->release();
}
}
};
class ThreadN : public QThread
{
int N;
public:
ThreadN(int n) :N(n) { }
protected:
void run()
{
int i = 0;
while ( i < 100 ) {
semaphore->acquire(N);
i++;
semaphore->release(N);
}
}
};
void tst_QSemaphore::acquire()
{
{
// old incrementOne() test
QVERIFY(!semaphore);
semaphore = new QSemaphore;
// make some "thing" available
semaphore->release();
ThreadOne t1;
ThreadOne t2;
t1.start();
t2.start();
QVERIFY(t1.wait(4000));
QVERIFY(t2.wait(4000));
delete semaphore;
semaphore = 0;
}
// old incrementN() test
{
QVERIFY(!semaphore);
semaphore = new QSemaphore;
// make 4 "things" available
semaphore->release(4);
ThreadN t1(2);
ThreadN t2(3);
t1.start();
t2.start();
QVERIFY(t1.wait(4000));
QVERIFY(t2.wait(4000));
delete semaphore;
semaphore = 0;
}
QSemaphore semaphore;
QCOMPARE(semaphore.available(), 0);
semaphore.release();
QCOMPARE(semaphore.available(), 1);
semaphore.release();
QCOMPARE(semaphore.available(), 2);
semaphore.release(10);
QCOMPARE(semaphore.available(), 12);
semaphore.release(10);
QCOMPARE(semaphore.available(), 22);
semaphore.acquire();
QCOMPARE(semaphore.available(), 21);
semaphore.acquire();
QCOMPARE(semaphore.available(), 20);
semaphore.acquire(10);
QCOMPARE(semaphore.available(), 10);
semaphore.acquire(10);
QCOMPARE(semaphore.available(), 0);
}
void tst_QSemaphore::multiRelease()
{
class Thread : public QThread
{
public:
QSemaphore &sem;
Thread(QSemaphore &sem) : sem(sem) {}
void run() override
{
sem.acquire();
}
};
QSemaphore sem;
QVector<Thread *> threads;
threads.resize(4);
for (Thread *&t : threads)
t = new Thread(sem);
for (Thread *&t : threads)
t->start();
// wait for all threads to reach the sem.acquire() and then
// release them all
QTest::qSleep(1);
sem.release(threads.size());
for (Thread *&t : threads)
t->wait();
qDeleteAll(threads);
}
void tst_QSemaphore::multiAcquireRelease()
{
class Thread : public QThread
{
public:
QSemaphore &sem;
Thread(QSemaphore &sem) : sem(sem) {}
void run() override
{
sem.acquire();
sem.release();
}
};
QSemaphore sem;
QVector<Thread *> threads;
threads.resize(4);
for (Thread *&t : threads)
t = new Thread(sem);
for (Thread *&t : threads)
t->start();
// wait for all threads to reach the sem.acquire() and then
// release them all
QTest::qSleep(1);
sem.release();
for (Thread *&t : threads)
t->wait();
qDeleteAll(threads);
}
void tst_QSemaphore::tryAcquire()
{
QSemaphore semaphore;
QCOMPARE(semaphore.available(), 0);
semaphore.release();
QCOMPARE(semaphore.available(), 1);
QVERIFY(!semaphore.tryAcquire(2));
QVERIFY(!semaphore.tryAcquire(2, 0));
QCOMPARE(semaphore.available(), 1);
semaphore.release();
QCOMPARE(semaphore.available(), 2);
QVERIFY(!semaphore.tryAcquire(3));
QVERIFY(!semaphore.tryAcquire(3, 0));
QCOMPARE(semaphore.available(), 2);
semaphore.release(10);
QCOMPARE(semaphore.available(), 12);
QVERIFY(!semaphore.tryAcquire(100));
QVERIFY(!semaphore.tryAcquire(100, 0));
QCOMPARE(semaphore.available(), 12);
semaphore.release(10);
QCOMPARE(semaphore.available(), 22);
QVERIFY(!semaphore.tryAcquire(100));
QVERIFY(!semaphore.tryAcquire(100, 0));
QCOMPARE(semaphore.available(), 22);
QVERIFY(semaphore.tryAcquire());
QCOMPARE(semaphore.available(), 21);
QVERIFY(semaphore.tryAcquire());
QCOMPARE(semaphore.available(), 20);
semaphore.release(2);
QVERIFY(semaphore.tryAcquire(1, 0));
QCOMPARE(semaphore.available(), 21);
QVERIFY(semaphore.tryAcquire(1, 0));
QCOMPARE(semaphore.available(), 20);
QVERIFY(semaphore.tryAcquire(10));
QCOMPARE(semaphore.available(), 10);
semaphore.release(10);
QVERIFY(semaphore.tryAcquire(10, 0));
QCOMPARE(semaphore.available(), 10);
QVERIFY(semaphore.tryAcquire(10));
QCOMPARE(semaphore.available(), 0);
// should not be able to acquire more
QVERIFY(!semaphore.tryAcquire());
QVERIFY(!semaphore.tryAcquire(1, 0));
QCOMPARE(semaphore.available(), 0);
QVERIFY(!semaphore.tryAcquire());
QVERIFY(!semaphore.tryAcquire(1, 0));
QCOMPARE(semaphore.available(), 0);
QVERIFY(!semaphore.tryAcquire(10));
QVERIFY(!semaphore.tryAcquire(10, 0));
QCOMPARE(semaphore.available(), 0);
QVERIFY(!semaphore.tryAcquire(10));
QVERIFY(!semaphore.tryAcquire(10, 0));
QCOMPARE(semaphore.available(), 0);
}
void tst_QSemaphore::tryAcquireWithTimeout_data()
{
QTest::addColumn<int>("timeout");
QTest::newRow("0.2s") << 200;
QTest::newRow("2s") << 2000;
}
void tst_QSemaphore::tryAcquireWithTimeout()
{
QFETCH(int, timeout);
// timers are not guaranteed to be accurate down to the last millisecond,
// so we permit the elapsed times to be up to this far from the expected value.
int fuzz = 50 + (timeout / 20);
QSemaphore semaphore;
QElapsedTimer time;
#define FUZZYCOMPARE(a,e) \
do { \
int a1 = a; \
int e1 = e; \
QVERIFY2(qAbs(a1-e1) < fuzz, \
qPrintable(QString("(%1=%2) is more than %3 milliseconds different from (%4=%5)") \
.arg(#a).arg(a1).arg(fuzz).arg(#e).arg(e1))); \
} while (0)
QCOMPARE(semaphore.available(), 0);
semaphore.release();
QCOMPARE(semaphore.available(), 1);
time.start();
QVERIFY(!semaphore.tryAcquire(2, timeout));
FUZZYCOMPARE(time.elapsed(), timeout);
QCOMPARE(semaphore.available(), 1);
semaphore.release();
QCOMPARE(semaphore.available(), 2);
time.start();
QVERIFY(!semaphore.tryAcquire(3, timeout));
FUZZYCOMPARE(time.elapsed(), timeout);
QCOMPARE(semaphore.available(), 2);
semaphore.release(10);
QCOMPARE(semaphore.available(), 12);
time.start();
QVERIFY(!semaphore.tryAcquire(100, timeout));
FUZZYCOMPARE(time.elapsed(), timeout);
QCOMPARE(semaphore.available(), 12);
semaphore.release(10);
QCOMPARE(semaphore.available(), 22);
time.start();
QVERIFY(!semaphore.tryAcquire(100, timeout));
FUZZYCOMPARE(time.elapsed(), timeout);
QCOMPARE(semaphore.available(), 22);
time.start();
QVERIFY(semaphore.tryAcquire(1, timeout));
FUZZYCOMPARE(time.elapsed(), 0);
QCOMPARE(semaphore.available(), 21);
time.start();
QVERIFY(semaphore.tryAcquire(1, timeout));
FUZZYCOMPARE(time.elapsed(), 0);
QCOMPARE(semaphore.available(), 20);
time.start();
QVERIFY(semaphore.tryAcquire(10, timeout));
FUZZYCOMPARE(time.elapsed(), 0);
QCOMPARE(semaphore.available(), 10);
time.start();
QVERIFY(semaphore.tryAcquire(10, timeout));
FUZZYCOMPARE(time.elapsed(), 0);
QCOMPARE(semaphore.available(), 0);
// should not be able to acquire more
time.start();
QVERIFY(!semaphore.tryAcquire(1, timeout));
FUZZYCOMPARE(time.elapsed(), timeout);
QCOMPARE(semaphore.available(), 0);
time.start();
QVERIFY(!semaphore.tryAcquire(1, timeout));
FUZZYCOMPARE(time.elapsed(), timeout);
QCOMPARE(semaphore.available(), 0);
time.start();
QVERIFY(!semaphore.tryAcquire(10, timeout));
FUZZYCOMPARE(time.elapsed(), timeout);
QCOMPARE(semaphore.available(), 0);
time.start();
QVERIFY(!semaphore.tryAcquire(10, timeout));
FUZZYCOMPARE(time.elapsed(), timeout);
QCOMPARE(semaphore.available(), 0);
#undef FUZZYCOMPARE
}
void tst_QSemaphore::tryAcquireWithTimeoutStarvation()
{
class Thread : public QThread
{
public:
QSemaphore startup;
QSemaphore *semaphore;
int amountToConsume, timeout;
void run()
{
startup.release();
forever {
if (!semaphore->tryAcquire(amountToConsume, timeout))
break;
semaphore->release(amountToConsume);
}
}
};
QSemaphore semaphore;
semaphore.release(1);
Thread consumer;
consumer.semaphore = &semaphore;
consumer.amountToConsume = 1;
consumer.timeout = 1000;
// start the thread and wait for it to start consuming
consumer.start();
consumer.startup.acquire();
// try to consume more than the thread we started is, and provide a longer
// timeout... we should timeout, not wait indefinitely
QVERIFY(!semaphore.tryAcquire(consumer.amountToConsume * 2, consumer.timeout * 2));
// the consumer should still be running
QVERIFY(consumer.isRunning() && !consumer.isFinished());
// acquire, and wait for smallConsumer to timeout
semaphore.acquire();
QVERIFY(consumer.wait());
}
void tst_QSemaphore::tryAcquireWithTimeoutForever_data()
{
QTest::addColumn<int>("timeout");
QTest::newRow("-1") << -1;
// tryAcquire is documented to take any negative value as "forever"
QTest::newRow("INT_MIN") << INT_MIN;
}
void tst_QSemaphore::tryAcquireWithTimeoutForever()
{
enum { WaitTime = 1000 };
struct Thread : public QThread {
QSemaphore sem;
void run() override
{
QTest::qWait(WaitTime);
sem.release(2);
}
};
QFETCH(int, timeout);
Thread t;
// sanity check it works if we can immediately acquire
t.sem.release(11);
QVERIFY(t.sem.tryAcquire(1, timeout));
QVERIFY(t.sem.tryAcquire(10, timeout));
// verify that we do wait for at least WaitTime if we can't acquire immediately
QElapsedTimer timer;
timer.start();
t.start();
QVERIFY(t.sem.tryAcquire(1, timeout));
QVERIFY(timer.elapsed() >= WaitTime);
QVERIFY(t.wait());
QCOMPARE(t.sem.available(), 1);
}
const char alphabet[] = "ACGTH";
const int AlphabetSize = sizeof(alphabet) - 1;
const int BufferSize = 4096; // GCD of BufferSize and alphabet size must be 1
char buffer[BufferSize];
const int ProducerChunkSize = 3;
const int ConsumerChunkSize = 7;
const int Multiplier = 10;
// note: the code depends on the fact that DataSize is a multiple of
// ProducerChunkSize, ConsumerChunkSize, and BufferSize
const int DataSize = ProducerChunkSize * ConsumerChunkSize * BufferSize * Multiplier;
QSemaphore freeSpace(BufferSize);
QSemaphore usedSpace;
class Producer : public QThread
{
public:
void run();
};
static const int Timeout = 60 * 1000; // 1min
void Producer::run()
{
for (int i = 0; i < DataSize; ++i) {
QVERIFY(freeSpace.tryAcquire(1, Timeout));
buffer[i % BufferSize] = alphabet[i % AlphabetSize];
usedSpace.release();
}
for (int i = 0; i < DataSize; ++i) {
if ((i % ProducerChunkSize) == 0)
QVERIFY(freeSpace.tryAcquire(ProducerChunkSize, Timeout));
buffer[i % BufferSize] = alphabet[i % AlphabetSize];
if ((i % ProducerChunkSize) == (ProducerChunkSize - 1))
usedSpace.release(ProducerChunkSize);
}
}
class Consumer : public QThread
{
public:
void run();
};
void Consumer::run()
{
for (int i = 0; i < DataSize; ++i) {
usedSpace.acquire();
QCOMPARE(buffer[i % BufferSize], alphabet[i % AlphabetSize]);
freeSpace.release();
}
for (int i = 0; i < DataSize; ++i) {
if ((i % ConsumerChunkSize) == 0)
usedSpace.acquire(ConsumerChunkSize);
QCOMPARE(buffer[i % BufferSize], alphabet[i % AlphabetSize]);
if ((i % ConsumerChunkSize) == (ConsumerChunkSize - 1))
freeSpace.release(ConsumerChunkSize);
}
}
void tst_QSemaphore::producerConsumer()
{
Producer producer;
Consumer consumer;
producer.start();
consumer.start();
producer.wait();
consumer.wait();
}
void tst_QSemaphore::raii()
{
QSemaphore sem;
QCOMPARE(sem.available(), 0);
// basic operation:
{
QSemaphoreReleaser r0;
const QSemaphoreReleaser r1(sem);
const QSemaphoreReleaser r2(sem, 2);
QCOMPARE(r0.semaphore(), nullptr);
QCOMPARE(r1.semaphore(), &sem);
QCOMPARE(r2.semaphore(), &sem);
}
QCOMPARE(sem.available(), 3);
// cancel:
{
const QSemaphoreReleaser r1(sem);
QSemaphoreReleaser r2(sem, 2);
QCOMPARE(r2.cancel(), &sem);
QCOMPARE(r2.semaphore(), nullptr);
}
QCOMPARE(sem.available(), 4);
// move-assignment:
{
const QSemaphoreReleaser r1(sem);
QSemaphoreReleaser r2(sem, 2);
QCOMPARE(sem.available(), 4);
r2 = QSemaphoreReleaser();
QCOMPARE(sem.available(), 6);
r2 = QSemaphoreReleaser(sem, 42);
QCOMPARE(sem.available(), 6);
}
QCOMPARE(sem.available(), 49);
}
QTEST_MAIN(tst_QSemaphore)
#include "tst_qsemaphore.moc"