1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
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 https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28#include <qtconcurrentthreadengine.h>
29#include <qexception.h>
30#include <QThread>
31#include <QElapsedTimer>
32#include <QtTest/QtTest>
33
34using namespace QtConcurrent;
35
36class tst_QtConcurrentThreadEngine: public QObject
37{
38 Q_OBJECT
39private slots:
40 void runDirectly();
41 void result();
42 void runThroughStarter();
43 void cancel();
44 void throttle();
45 void threadCount();
46 void multipleResults();
47 void stresstest();
48 void cancelQueuedSlowUser();
49#ifndef QT_NO_EXCEPTIONS
50 void exceptions();
51#endif
52};
53
54
55class PrintUser : public ThreadEngine<void>
56{
57public:
58 ThreadFunctionResult threadFunction()
59 {
60 QTest::qSleep(ms: 50);
61 QTest::qSleep(ms: 100);
62 return ThreadFinished;
63 }
64};
65
66void tst_QtConcurrentThreadEngine::runDirectly()
67{
68 PrintUser *engine = new PrintUser();
69 QFuture<void> f = engine->startAsynchronously();
70 f.waitForFinished();
71}
72
73class StringResultUser : public ThreadEngine<QString>
74{
75public:
76 typedef QString ResultType;
77 StringResultUser()
78 : done(false) { }
79
80 bool shouldStartThread()
81 {
82 return !done;
83 }
84
85 ThreadFunctionResult threadFunction()
86 {
87 done = true;
88 return ThreadFinished;
89 }
90
91 QString *result()
92 {
93 foo = "Foo";
94 return &foo;
95 }
96 QString foo;
97 bool done;
98};
99
100void tst_QtConcurrentThreadEngine::result()
101{
102 // ThreadEngine will delete 'engine' when it finishes
103 auto engine = new StringResultUser();
104 auto future = engine->startAsynchronously();
105 QCOMPARE(future.result(), QString("Foo"));
106}
107
108class VoidResultUser : public ThreadEngine<void>
109{
110public:
111 bool shouldStartThread()
112 {
113 return !done;
114 }
115
116 ThreadFunctionResult threadFunction()
117 {
118 done = true;
119 return ThreadFinished;
120 }
121
122 void *result()
123 {
124 return 0;
125 }
126 bool done;
127};
128
129void tst_QtConcurrentThreadEngine::runThroughStarter()
130{
131 ThreadEngineStarter<QString> starter = startThreadEngine(threadEngine: new StringResultUser());
132 QFuture<QString> f = starter.startAsynchronously();
133 QCOMPARE(f.result(), QString("Foo"));
134}
135
136class CancelUser : public ThreadEngine<void>
137{
138public:
139 void *result()
140 {
141 return 0;
142 }
143
144 ThreadFunctionResult threadFunction()
145 {
146 while (this->isCanceled() == false)
147 {
148 QTest::qSleep(ms: 10);
149 }
150 return ThreadFinished;
151 }
152};
153
154void tst_QtConcurrentThreadEngine::cancel()
155{
156 {
157 CancelUser *engine = new CancelUser();
158 QFuture<void> f = engine->startAsynchronously();
159 f.cancel();
160 f.waitForFinished();
161 }
162 {
163 CancelUser *engine = new CancelUser();
164 QFuture<void> f = engine->startAsynchronously();
165 QTest::qSleep(ms: 10);
166 f.cancel();
167 f.waitForFinished();
168 }
169}
170
171QAtomicInt count;
172class ThrottleAlwaysUser : public ThreadEngine<void>
173{
174public:
175 ThrottleAlwaysUser()
176 {
177 count.storeRelaxed(newValue: initialCount = 100);
178 finishing = false;
179 }
180
181 bool shouldStartThread()
182 {
183 return !finishing;
184 }
185
186 ThreadFunctionResult threadFunction()
187 {
188 forever {
189 const int local = count.loadRelaxed();
190 if (local == 0) {
191 finishing = true;
192 return ThreadFinished;
193 }
194
195 if (count.testAndSetOrdered(expectedValue: local, newValue: local - 1))
196 break;
197 }
198 return ThrottleThread;
199 }
200
201 bool finishing;
202 int initialCount;
203};
204
205// Test that a user task with a thread function that always
206// want to be throttled still completes. The thread engine
207// should make keep one thread running at all times.
208void tst_QtConcurrentThreadEngine::throttle()
209{
210 const int repeats = 10;
211 for (int i = 0; i < repeats; ++i) {
212 QFuture<void> f = (new ThrottleAlwaysUser())->startAsynchronously();
213 f.waitForFinished();
214 QCOMPARE(count.loadRelaxed(), 0);
215 }
216}
217
218QSet<QThread *> threads;
219QMutex mutex;
220class ThreadCountUser : public ThreadEngine<void>
221{
222public:
223 ThreadCountUser(bool finishImmediately = false)
224 {
225 threads.clear();
226 finishing = finishImmediately;
227 }
228
229 bool shouldStartThread()
230 {
231 return !finishing;
232 }
233
234 ThreadFunctionResult threadFunction()
235 {
236 {
237 QMutexLocker lock(&mutex);
238 threads.insert(value: QThread::currentThread());
239 }
240 QTest::qSleep(ms: 10);
241 finishing = true;
242 return ThreadFinished;
243 }
244
245 bool finishing;
246};
247
248void tst_QtConcurrentThreadEngine::threadCount()
249{
250 const int repeats = 10;
251 for (int i = 0; i < repeats; ++i) {
252 (new ThreadCountUser())->startAsynchronously().waitForFinished();
253 const auto count = threads.count();
254 const auto maxThreadCount = QThreadPool::globalInstance()->maxThreadCount();
255 QVERIFY(count <= maxThreadCount);
256 QVERIFY(!threads.contains(QThread::currentThread()));
257 }
258
259 // Set the finish flag immediately, this should give us one thread only.
260 for (int i = 0; i < repeats; ++i) {
261 (new ThreadCountUser(true /*finishImmediately*/))->startAsynchronously().waitForFinished();
262 const auto count = threads.count();
263 QCOMPARE(count, 1);
264 QVERIFY(!threads.contains(QThread::currentThread()));
265 }
266}
267
268class MultipleResultsUser : public ThreadEngine<int>
269{
270public:
271 bool shouldStartThread()
272 {
273 return false;
274 }
275
276 ThreadFunctionResult threadFunction()
277 {
278 for (int i = 0; i < 10; ++i)
279 this->reportResult(result: &i);
280 return ThreadFinished;
281 }
282};
283
284
285void tst_QtConcurrentThreadEngine::multipleResults()
286{
287 MultipleResultsUser *engine = new MultipleResultsUser();
288 QFuture<int> f = engine->startAsynchronously();
289 QCOMPARE(f.results().count() , 10);
290 QCOMPARE(f.resultAt(0), 0);
291 QCOMPARE(f.resultAt(5), 5);
292 QCOMPARE(f.resultAt(9), 9);
293 f.waitForFinished();
294}
295
296
297class NoThreadsUser : public ThreadEngine<void>
298{
299public:
300 bool shouldStartThread()
301 {
302 return false;
303 }
304
305 ThreadFunctionResult threadFunction()
306 {
307 return ThreadFinished;
308 }
309
310 void *result()
311 {
312 return 0;
313 }
314};
315
316void tst_QtConcurrentThreadEngine::stresstest()
317{
318 const int times = 20000;
319
320 for (int i = 0; i < times; ++i) {
321 VoidResultUser *engine = new VoidResultUser();
322 engine->startAsynchronously().waitForFinished();
323 }
324
325 for (int i = 0; i < times; ++i) {
326 VoidResultUser *engine = new VoidResultUser();
327 engine->startAsynchronously();
328 }
329
330 for (int i = 0; i < times; ++i) {
331 VoidResultUser *engine = new VoidResultUser();
332 engine->startAsynchronously().waitForFinished();
333 }
334}
335
336const int sleepTime = 20;
337class SlowUser : public ThreadEngine<void>
338{
339public:
340 bool shouldStartThread() { return false; }
341 ThreadFunctionResult threadFunction() { QTest::qSleep(ms: sleepTime); return ThreadFinished; }
342};
343
344void tst_QtConcurrentThreadEngine::cancelQueuedSlowUser()
345{
346 const int times = 100;
347
348 QElapsedTimer t;
349 t.start();
350
351 {
352 QList<QFuture<void> > futures;
353 for (int i = 0; i < times; ++i) {
354 SlowUser *engine = new SlowUser();
355 futures.append(t: engine->startAsynchronously());
356 }
357
358 foreach(QFuture<void> future, futures)
359 future.cancel();
360 }
361
362 QVERIFY(t.elapsed() < (sleepTime * times) / 2);
363}
364
365#ifndef QT_NO_EXCEPTIONS
366
367class QtConcurrentExceptionThrower : public ThreadEngine<void>
368{
369public:
370 QtConcurrentExceptionThrower(QThread *blockThread = 0)
371 {
372 this->blockThread = blockThread;
373 }
374
375 ThreadFunctionResult threadFunction()
376 {
377 QTest::qSleep(ms: 50);
378 throw QException();
379 return ThreadFinished;
380 }
381 QThread *blockThread;
382};
383
384class UnrelatedExceptionThrower : public ThreadEngine<void>
385{
386public:
387 UnrelatedExceptionThrower(QThread *blockThread = 0)
388 {
389 this->blockThread = blockThread;
390 }
391
392 ThreadFunctionResult threadFunction()
393 {
394 QTest::qSleep(ms: 50);
395 throw int();
396 return ThreadFinished;
397 }
398 QThread *blockThread;
399};
400
401void tst_QtConcurrentThreadEngine::exceptions()
402{
403 {
404 bool caught = false;
405 try {
406 QtConcurrentExceptionThrower *e = new QtConcurrentExceptionThrower();
407 QFuture<void> f = e->startAsynchronously();
408 f.waitForFinished();
409 } catch (const QException &) {
410 caught = true;
411 }
412 QVERIFY2(caught, "did not get exception");
413 }
414
415 {
416 bool caught = false;
417 try {
418 UnrelatedExceptionThrower *e = new UnrelatedExceptionThrower();
419 QFuture<void> f = e->startAsynchronously();
420 f.waitForFinished();
421 } catch (const QUnhandledException &) {
422 caught = true;
423 }
424 QVERIFY2(caught, "did not get exception");
425 }
426}
427
428#endif
429
430QTEST_MAIN(tst_QtConcurrentThreadEngine)
431
432#include "tst_qtconcurrentthreadengine.moc"
433

source code of qtbase/tests/auto/concurrent/qtconcurrentthreadengine/tst_qtconcurrentthreadengine.cpp