1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the test suite of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 
43 #include <QtTest/QtTest>
44 
45 
46 #include <qcoreapplication.h>
47 #include <qthread.h>
48 #include <qsemaphore.h>
49 
50 //TESTED_CLASS=
51 //TESTED_FILES=
52 
53 class tst_QSemaphore : public QObject
54 {
55     Q_OBJECT
56 
57 public:
58     tst_QSemaphore();
59     ~tst_QSemaphore();
60 
61 private slots:
62     void acquire();
63     void tryAcquire();
64     void tryAcquireWithTimeout_data();
65     void tryAcquireWithTimeout();
66     void tryAcquireWithTimeoutStarvation();
67     void release();
68     void available();
69     void producerConsumer();
70 };
71 
72 static QSemaphore *semaphore = 0;
73 
tst_QSemaphore()74 tst_QSemaphore::tst_QSemaphore()
75 { }
76 
~tst_QSemaphore()77 tst_QSemaphore::~tst_QSemaphore()
78 { }
79 
80 class ThreadOne : public QThread
81 {
82 public:
ThreadOne()83     ThreadOne() {}
84 
85 protected:
run()86     void run()
87     {
88 	int i = 0;
89 	while ( i < 100 ) {
90             semaphore->acquire();
91 	    i++;
92             semaphore->release();
93 	}
94     }
95 };
96 
97 class ThreadN : public QThread
98 {
99     int N;
100 
101 public:
ThreadN(int n)102     ThreadN(int n) :N(n) { }
103 
104 protected:
run()105     void run()
106     {
107 	int i = 0;
108 	while ( i < 100 ) {
109             semaphore->acquire(N);
110 	    i++;
111             semaphore->release(N);
112 	}
113     }
114 };
115 
acquire()116 void tst_QSemaphore::acquire()
117 {
118     {
119         // old incrementOne() test
120         QVERIFY(!semaphore);
121         semaphore = new QSemaphore;
122         // make some "thing" available
123         semaphore->release();
124 
125         ThreadOne t1;
126         ThreadOne t2;
127 
128         t1.start();
129         t2.start();
130 
131         QVERIFY(t1.wait(4000));
132         QVERIFY(t2.wait(4000));
133 
134         delete semaphore;
135         semaphore = 0;
136     }
137 
138     // old incrementN() test
139     {
140         QVERIFY(!semaphore);
141         semaphore = new QSemaphore;
142         // make 4 "things" available
143         semaphore->release(4);
144 
145         ThreadN t1(2);
146         ThreadN t2(3);
147 
148         t1.start();
149         t2.start();
150 
151         QVERIFY(t1.wait(4000));
152         QVERIFY(t2.wait(4000));
153 
154         delete semaphore;
155         semaphore = 0;
156     }
157 
158     QSemaphore semaphore;
159 
160     QCOMPARE(semaphore.available(), 0);
161     semaphore.release();
162     QCOMPARE(semaphore.available(), 1);
163     semaphore.release();
164     QCOMPARE(semaphore.available(), 2);
165     semaphore.release(10);
166     QCOMPARE(semaphore.available(), 12);
167     semaphore.release(10);
168     QCOMPARE(semaphore.available(), 22);
169 
170     semaphore.acquire();
171     QCOMPARE(semaphore.available(), 21);
172     semaphore.acquire();
173     QCOMPARE(semaphore.available(), 20);
174     semaphore.acquire(10);
175     QCOMPARE(semaphore.available(), 10);
176     semaphore.acquire(10);
177     QCOMPARE(semaphore.available(), 0);
178 }
179 
tryAcquire()180 void tst_QSemaphore::tryAcquire()
181 {
182     QSemaphore semaphore;
183 
184     QCOMPARE(semaphore.available(), 0);
185 
186     semaphore.release();
187     QCOMPARE(semaphore.available(), 1);
188     QVERIFY(!semaphore.tryAcquire(2));
189     QCOMPARE(semaphore.available(), 1);
190 
191     semaphore.release();
192     QCOMPARE(semaphore.available(), 2);
193     QVERIFY(!semaphore.tryAcquire(3));
194     QCOMPARE(semaphore.available(), 2);
195 
196     semaphore.release(10);
197     QCOMPARE(semaphore.available(), 12);
198     QVERIFY(!semaphore.tryAcquire(100));
199     QCOMPARE(semaphore.available(), 12);
200 
201     semaphore.release(10);
202     QCOMPARE(semaphore.available(), 22);
203     QVERIFY(!semaphore.tryAcquire(100));
204     QCOMPARE(semaphore.available(), 22);
205 
206     QVERIFY(semaphore.tryAcquire());
207     QCOMPARE(semaphore.available(), 21);
208 
209     QVERIFY(semaphore.tryAcquire());
210     QCOMPARE(semaphore.available(), 20);
211 
212     QVERIFY(semaphore.tryAcquire(10));
213     QCOMPARE(semaphore.available(), 10);
214 
215     QVERIFY(semaphore.tryAcquire(10));
216     QCOMPARE(semaphore.available(), 0);
217 
218     // should not be able to acquire more
219     QVERIFY(!semaphore.tryAcquire());
220     QCOMPARE(semaphore.available(), 0);
221 
222     QVERIFY(!semaphore.tryAcquire());
223     QCOMPARE(semaphore.available(), 0);
224 
225     QVERIFY(!semaphore.tryAcquire(10));
226     QCOMPARE(semaphore.available(), 0);
227 
228     QVERIFY(!semaphore.tryAcquire(10));
229     QCOMPARE(semaphore.available(), 0);
230 }
231 
tryAcquireWithTimeout_data()232 void tst_QSemaphore::tryAcquireWithTimeout_data()
233 {
234     QTest::addColumn<int>("timeout");
235 
236     QTest::newRow("1s") << 1000;
237     QTest::newRow("10s") << 10000;
238 }
239 
tryAcquireWithTimeout()240 void tst_QSemaphore::tryAcquireWithTimeout()
241 {
242     QFETCH(int, timeout);
243 
244     // timers are not guaranteed to be accurate down to the last millisecond,
245     // so we permit the elapsed times to be up to this far from the expected value.
246     int fuzz = 10;
247 
248     QSemaphore semaphore;
249     QTime time;
250 
251 #define FUZZYCOMPARE(a,e) \
252     do { \
253         int a1 = a; \
254         int e1 = e; \
255         QVERIFY2(qAbs(a1-e1) < fuzz, \
256             qPrintable(QString("(%1=%2) is more than %3 milliseconds different from (%4=%5)") \
257                         .arg(#a).arg(a1).arg(fuzz).arg(#e).arg(e1))); \
258     } while (0)
259 
260     QCOMPARE(semaphore.available(), 0);
261 
262     semaphore.release();
263     QCOMPARE(semaphore.available(), 1);
264     time.start();
265     QVERIFY(!semaphore.tryAcquire(2, timeout));
266     FUZZYCOMPARE(time.elapsed(), timeout);
267     QCOMPARE(semaphore.available(), 1);
268 
269     semaphore.release();
270     QCOMPARE(semaphore.available(), 2);
271     time.start();
272     QVERIFY(!semaphore.tryAcquire(3, timeout));
273     FUZZYCOMPARE(time.elapsed(), timeout);
274     QCOMPARE(semaphore.available(), 2);
275 
276     semaphore.release(10);
277     QCOMPARE(semaphore.available(), 12);
278     time.start();
279     QVERIFY(!semaphore.tryAcquire(100, timeout));
280     FUZZYCOMPARE(time.elapsed(), timeout);
281     QCOMPARE(semaphore.available(), 12);
282 
283     semaphore.release(10);
284     QCOMPARE(semaphore.available(), 22);
285     time.start();
286     QVERIFY(!semaphore.tryAcquire(100, timeout));
287     FUZZYCOMPARE(time.elapsed(), timeout);
288     QCOMPARE(semaphore.available(), 22);
289 
290     time.start();
291     QVERIFY(semaphore.tryAcquire(1, timeout));
292     FUZZYCOMPARE(time.elapsed(), 0);
293     QCOMPARE(semaphore.available(), 21);
294 
295     time.start();
296     QVERIFY(semaphore.tryAcquire(1, timeout));
297     FUZZYCOMPARE(time.elapsed(), 0);
298     QCOMPARE(semaphore.available(), 20);
299 
300     time.start();
301     QVERIFY(semaphore.tryAcquire(10, timeout));
302     FUZZYCOMPARE(time.elapsed(), 0);
303     QCOMPARE(semaphore.available(), 10);
304 
305     time.start();
306     QVERIFY(semaphore.tryAcquire(10, timeout));
307     FUZZYCOMPARE(time.elapsed(), 0);
308     QCOMPARE(semaphore.available(), 0);
309 
310     // should not be able to acquire more
311     time.start();
312     QVERIFY(!semaphore.tryAcquire(1, timeout));
313     FUZZYCOMPARE(time.elapsed(), timeout);
314     QCOMPARE(semaphore.available(), 0);
315 
316     time.start();
317     QVERIFY(!semaphore.tryAcquire(1, timeout));
318     FUZZYCOMPARE(time.elapsed(), timeout);
319     QCOMPARE(semaphore.available(), 0);
320 
321     time.start();
322     QVERIFY(!semaphore.tryAcquire(10, timeout));
323     FUZZYCOMPARE(time.elapsed(), timeout);
324     QCOMPARE(semaphore.available(), 0);
325 
326     time.start();
327     QVERIFY(!semaphore.tryAcquire(10, timeout));
328     FUZZYCOMPARE(time.elapsed(), timeout);
329     QCOMPARE(semaphore.available(), 0);
330 
331 #undef FUZZYCOMPARE
332 }
333 
tryAcquireWithTimeoutStarvation()334 void tst_QSemaphore::tryAcquireWithTimeoutStarvation()
335 {
336     class Thread : public QThread
337     {
338     public:
339         QSemaphore startup;
340         QSemaphore *semaphore;
341         int amountToConsume, timeout;
342 
343         void run()
344         {
345             startup.release();
346             forever {
347                 if (!semaphore->tryAcquire(amountToConsume, timeout))
348                     break;
349                 semaphore->release(amountToConsume);
350             }
351         }
352     };
353 
354     QSemaphore semaphore;
355     semaphore.release(1);
356 
357     Thread consumer;
358     consumer.semaphore = &semaphore;
359     consumer.amountToConsume = 1;
360     consumer.timeout = 1000;
361 
362     // start the thread and wait for it to start consuming
363     consumer.start();
364     consumer.startup.acquire();
365 
366     // try to consume more than the thread we started is, and provide a longer
367     // timeout... we should timeout, not wait indefinitely
368     QVERIFY(!semaphore.tryAcquire(consumer.amountToConsume * 2, consumer.timeout * 2));
369 
370     // the consumer should still be running
371     QVERIFY(consumer.isRunning() && !consumer.isFinished());
372 
373     // acquire, and wait for smallConsumer to timeout
374     semaphore.acquire();
375     QVERIFY(consumer.wait());
376 }
377 
release()378 void tst_QSemaphore::release()
379 { DEPENDS_ON("acquire"); }
380 
available()381 void tst_QSemaphore::available()
382 { DEPENDS_ON("acquire"); }
383 
384 const char alphabet[] = "ACGTH";
385 const int AlphabetSize = sizeof(alphabet) - 1;
386 
387 const int BufferSize = 4096; // GCD of BufferSize and alphabet size must be 1
388 char buffer[BufferSize];
389 
390 #ifndef Q_OS_WINCE
391 const int ProducerChunkSize = 3;
392 const int ConsumerChunkSize = 7;
393 const int Multiplier = 10;
394 #else
395 const int ProducerChunkSize = 2;
396 const int ConsumerChunkSize = 5;
397 const int Multiplier = 3;
398 #endif
399 
400 // note: the code depends on the fact that DataSize is a multiple of
401 // ProducerChunkSize, ConsumerChunkSize, and BufferSize
402 const int DataSize = ProducerChunkSize * ConsumerChunkSize * BufferSize * Multiplier;
403 
404 QSemaphore freeSpace(BufferSize);
405 QSemaphore usedSpace;
406 
407 class Producer : public QThread
408 {
409 public:
410     void run();
411 };
412 
run()413 void Producer::run()
414 {
415     for (int i = 0; i < DataSize; ++i) {
416         freeSpace.acquire();
417         buffer[i % BufferSize] = alphabet[i % AlphabetSize];
418         usedSpace.release();
419     }
420     for (int i = 0; i < DataSize; ++i) {
421         if ((i % ProducerChunkSize) == 0)
422             freeSpace.acquire(ProducerChunkSize);
423         buffer[i % BufferSize] = alphabet[i % AlphabetSize];
424         if ((i % ProducerChunkSize) == (ProducerChunkSize - 1))
425             usedSpace.release(ProducerChunkSize);
426     }
427 }
428 
429 class Consumer : public QThread
430 {
431 public:
432     void run();
433 };
434 
run()435 void Consumer::run()
436 {
437     for (int i = 0; i < DataSize; ++i) {
438         usedSpace.acquire();
439         QCOMPARE(buffer[i % BufferSize], alphabet[i % AlphabetSize]);
440         freeSpace.release();
441     }
442     for (int i = 0; i < DataSize; ++i) {
443         if ((i % ConsumerChunkSize) == 0)
444             usedSpace.acquire(ConsumerChunkSize);
445         QCOMPARE(buffer[i % BufferSize], alphabet[i % AlphabetSize]);
446         if ((i % ConsumerChunkSize) == (ConsumerChunkSize - 1))
447             freeSpace.release(ConsumerChunkSize);
448     }
449 }
450 
producerConsumer()451 void tst_QSemaphore::producerConsumer()
452 {
453     Producer producer;
454     Consumer consumer;
455     producer.start();
456     consumer.start();
457     producer.wait();
458     consumer.wait();
459 }
460 
461 QTEST_MAIN(tst_QSemaphore)
462 #include "tst_qsemaphore.moc"
463