1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Copyright (C) 2016 Intel Corporation. |
5 | ** Contact: https://www.qt.io/licensing/ |
6 | ** |
7 | ** This file is part of the test suite of the Qt Toolkit. |
8 | ** |
9 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
10 | ** Commercial License Usage |
11 | ** Licensees holding valid commercial Qt licenses may use this file in |
12 | ** accordance with the commercial license agreement provided with the |
13 | ** Software or, alternatively, in accordance with the terms contained in |
14 | ** a written agreement between you and The Qt Company. For licensing terms |
15 | ** and conditions see https://www.qt.io/terms-conditions. For further |
16 | ** information use the contact form at https://www.qt.io/contact-us. |
17 | ** |
18 | ** GNU General Public License Usage |
19 | ** Alternatively, this file may be used under the terms of the GNU |
20 | ** General Public License version 3 as published by the Free Software |
21 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
22 | ** included in the packaging of this file. Please review the following |
23 | ** information to ensure the GNU General Public License requirements will |
24 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
25 | ** |
26 | ** $QT_END_LICENSE$ |
27 | ** |
28 | ****************************************************************************/ |
29 | |
30 | #ifdef QT_GUI_LIB |
31 | # include <QtGui/QGuiApplication> |
32 | #else |
33 | # include <QtCore/QCoreApplication> |
34 | #endif |
35 | |
36 | #include <QtCore/private/qglobal_p.h> |
37 | #include <QtTest/QtTest> |
38 | |
39 | #include <qtimer.h> |
40 | #include <qthread.h> |
41 | #include <qelapsedtimer.h> |
42 | |
43 | #if defined Q_OS_UNIX |
44 | #include <unistd.h> |
45 | #endif |
46 | |
47 | class tst_QTimer : public QObject |
48 | { |
49 | Q_OBJECT |
50 | public: |
51 | static void initMain(); |
52 | |
53 | private slots: |
54 | void cleanupTestCase(); |
55 | void zeroTimer(); |
56 | void singleShotTimeout(); |
57 | void timeout(); |
58 | void remainingTime(); |
59 | void remainingTimeInitial_data(); |
60 | void remainingTimeInitial(); |
61 | void remainingTimeDuringActivation_data(); |
62 | void remainingTimeDuringActivation(); |
63 | void basic_chrono(); |
64 | void livelock_data(); |
65 | void livelock(); |
66 | void timerInfiniteRecursion_data(); |
67 | void timerInfiniteRecursion(); |
68 | void recurringTimer_data(); |
69 | void recurringTimer(); |
70 | void deleteLaterOnQTimer(); // long name, don't want to shadow QObject::deleteLater() |
71 | void moveToThread(); |
72 | void restartedTimerFiresTooSoon(); |
73 | void timerFiresOnlyOncePerProcessEvents_data(); |
74 | void timerFiresOnlyOncePerProcessEvents(); |
75 | void timerIdPersistsAfterThreadExit(); |
76 | void cancelLongTimer(); |
77 | void singleShotStaticFunctionZeroTimeout(); |
78 | void recurseOnTimeoutAndStopTimer(); |
79 | void singleShotToFunctors(); |
80 | void singleShot_chrono(); |
81 | void singleShot_static(); |
82 | void crossThreadSingleShotToFunctor(); |
83 | void timerOrder(); |
84 | void timerOrder_data(); |
85 | void timerOrderBackgroundThread(); |
86 | void timerOrderBackgroundThread_data() { timerOrder_data(); } |
87 | |
88 | void dontBlockEvents(); |
89 | void postedEventsShouldNotStarveTimers(); |
90 | void callOnTimeout(); |
91 | }; |
92 | |
93 | void tst_QTimer::zeroTimer() |
94 | { |
95 | QTimer timer; |
96 | timer.setInterval(0); |
97 | |
98 | QSignalSpy timeoutSpy(&timer, &QTimer::timeout); |
99 | timer.start(); |
100 | |
101 | QCoreApplication::processEvents(); |
102 | |
103 | QCOMPARE(timeoutSpy.count(), 1); |
104 | } |
105 | |
106 | void tst_QTimer::singleShotTimeout() |
107 | { |
108 | QTimer timer; |
109 | timer.setSingleShot(true); |
110 | |
111 | QSignalSpy timeoutSpy(&timer, &QTimer::timeout); |
112 | timer.start(msec: 100); |
113 | |
114 | QVERIFY(timeoutSpy.wait(500)); |
115 | QCOMPARE(timeoutSpy.count(), 1); |
116 | QTest::qWait(ms: 500); |
117 | QCOMPARE(timeoutSpy.count(), 1); |
118 | } |
119 | |
120 | #define TIMEOUT_TIMEOUT 200 |
121 | |
122 | void tst_QTimer::timeout() |
123 | { |
124 | QTimer timer; |
125 | QSignalSpy timeoutSpy(&timer, &QTimer::timeout); |
126 | timer.start(msec: 100); |
127 | |
128 | QCOMPARE(timeoutSpy.count(), 0); |
129 | |
130 | QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.count() > 0, TIMEOUT_TIMEOUT); |
131 | int oldCount = timeoutSpy.count(); |
132 | |
133 | QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.count() > oldCount, TIMEOUT_TIMEOUT); |
134 | } |
135 | |
136 | void tst_QTimer::remainingTime() |
137 | { |
138 | QTimer tested; |
139 | tested.setTimerType(Qt::PreciseTimer); |
140 | |
141 | QTimer tester; |
142 | tester.setTimerType(Qt::PreciseTimer); |
143 | tester.setSingleShot(true); |
144 | |
145 | const int testedInterval = 200; |
146 | const int testerInterval = 50; |
147 | const int expectedRemainingTime = testedInterval - testerInterval; |
148 | |
149 | int testIteration = 0; |
150 | const int desiredTestCount = 2; |
151 | |
152 | auto connection = QObject::connect(sender: &tested, signal: &QTimer::timeout, slot: [&tester]() { |
153 | // We let tested (which isn't a single-shot) run repeatedly, to verify |
154 | // it *does* repeat, and check that the single-shot tester, starting |
155 | // at the same time, does finish first each time, by about the right duration. |
156 | tester.start(); // Start tester again. |
157 | }); |
158 | |
159 | QObject::connect(sender: &tester, signal: &QTimer::timeout, slot: [&]() { |
160 | const int remainingTime = tested.remainingTime(); |
161 | // We expect that remainingTime is at most 150 and not overdue. |
162 | const bool remainingTimeInRange = remainingTime > 0 |
163 | && remainingTime <= expectedRemainingTime; |
164 | if (remainingTimeInRange) |
165 | ++testIteration; |
166 | else |
167 | testIteration = desiredTestCount; // We are going to fail on QVERIFY2() |
168 | // below, so we don't want to iterate |
169 | // anymore and quickly exit the QTRY_...() |
170 | // with this failure. |
171 | if (testIteration == desiredTestCount) |
172 | QObject::disconnect(connection); // Last iteration, don't start tester again. |
173 | QVERIFY2(remainingTimeInRange, qPrintable("Remaining time " |
174 | + QByteArray::number(remainingTime) + "ms outside expected range (0ms, " |
175 | + QByteArray::number(expectedRemainingTime) + "ms]" )); |
176 | }); |
177 | |
178 | tested.start(msec: testedInterval); |
179 | tester.start(msec: testerInterval); // Start tester for the 1st time. |
180 | |
181 | // Test it desiredTestCount times, give it reasonable amount of time |
182 | // (twice as much as needed). |
183 | QTRY_COMPARE_WITH_TIMEOUT(testIteration, desiredTestCount, |
184 | testedInterval * desiredTestCount * 2); |
185 | } |
186 | |
187 | void tst_QTimer::remainingTimeInitial_data() |
188 | { |
189 | QTest::addColumn<int>(name: "startTimeMs" ); |
190 | QTest::addColumn<Qt::TimerType>(name: "timerType" ); |
191 | |
192 | QTest::addRow(format: "precise time 0ms" ) << 0 << Qt::PreciseTimer; |
193 | QTest::addRow(format: "precise time 1ms" ) << 1 << Qt::PreciseTimer; |
194 | QTest::addRow(format: "precise time 10ms" ) << 10 << Qt::PreciseTimer; |
195 | |
196 | QTest::addRow(format: "coarse time 0ms" ) << 0 << Qt::CoarseTimer; |
197 | QTest::addRow(format: "coarse time 1ms" ) << 1 << Qt::CoarseTimer; |
198 | QTest::addRow(format: "coarse time 10ms" ) << 10 << Qt::CoarseTimer; |
199 | } |
200 | |
201 | void tst_QTimer::remainingTimeInitial() |
202 | { |
203 | QFETCH(int, startTimeMs); |
204 | QFETCH(Qt::TimerType, timerType); |
205 | |
206 | QTimer timer; |
207 | timer.setTimerType(timerType); |
208 | timer.start(msec: startTimeMs); |
209 | |
210 | const int rt = timer.remainingTime(); |
211 | QVERIFY2(rt >= 0 && rt <= startTimeMs, qPrintable(QString::number(rt))); |
212 | } |
213 | |
214 | void tst_QTimer::remainingTimeDuringActivation_data() |
215 | { |
216 | QTest::addColumn<bool>(name: "singleShot" ); |
217 | QTest::newRow(dataTag: "repeating" ) << false; |
218 | QTest::newRow(dataTag: "single-shot" ) << true; |
219 | } |
220 | |
221 | void tst_QTimer::remainingTimeDuringActivation() |
222 | { |
223 | QFETCH(bool, singleShot); |
224 | |
225 | QTimer timer; |
226 | timer.setSingleShot(singleShot); |
227 | |
228 | int remainingTime = 0; // not the expected value in either case |
229 | connect(sender: &timer, signal: &QTimer::timeout, |
230 | slot: [&]() { |
231 | remainingTime = timer.remainingTime(); |
232 | }); |
233 | QSignalSpy timeoutSpy(&timer, &QTimer::timeout); |
234 | const int timeout = 20; // 20 ms is short enough and should not round down to 0 in any timer mode |
235 | timer.start(msec: timeout); |
236 | |
237 | QVERIFY(timeoutSpy.wait()); |
238 | if (singleShot) |
239 | QCOMPARE(remainingTime, -1); // timer not running |
240 | else |
241 | QVERIFY2(remainingTime <= timeout && remainingTime > 0, |
242 | qPrintable(QString::number(remainingTime))); |
243 | |
244 | if (!singleShot) { |
245 | // do it again - see QTBUG-46940 |
246 | remainingTime = -1; |
247 | QVERIFY(timeoutSpy.wait()); |
248 | QVERIFY2(remainingTime <= timeout && remainingTime > 0, |
249 | qPrintable(QString::number(remainingTime))); |
250 | } |
251 | } |
252 | |
253 | namespace { |
254 | |
255 | #if __has_include(<chrono>) |
256 | template <typename T> |
257 | std::chrono::milliseconds to_ms(T t) |
258 | { return std::chrono::duration_cast<std::chrono::milliseconds>(t); } |
259 | #endif |
260 | |
261 | } // unnamed namespace |
262 | |
263 | void tst_QTimer::basic_chrono() |
264 | { |
265 | #if !__has_include(<chrono>) |
266 | QSKIP("This test requires C++11 <chrono> support" ); |
267 | #else |
268 | // duplicates zeroTimer, singleShotTimeout, interval and remainingTime |
269 | using namespace std::chrono; |
270 | QTimer timer; |
271 | QSignalSpy timeoutSpy(&timer, &QTimer::timeout); |
272 | timer.setInterval(to_ms(t: nanoseconds(0))); |
273 | timer.start(); |
274 | QCOMPARE(timer.intervalAsDuration().count(), milliseconds::rep(0)); |
275 | QCOMPARE(timer.remainingTimeAsDuration().count(), milliseconds::rep(0)); |
276 | |
277 | QCoreApplication::processEvents(); |
278 | |
279 | QCOMPARE(timeoutSpy.count(), 1); |
280 | |
281 | timeoutSpy.clear(); |
282 | timer.start(value: milliseconds(100)); |
283 | QCOMPARE(timeoutSpy.count(), 0); |
284 | |
285 | QVERIFY(timeoutSpy.wait(TIMEOUT_TIMEOUT)); |
286 | QVERIFY(timeoutSpy.count() > 0); |
287 | int oldCount = timeoutSpy.count(); |
288 | |
289 | QVERIFY(timeoutSpy.wait(TIMEOUT_TIMEOUT)); |
290 | QVERIFY(timeoutSpy.count() > oldCount); |
291 | |
292 | timeoutSpy.clear(); |
293 | timer.start(value: to_ms(t: microseconds(200000))); |
294 | QCOMPARE(timer.intervalAsDuration().count(), milliseconds::rep(200)); |
295 | QTest::qWait(ms: 50); |
296 | QCOMPARE(timeoutSpy.count(), 0); |
297 | |
298 | milliseconds rt = timer.remainingTimeAsDuration(); |
299 | QVERIFY2(rt.count() >= 50 && rt.count() <= 200, qPrintable(QString::number(rt.count()))); |
300 | |
301 | timeoutSpy.clear(); |
302 | timer.setSingleShot(true); |
303 | timer.start(value: milliseconds(100)); |
304 | QVERIFY(timeoutSpy.wait(TIMEOUT_TIMEOUT)); |
305 | QCOMPARE(timeoutSpy.count(), 1); |
306 | QTest::qWait(ms: 500); |
307 | QCOMPARE(timeoutSpy.count(), 1); |
308 | #endif |
309 | } |
310 | |
311 | void tst_QTimer::livelock_data() |
312 | { |
313 | QTest::addColumn<int>(name: "interval" ); |
314 | QTest::newRow(dataTag: "zero timer" ) << 0; |
315 | QTest::newRow(dataTag: "non-zero timer" ) << 1; |
316 | QTest::newRow(dataTag: "longer than sleep" ) << 20; |
317 | } |
318 | |
319 | /*! |
320 | * |
321 | * DO NOT "FIX" THIS TEST! it is written like this for a reason, do |
322 | * not *change it without first dicussing it with its maintainers. |
323 | * |
324 | */ |
325 | class LiveLockTester : public QObject |
326 | { |
327 | public: |
328 | LiveLockTester(int i) |
329 | : interval(i), |
330 | timeoutsForFirst(0), timeoutsForExtra(0), timeoutsForSecond(0), |
331 | postEventAtRightTime(false) |
332 | { |
333 | firstTimerId = startTimer(interval); |
334 | extraTimerId = startTimer(interval: interval + 80); |
335 | secondTimerId = -1; // started later |
336 | } |
337 | |
338 | bool event(QEvent *e) { |
339 | if (e->type() == 4002) { |
340 | // got the posted event |
341 | if (timeoutsForFirst == 1 && timeoutsForSecond == 0) |
342 | postEventAtRightTime = true; |
343 | return true; |
344 | } |
345 | return QObject::event(event: e); |
346 | } |
347 | |
348 | void timerEvent(QTimerEvent *te) { |
349 | if (te->timerId() == firstTimerId) { |
350 | if (++timeoutsForFirst == 1) { |
351 | killTimer(id: extraTimerId); |
352 | extraTimerId = -1; |
353 | QCoreApplication::postEvent(receiver: this, event: new QEvent(static_cast<QEvent::Type>(4002))); |
354 | secondTimerId = startTimer(interval); |
355 | } |
356 | } else if (te->timerId() == secondTimerId) { |
357 | ++timeoutsForSecond; |
358 | } else if (te->timerId() == extraTimerId) { |
359 | ++timeoutsForExtra; |
360 | } |
361 | |
362 | // sleep for 2ms |
363 | QTest::qSleep(ms: 2); |
364 | killTimer(id: te->timerId()); |
365 | } |
366 | |
367 | const int interval; |
368 | int firstTimerId; |
369 | int secondTimerId; |
370 | int ; |
371 | int timeoutsForFirst; |
372 | int ; |
373 | int timeoutsForSecond; |
374 | bool postEventAtRightTime; |
375 | }; |
376 | |
377 | void tst_QTimer::livelock() |
378 | { |
379 | /* |
380 | New timers created in timer event handlers should not be sent |
381 | until the next iteration of the eventloop. Note: this test |
382 | depends on the fact that we send posted events before timer |
383 | events (since new posted events are not sent until the next |
384 | iteration of the eventloop either). |
385 | */ |
386 | QFETCH(int, interval); |
387 | LiveLockTester tester(interval); |
388 | QTest::qWait(ms: 180); // we have to use wait here, since we're testing timers with a non-zero timeout |
389 | QTRY_COMPARE(tester.timeoutsForFirst, 1); |
390 | QCOMPARE(tester.timeoutsForExtra, 0); |
391 | QTRY_COMPARE(tester.timeoutsForSecond, 1); |
392 | QVERIFY(tester.postEventAtRightTime); |
393 | } |
394 | |
395 | class TimerInfiniteRecursionObject : public QObject |
396 | { |
397 | public: |
398 | bool inTimerEvent; |
399 | bool timerEventRecursed; |
400 | int interval; |
401 | |
402 | TimerInfiniteRecursionObject(int interval) |
403 | : inTimerEvent(false), timerEventRecursed(false), interval(interval) |
404 | { } |
405 | |
406 | void timerEvent(QTimerEvent *timerEvent) |
407 | { |
408 | timerEventRecursed = inTimerEvent; |
409 | if (timerEventRecursed) { |
410 | // bug detected! |
411 | return; |
412 | } |
413 | |
414 | inTimerEvent = true; |
415 | |
416 | QEventLoop eventLoop; |
417 | QTimer::singleShot(msec: qMax(a: 100, b: interval * 2), receiver: &eventLoop, SLOT(quit())); |
418 | eventLoop.exec(); |
419 | |
420 | inTimerEvent = false; |
421 | |
422 | killTimer(id: timerEvent->timerId()); |
423 | } |
424 | }; |
425 | |
426 | void tst_QTimer::timerInfiniteRecursion_data() |
427 | { |
428 | QTest::addColumn<int>(name: "interval" ); |
429 | QTest::newRow(dataTag: "zero timer" ) << 0; |
430 | QTest::newRow(dataTag: "non-zero timer" ) << 1; |
431 | QTest::newRow(dataTag: "10ms timer" ) << 10; |
432 | QTest::newRow(dataTag: "11ms timer" ) << 11; |
433 | QTest::newRow(dataTag: "100ms timer" ) << 100; |
434 | QTest::newRow(dataTag: "1s timer" ) << 1000; |
435 | } |
436 | |
437 | |
438 | void tst_QTimer::timerInfiniteRecursion() |
439 | { |
440 | QFETCH(int, interval); |
441 | TimerInfiniteRecursionObject object(interval); |
442 | (void) object.startTimer(interval); |
443 | |
444 | QEventLoop eventLoop; |
445 | QTimer::singleShot(msec: qMax(a: 100, b: interval * 2), receiver: &eventLoop, SLOT(quit())); |
446 | eventLoop.exec(); |
447 | |
448 | QVERIFY(!object.timerEventRecursed); |
449 | } |
450 | |
451 | class RecurringTimerObject : public QObject |
452 | { |
453 | Q_OBJECT |
454 | public: |
455 | int times; |
456 | int target; |
457 | bool recurse; |
458 | |
459 | RecurringTimerObject(int target) |
460 | : times(0), target(target), recurse(false) |
461 | { } |
462 | |
463 | void timerEvent(QTimerEvent *timerEvent) |
464 | { |
465 | if (++times == target) { |
466 | killTimer(id: timerEvent->timerId()); |
467 | emit done(); |
468 | } if (recurse) { |
469 | QEventLoop eventLoop; |
470 | QTimer::singleShot(msec: 100, receiver: &eventLoop, SLOT(quit())); |
471 | eventLoop.exec(); |
472 | } |
473 | } |
474 | |
475 | signals: |
476 | void done(); |
477 | }; |
478 | |
479 | void tst_QTimer::recurringTimer_data() |
480 | { |
481 | QTest::addColumn<int>(name: "interval" ); |
482 | QTest::addColumn<bool>(name: "recurse" ); |
483 | // make sure that eventloop recursion doesn't affect timer recurrence |
484 | QTest::newRow(dataTag: "zero timer, don't recurse" ) << 0 << false; |
485 | QTest::newRow(dataTag: "zero timer, recurse" ) << 0 << true; |
486 | QTest::newRow(dataTag: "non-zero timer, don't recurse" ) << 1 << false; |
487 | QTest::newRow(dataTag: "non-zero timer, recurse" ) << 1 << true; |
488 | } |
489 | |
490 | void tst_QTimer::recurringTimer() |
491 | { |
492 | const int target = 5; |
493 | QFETCH(int, interval); |
494 | QFETCH(bool, recurse); |
495 | |
496 | RecurringTimerObject object(target); |
497 | object.recurse = recurse; |
498 | QSignalSpy doneSpy(&object, &RecurringTimerObject::done); |
499 | |
500 | (void) object.startTimer(interval); |
501 | QVERIFY(doneSpy.wait()); |
502 | |
503 | QCOMPARE(object.times, target); |
504 | } |
505 | |
506 | void tst_QTimer::deleteLaterOnQTimer() |
507 | { |
508 | QTimer *timer = new QTimer; |
509 | connect(sender: timer, SIGNAL(timeout()), receiver: timer, SLOT(deleteLater())); |
510 | QSignalSpy destroyedSpy(timer, &QObject::destroyed); |
511 | timer->setInterval(1); |
512 | timer->setSingleShot(true); |
513 | timer->start(); |
514 | QPointer<QTimer> pointer = timer; |
515 | QVERIFY(destroyedSpy.wait()); |
516 | QVERIFY(pointer.isNull()); |
517 | } |
518 | |
519 | #define MOVETOTHREAD_TIMEOUT 200 |
520 | #define MOVETOTHREAD_WAIT 300 |
521 | |
522 | void tst_QTimer::moveToThread() |
523 | { |
524 | #if defined(Q_OS_WIN32) |
525 | QSKIP("Does not work reliably on Windows :(" ); |
526 | #elif defined(Q_OS_MACOS) |
527 | QSKIP("Does not work reliably on macOS 10.12+ (QTBUG-59679)" ); |
528 | #endif |
529 | QTimer ti1; |
530 | QTimer ti2; |
531 | ti1.start(MOVETOTHREAD_TIMEOUT); |
532 | ti2.start(MOVETOTHREAD_TIMEOUT); |
533 | QVERIFY((ti1.timerId() & 0xffffff) != (ti2.timerId() & 0xffffff)); |
534 | QThread tr; |
535 | ti1.moveToThread(thread: &tr); |
536 | connect(sender: &ti1,SIGNAL(timeout()), receiver: &tr, SLOT(quit())); |
537 | tr.start(); |
538 | QTimer ti3; |
539 | ti3.start(MOVETOTHREAD_TIMEOUT); |
540 | QVERIFY((ti3.timerId() & 0xffffff) != (ti2.timerId() & 0xffffff)); |
541 | QVERIFY((ti3.timerId() & 0xffffff) != (ti1.timerId() & 0xffffff)); |
542 | QTest::qWait(MOVETOTHREAD_WAIT); |
543 | QVERIFY(tr.wait()); |
544 | ti2.stop(); |
545 | QTimer ti4; |
546 | ti4.start(MOVETOTHREAD_TIMEOUT); |
547 | ti3.stop(); |
548 | ti2.start(MOVETOTHREAD_TIMEOUT); |
549 | ti3.start(MOVETOTHREAD_TIMEOUT); |
550 | QVERIFY((ti4.timerId() & 0xffffff) != (ti2.timerId() & 0xffffff)); |
551 | QVERIFY((ti3.timerId() & 0xffffff) != (ti2.timerId() & 0xffffff)); |
552 | QVERIFY((ti3.timerId() & 0xffffff) != (ti1.timerId() & 0xffffff)); |
553 | } |
554 | |
555 | class RestartedTimerFiresTooSoonObject : public QObject |
556 | { |
557 | Q_OBJECT |
558 | |
559 | public: |
560 | QBasicTimer m_timer; |
561 | |
562 | int m_interval; |
563 | QElapsedTimer m_elapsedTimer; |
564 | QEventLoop eventLoop; |
565 | |
566 | inline RestartedTimerFiresTooSoonObject() |
567 | : QObject(), m_interval(0) |
568 | { } |
569 | |
570 | void timerFired() |
571 | { |
572 | static int interval = 1000; |
573 | |
574 | m_interval = interval; |
575 | m_elapsedTimer.start(); |
576 | m_timer.start(msec: interval, obj: this); |
577 | |
578 | // alternate between single-shot and 1 sec |
579 | interval = interval ? 0 : 1000; |
580 | } |
581 | |
582 | void timerEvent(QTimerEvent* ev) |
583 | { |
584 | if (ev->timerId() != m_timer.timerId()) |
585 | return; |
586 | |
587 | m_timer.stop(); |
588 | |
589 | int elapsed = m_elapsedTimer.elapsed(); |
590 | |
591 | if (elapsed < m_interval / 2) { |
592 | // severely too early! |
593 | m_timer.stop(); |
594 | eventLoop.exit(returnCode: -1); |
595 | return; |
596 | } |
597 | |
598 | timerFired(); |
599 | |
600 | // don't do this forever |
601 | static int count = 0; |
602 | if (count++ > 20) { |
603 | m_timer.stop(); |
604 | eventLoop.quit(); |
605 | return; |
606 | } |
607 | } |
608 | }; |
609 | |
610 | void tst_QTimer::restartedTimerFiresTooSoon() |
611 | { |
612 | RestartedTimerFiresTooSoonObject object; |
613 | object.timerFired(); |
614 | QCOMPARE(object.eventLoop.exec(), 0); |
615 | } |
616 | |
617 | class LongLastingSlotClass : public QObject |
618 | { |
619 | Q_OBJECT |
620 | |
621 | public: |
622 | LongLastingSlotClass(QTimer *timer) : count(0), timer(timer) {} |
623 | |
624 | public slots: |
625 | void longLastingSlot() |
626 | { |
627 | // Don't use QTimer for this, because we are testing it. |
628 | QElapsedTimer control; |
629 | control.start(); |
630 | while (control.elapsed() < 200) { |
631 | for (int c = 0; c < 100000; c++) {} // Mindless looping. |
632 | } |
633 | if (++count >= 2) { |
634 | timer->stop(); |
635 | } |
636 | } |
637 | |
638 | public: |
639 | int count; |
640 | QTimer *timer; |
641 | }; |
642 | |
643 | void tst_QTimer::timerFiresOnlyOncePerProcessEvents_data() |
644 | { |
645 | QTest::addColumn<int>(name: "interval" ); |
646 | QTest::newRow(dataTag: "zero timer" ) << 0; |
647 | QTest::newRow(dataTag: "non-zero timer" ) << 10; |
648 | } |
649 | |
650 | void tst_QTimer::timerFiresOnlyOncePerProcessEvents() |
651 | { |
652 | QFETCH(int, interval); |
653 | |
654 | QTimer t; |
655 | LongLastingSlotClass longSlot(&t); |
656 | t.start(msec: interval); |
657 | connect(sender: &t, SIGNAL(timeout()), receiver: &longSlot, SLOT(longLastingSlot())); |
658 | // Loop because there may be other events pending. |
659 | while (longSlot.count == 0) { |
660 | QCoreApplication::processEvents(flags: QEventLoop::WaitForMoreEvents); |
661 | } |
662 | |
663 | QCOMPARE(longSlot.count, 1); |
664 | } |
665 | |
666 | class TimerIdPersistsAfterThreadExitThread : public QThread |
667 | { |
668 | public: |
669 | QTimer *timer; |
670 | int timerId, returnValue; |
671 | |
672 | TimerIdPersistsAfterThreadExitThread() |
673 | : QThread(), timer(0), timerId(-1), returnValue(-1) |
674 | { } |
675 | ~TimerIdPersistsAfterThreadExitThread() |
676 | { |
677 | delete timer; |
678 | } |
679 | |
680 | void run() |
681 | { |
682 | QEventLoop eventLoop; |
683 | timer = new QTimer; |
684 | connect(sender: timer, SIGNAL(timeout()), receiver: &eventLoop, SLOT(quit())); |
685 | timer->start(msec: 100); |
686 | timerId = timer->timerId(); |
687 | returnValue = eventLoop.exec(); |
688 | } |
689 | }; |
690 | |
691 | void tst_QTimer::timerIdPersistsAfterThreadExit() |
692 | { |
693 | TimerIdPersistsAfterThreadExitThread thread; |
694 | thread.start(); |
695 | QVERIFY(thread.wait(30000)); |
696 | QCOMPARE(thread.returnValue, 0); |
697 | |
698 | // even though the thread has exited, and the event dispatcher destroyed, the timer is still |
699 | // "active", meaning the timer id should NOT be reused (i.e. the event dispatcher should not |
700 | // have unregistered it) |
701 | int timerId = thread.startTimer(interval: 100); |
702 | QVERIFY((timerId & 0xffffff) != (thread.timerId & 0xffffff)); |
703 | } |
704 | |
705 | void tst_QTimer::cancelLongTimer() |
706 | { |
707 | QTimer timer; |
708 | timer.setSingleShot(true); |
709 | timer.start(msec: 1000 * 60 * 60); //set timer for 1 hour |
710 | QCoreApplication::processEvents(); |
711 | QVERIFY(timer.isActive()); //if the timer completes immediately with an error, then this will fail |
712 | timer.stop(); |
713 | QVERIFY(!timer.isActive()); |
714 | } |
715 | |
716 | class TimeoutCounter : public QObject |
717 | { |
718 | Q_OBJECT |
719 | public slots: |
720 | void timeout() { ++count; }; |
721 | public: |
722 | int count = 0; |
723 | }; |
724 | |
725 | void tst_QTimer::singleShotStaticFunctionZeroTimeout() |
726 | { |
727 | { |
728 | TimeoutCounter counter; |
729 | |
730 | QTimer::singleShot(msec: 0, receiver: &counter, SLOT(timeout())); |
731 | QTRY_COMPARE(counter.count, 1); |
732 | QTest::qWait(ms: 500); |
733 | QCOMPARE(counter.count, 1); |
734 | } |
735 | |
736 | { |
737 | TimeoutCounter counter; |
738 | |
739 | QTimer::singleShot(interval: 0, receiver: &counter, slot: &TimeoutCounter::timeout); |
740 | QTRY_COMPARE(counter.count, 1); |
741 | QTest::qWait(ms: 500); |
742 | QCOMPARE(counter.count, 1); |
743 | } |
744 | } |
745 | |
746 | class RecursOnTimeoutAndStopTimerTimer : public QObject |
747 | { |
748 | Q_OBJECT |
749 | |
750 | public: |
751 | QTimer *one; |
752 | QTimer *two; |
753 | |
754 | public slots: |
755 | void onetrigger() |
756 | { |
757 | QCoreApplication::processEvents(); |
758 | } |
759 | |
760 | void twotrigger() |
761 | { |
762 | one->stop(); |
763 | } |
764 | }; |
765 | |
766 | void tst_QTimer::recurseOnTimeoutAndStopTimer() |
767 | { |
768 | QEventLoop eventLoop; |
769 | QTimer::singleShot(msec: 1000, receiver: &eventLoop, SLOT(quit())); |
770 | |
771 | RecursOnTimeoutAndStopTimerTimer t; |
772 | t.one = new QTimer(&t); |
773 | t.two = new QTimer(&t); |
774 | |
775 | QObject::connect(sender: t.one, SIGNAL(timeout()), receiver: &t, SLOT(onetrigger())); |
776 | QObject::connect(sender: t.two, SIGNAL(timeout()), receiver: &t, SLOT(twotrigger())); |
777 | |
778 | t.two->setSingleShot(true); |
779 | |
780 | t.one->start(); |
781 | t.two->start(); |
782 | |
783 | (void) eventLoop.exec(); |
784 | |
785 | QVERIFY(!t.one->isActive()); |
786 | QVERIFY(!t.two->isActive()); |
787 | } |
788 | |
789 | struct CountedStruct |
790 | { |
791 | CountedStruct(int *count, QThread *t = nullptr) : count(count), thread(t) { } |
792 | ~CountedStruct() { } |
793 | void operator()() const { ++(*count); if (thread) QCOMPARE(QThread::currentThread(), thread); } |
794 | |
795 | int *count; |
796 | QThread *thread; |
797 | }; |
798 | |
799 | static QScopedPointer<QEventLoop> _e; |
800 | static QThread *_t = nullptr; |
801 | |
802 | class StaticEventLoop |
803 | { |
804 | public: |
805 | static void quitEventLoop() |
806 | { |
807 | quitEventLoop_noexcept(); |
808 | } |
809 | |
810 | static void quitEventLoop_noexcept() noexcept |
811 | { |
812 | QVERIFY(!_e.isNull()); |
813 | _e->quit(); |
814 | if (_t) |
815 | QCOMPARE(QThread::currentThread(), _t); |
816 | } |
817 | }; |
818 | |
819 | void tst_QTimer::singleShotToFunctors() |
820 | { |
821 | int count = 0; |
822 | _e.reset(other: new QEventLoop); |
823 | QEventLoop e; |
824 | |
825 | QTimer::singleShot(interval: 0, slot: CountedStruct(&count)); |
826 | QCoreApplication::processEvents(); |
827 | QCOMPARE(count, 1); |
828 | |
829 | QTimer::singleShot(interval: 0, slot: &StaticEventLoop::quitEventLoop); |
830 | QCOMPARE(_e->exec(), 0); |
831 | |
832 | QTimer::singleShot(interval: 0, slot: &StaticEventLoop::quitEventLoop_noexcept); |
833 | QCOMPARE(_e->exec(), 0); |
834 | |
835 | QThread t1; |
836 | QObject c1; |
837 | c1.moveToThread(thread: &t1); |
838 | |
839 | QObject::connect(sender: &t1, SIGNAL(started()), receiver: &e, SLOT(quit())); |
840 | t1.start(); |
841 | QCOMPARE(e.exec(), 0); |
842 | |
843 | QTimer::singleShot(interval: 0, context: &c1, slot: CountedStruct(&count, &t1)); |
844 | QTRY_COMPARE(count, 2); |
845 | |
846 | t1.quit(); |
847 | t1.wait(); |
848 | |
849 | _t = new QThread; |
850 | QObject c2; |
851 | c2.moveToThread(thread: _t); |
852 | |
853 | QObject::connect(sender: _t, SIGNAL(started()), receiver: &e, SLOT(quit())); |
854 | _t->start(); |
855 | QCOMPARE(e.exec(), 0); |
856 | |
857 | QTimer::singleShot(interval: 0, context: &c2, slot: &StaticEventLoop::quitEventLoop); |
858 | QCOMPARE(_e->exec(), 0); |
859 | |
860 | _t->quit(); |
861 | _t->wait(); |
862 | _t->deleteLater(); |
863 | _t = nullptr; |
864 | |
865 | { |
866 | QObject c3; |
867 | QTimer::singleShot(interval: 500, context: &c3, slot: CountedStruct(&count)); |
868 | } |
869 | QTest::qWait(ms: 800); // Wait until the singleshot timer would have timed out |
870 | QCOMPARE(count, 2); |
871 | |
872 | QTimer::singleShot(interval: 0, slot: [&count] { ++count; }); |
873 | QTRY_COMPARE(count, 3); |
874 | |
875 | QObject context; |
876 | QThread thread; |
877 | |
878 | context.moveToThread(thread: &thread); |
879 | QObject::connect(sender: &thread, SIGNAL(started()), receiver: &e, SLOT(quit())); |
880 | thread.start(); |
881 | QCOMPARE(e.exec(), 0); |
882 | |
883 | QTimer::singleShot(interval: 0, context: &context, slot: [&count, &thread] { ++count; QCOMPARE(QThread::currentThread(), &thread); }); |
884 | QTRY_COMPARE(count, 4); |
885 | |
886 | thread.quit(); |
887 | thread.wait(); |
888 | |
889 | struct MoveOnly : CountedStruct { |
890 | Q_DISABLE_COPY(MoveOnly); |
891 | MoveOnly(MoveOnly &&o) : CountedStruct(std::move(o)) {}; |
892 | MoveOnly(int *c) : CountedStruct(c) {} |
893 | }; |
894 | QTimer::singleShot(interval: 0, slot: MoveOnly(&count)); |
895 | QTRY_COMPARE(count, 5); |
896 | |
897 | _e.reset(); |
898 | _t = nullptr; |
899 | } |
900 | |
901 | void tst_QTimer::singleShot_chrono() |
902 | { |
903 | #if !__has_include(<chrono>) |
904 | QSKIP("This test requires C++11 <chrono> support" ); |
905 | #else |
906 | // duplicates singleShotStaticFunctionZeroTimeout and singleShotToFunctors |
907 | using namespace std::chrono; |
908 | { |
909 | TimeoutCounter counter; |
910 | |
911 | QTimer::singleShot(value: hours(0), receiver: &counter, SLOT(timeout())); |
912 | QTRY_COMPARE(counter.count, 1); |
913 | QTest::qWait(ms: 500); |
914 | QCOMPARE(counter.count, 1); |
915 | } |
916 | |
917 | { |
918 | TimeoutCounter counter; |
919 | |
920 | QTimer::singleShot(interval: hours(0), receiver: &counter, slot: &TimeoutCounter::timeout); |
921 | QTRY_COMPARE(counter.count, 1); |
922 | QTest::qWait(ms: 500); |
923 | QCOMPARE(counter.count, 1); |
924 | } |
925 | |
926 | int count = 0; |
927 | QTimer::singleShot(interval: to_ms(t: microseconds(0)), slot: CountedStruct(&count)); |
928 | QTRY_COMPARE(count, 1); |
929 | |
930 | _e.reset(other: new QEventLoop); |
931 | QTimer::singleShot(interval: 0, slot: &StaticEventLoop::quitEventLoop); |
932 | QCOMPARE(_e->exec(), 0); |
933 | |
934 | QObject c3; |
935 | QTimer::singleShot(interval: milliseconds(500), context: &c3, slot: CountedStruct(&count)); |
936 | QTRY_COMPARE(count, 2); |
937 | |
938 | QTimer::singleShot(interval: 0, slot: [&count] { ++count; }); |
939 | QTRY_COMPARE(count, 3); |
940 | |
941 | _e.reset(); |
942 | #endif |
943 | } |
944 | |
945 | class DontBlockEvents : public QObject |
946 | { |
947 | Q_OBJECT |
948 | public: |
949 | DontBlockEvents(); |
950 | void timerEvent(QTimerEvent*); |
951 | |
952 | int count; |
953 | int total; |
954 | QBasicTimer m_timer; |
955 | |
956 | public slots: |
957 | void paintEvent(); |
958 | |
959 | }; |
960 | |
961 | DontBlockEvents::DontBlockEvents() |
962 | { |
963 | count = 0; |
964 | total = 0; |
965 | |
966 | // need a few unrelated timers running to reproduce the bug. |
967 | (new QTimer(this))->start(msec: 2000); |
968 | (new QTimer(this))->start(msec: 2500); |
969 | (new QTimer(this))->start(msec: 3000); |
970 | (new QTimer(this))->start(msec: 5000); |
971 | (new QTimer(this))->start(msec: 1000); |
972 | (new QTimer(this))->start(msec: 2000); |
973 | |
974 | m_timer.start(msec: 1, obj: this); |
975 | } |
976 | |
977 | void DontBlockEvents::timerEvent(QTimerEvent* event) |
978 | { |
979 | if (event->timerId() == m_timer.timerId()) { |
980 | QMetaObject::invokeMethod(obj: this, member: "paintEvent" , type: Qt::QueuedConnection); |
981 | m_timer.start(msec: 0, obj: this); |
982 | count++; |
983 | QCOMPARE(count, 1); |
984 | total++; |
985 | } |
986 | } |
987 | |
988 | void DontBlockEvents::paintEvent() |
989 | { |
990 | count--; |
991 | QCOMPARE(count, 0); |
992 | } |
993 | |
994 | // This is a regression test for QTBUG-13633, where a timer with a zero |
995 | // timeout that was restarted by the event handler could starve other timers. |
996 | void tst_QTimer::dontBlockEvents() |
997 | { |
998 | DontBlockEvents t; |
999 | QTest::qWait(ms: 60); |
1000 | QTRY_VERIFY(t.total > 2); |
1001 | } |
1002 | |
1003 | class SlotRepeater : public QObject { |
1004 | Q_OBJECT |
1005 | public: |
1006 | SlotRepeater() {} |
1007 | |
1008 | public slots: |
1009 | void repeatThisSlot() |
1010 | { |
1011 | QMetaObject::invokeMethod(obj: this, member: "repeatThisSlot" , type: Qt::QueuedConnection); |
1012 | } |
1013 | }; |
1014 | |
1015 | void tst_QTimer::postedEventsShouldNotStarveTimers() |
1016 | { |
1017 | QTimer timer; |
1018 | timer.setInterval(0); |
1019 | timer.setSingleShot(false); |
1020 | QSignalSpy timeoutSpy(&timer, &QTimer::timeout); |
1021 | timer.start(); |
1022 | SlotRepeater slotRepeater; |
1023 | slotRepeater.repeatThisSlot(); |
1024 | QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.count() > 5, 100); |
1025 | } |
1026 | |
1027 | struct DummyFunctor { |
1028 | void operator()() {} |
1029 | }; |
1030 | |
1031 | void tst_QTimer::crossThreadSingleShotToFunctor() |
1032 | { |
1033 | // We're testing for crashes here, so the test simply running to |
1034 | // completion is considered a success |
1035 | QThread t; |
1036 | t.start(); |
1037 | |
1038 | QObject* o = new QObject(); |
1039 | o->moveToThread(thread: &t); |
1040 | |
1041 | for (int i = 0; i < 10000; i++) { |
1042 | QTimer::singleShot(interval: 0, context: o, slot: DummyFunctor()); |
1043 | } |
1044 | |
1045 | t.quit(); |
1046 | t.wait(); |
1047 | delete o; |
1048 | } |
1049 | |
1050 | void tst_QTimer::callOnTimeout() |
1051 | { |
1052 | QTimer timer; |
1053 | QSignalSpy timeoutSpy(&timer, &QTimer::timeout); |
1054 | timer.setInterval(0); |
1055 | timer.start(); |
1056 | |
1057 | auto context = new QObject(); |
1058 | |
1059 | int count = 0; |
1060 | timer.callOnTimeout(args: [&count] { count++; }); |
1061 | QMetaObject::Connection connection = timer.callOnTimeout(args&: context, args: [&count] { count++; }); |
1062 | timer.callOnTimeout(args: &timer, args: &QTimer::stop); |
1063 | |
1064 | |
1065 | QTest::qWait(ms: 100); |
1066 | QCOMPARE(count, 2); |
1067 | QCOMPARE(timeoutSpy.count(), 1); |
1068 | |
1069 | // Test that connection is bound to context lifetime |
1070 | QVERIFY(connection); |
1071 | delete context; |
1072 | QVERIFY(!connection); |
1073 | } |
1074 | |
1075 | class OrderHelper : public QObject |
1076 | { |
1077 | Q_OBJECT |
1078 | public: |
1079 | enum CallType |
1080 | { |
1081 | String, |
1082 | PMF, |
1083 | Functor, |
1084 | FunctorNoCtx |
1085 | }; |
1086 | Q_ENUM(CallType) |
1087 | QVector<CallType> calls; |
1088 | |
1089 | void triggerCall(CallType callType) |
1090 | { |
1091 | switch (callType) |
1092 | { |
1093 | case String: |
1094 | QTimer::singleShot(msec: 0, receiver: this, SLOT(stringSlot())); |
1095 | break; |
1096 | case PMF: |
1097 | QTimer::singleShot(interval: 0, receiver: this, slot: &OrderHelper::pmfSlot); |
1098 | break; |
1099 | case Functor: |
1100 | QTimer::singleShot(interval: 0, context: this, slot: [this]() { functorSlot(); }); |
1101 | break; |
1102 | case FunctorNoCtx: |
1103 | QTimer::singleShot(interval: 0, slot: [this]() { functorNoCtxSlot(); }); |
1104 | break; |
1105 | } |
1106 | } |
1107 | |
1108 | public slots: |
1109 | void stringSlot() { calls << String; } |
1110 | void pmfSlot() { calls << PMF; } |
1111 | void functorSlot() { calls << Functor; } |
1112 | void functorNoCtxSlot() { calls << FunctorNoCtx; } |
1113 | }; |
1114 | |
1115 | Q_DECLARE_METATYPE(OrderHelper::CallType) |
1116 | |
1117 | void tst_QTimer::timerOrder() |
1118 | { |
1119 | QFETCH(QVector<OrderHelper::CallType>, calls); |
1120 | |
1121 | OrderHelper helper; |
1122 | |
1123 | for (const auto call : calls) |
1124 | helper.triggerCall(callType: call); |
1125 | |
1126 | QTRY_COMPARE(helper.calls, calls); |
1127 | } |
1128 | |
1129 | void tst_QTimer::timerOrder_data() |
1130 | { |
1131 | QTest::addColumn<QVector<OrderHelper::CallType>>(name: "calls" ); |
1132 | |
1133 | QVector<OrderHelper::CallType> calls = { |
1134 | OrderHelper::String, OrderHelper::PMF, |
1135 | OrderHelper::Functor, OrderHelper::FunctorNoCtx |
1136 | }; |
1137 | std::sort(first: calls.begin(), last: calls.end()); |
1138 | |
1139 | int permutation = 0; |
1140 | do { |
1141 | QTest::addRow(format: "permutation=%d" , permutation) << calls; |
1142 | ++permutation; |
1143 | } while (std::next_permutation(first: calls.begin(), last: calls.end())); |
1144 | } |
1145 | |
1146 | void tst_QTimer::timerOrderBackgroundThread() |
1147 | { |
1148 | #if !QT_CONFIG(cxx11_future) |
1149 | QSKIP("This test requires QThread::create" ); |
1150 | #else |
1151 | auto *thread = QThread::create(f: [this]() { timerOrder(); }); |
1152 | thread->start(); |
1153 | QVERIFY(thread->wait()); |
1154 | delete thread; |
1155 | #endif |
1156 | } |
1157 | |
1158 | struct StaticSingleShotUser |
1159 | { |
1160 | StaticSingleShotUser() |
1161 | { |
1162 | for (auto call : calls()) |
1163 | helper.triggerCall(callType: call); |
1164 | } |
1165 | OrderHelper helper; |
1166 | |
1167 | static QVector<OrderHelper::CallType> calls() |
1168 | { |
1169 | return {OrderHelper::String, OrderHelper::PMF, |
1170 | OrderHelper::Functor, OrderHelper::FunctorNoCtx}; |
1171 | } |
1172 | }; |
1173 | |
1174 | // NOTE: to prevent any static initialization order fiasco, we implement |
1175 | // initMain() to instantiate staticSingleShotUser before qApp |
1176 | |
1177 | static StaticSingleShotUser *s_staticSingleShotUser = nullptr; |
1178 | |
1179 | void tst_QTimer::initMain() |
1180 | { |
1181 | s_staticSingleShotUser = new StaticSingleShotUser; |
1182 | } |
1183 | |
1184 | void tst_QTimer::cleanupTestCase() |
1185 | { |
1186 | delete s_staticSingleShotUser; |
1187 | } |
1188 | |
1189 | void tst_QTimer::singleShot_static() |
1190 | { |
1191 | QCoreApplication::processEvents(); |
1192 | QCOMPARE(s_staticSingleShotUser->helper.calls, s_staticSingleShotUser->calls()); |
1193 | } |
1194 | |
1195 | QTEST_MAIN(tst_QTimer) |
1196 | |
1197 | #include "tst_qtimer.moc" |
1198 | |