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 | |
34 | using namespace QtConcurrent; |
35 | |
36 | class tst_QtConcurrentThreadEngine: public QObject |
37 | { |
38 | Q_OBJECT |
39 | private 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 | |
55 | class PrintUser : public ThreadEngine<void> |
56 | { |
57 | public: |
58 | ThreadFunctionResult threadFunction() |
59 | { |
60 | QTest::qSleep(ms: 50); |
61 | QTest::qSleep(ms: 100); |
62 | return ThreadFinished; |
63 | } |
64 | }; |
65 | |
66 | void tst_QtConcurrentThreadEngine::runDirectly() |
67 | { |
68 | PrintUser *engine = new PrintUser(); |
69 | QFuture<void> f = engine->startAsynchronously(); |
70 | f.waitForFinished(); |
71 | } |
72 | |
73 | class StringResultUser : public ThreadEngine<QString> |
74 | { |
75 | public: |
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 | |
100 | void 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 | |
108 | class VoidResultUser : public ThreadEngine<void> |
109 | { |
110 | public: |
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 | |
129 | void 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 | |
136 | class CancelUser : public ThreadEngine<void> |
137 | { |
138 | public: |
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 | |
154 | void 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 | |
171 | QAtomicInt count; |
172 | class ThrottleAlwaysUser : public ThreadEngine<void> |
173 | { |
174 | public: |
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. |
208 | void 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 | |
218 | QSet<QThread *> threads; |
219 | QMutex mutex; |
220 | class ThreadCountUser : public ThreadEngine<void> |
221 | { |
222 | public: |
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 | |
248 | void 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 | |
268 | class MultipleResultsUser : public ThreadEngine<int> |
269 | { |
270 | public: |
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 | |
285 | void 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 | |
297 | class NoThreadsUser : public ThreadEngine<void> |
298 | { |
299 | public: |
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 | |
316 | void 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 | |
336 | const int sleepTime = 20; |
337 | class SlowUser : public ThreadEngine<void> |
338 | { |
339 | public: |
340 | bool shouldStartThread() { return false; } |
341 | ThreadFunctionResult threadFunction() { QTest::qSleep(ms: sleepTime); return ThreadFinished; } |
342 | }; |
343 | |
344 | void 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 | |
367 | class QtConcurrentExceptionThrower : public ThreadEngine<void> |
368 | { |
369 | public: |
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 | |
384 | class UnrelatedExceptionThrower : public ThreadEngine<void> |
385 | { |
386 | public: |
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 | |
401 | void 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 | |
430 | QTEST_MAIN(tst_QtConcurrentThreadEngine) |
431 | |
432 | #include "tst_qtconcurrentthreadengine.moc" |
433 | |