/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Copyright (C) 2016 Intel Corporation. ** 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$ ** ****************************************************************************/ #ifdef QT_GUI_LIB # include #else # include #endif #include #include #include #include #include #if defined Q_OS_UNIX #include #endif class tst_QTimer : public QObject { Q_OBJECT public: static void initMain(); private slots: void cleanupTestCase(); void zeroTimer(); void singleShotTimeout(); void timeout(); void remainingTime(); void remainingTimeInitial_data(); void remainingTimeInitial(); void remainingTimeDuringActivation_data(); void remainingTimeDuringActivation(); void basic_chrono(); void livelock_data(); void livelock(); void timerInfiniteRecursion_data(); void timerInfiniteRecursion(); void recurringTimer_data(); void recurringTimer(); void deleteLaterOnQTimer(); // long name, don't want to shadow QObject::deleteLater() void moveToThread(); void restartedTimerFiresTooSoon(); void timerFiresOnlyOncePerProcessEvents_data(); void timerFiresOnlyOncePerProcessEvents(); void timerIdPersistsAfterThreadExit(); void cancelLongTimer(); void singleShotStaticFunctionZeroTimeout(); void recurseOnTimeoutAndStopTimer(); void singleShotToFunctors(); void singleShot_chrono(); void singleShot_static(); void crossThreadSingleShotToFunctor(); void timerOrder(); void timerOrder_data(); void timerOrderBackgroundThread(); void timerOrderBackgroundThread_data() { timerOrder_data(); } void dontBlockEvents(); void postedEventsShouldNotStarveTimers(); void callOnTimeout(); }; void tst_QTimer::zeroTimer() { QTimer timer; timer.setInterval(0); QSignalSpy timeoutSpy(&timer, &QTimer::timeout); timer.start(); QCoreApplication::processEvents(); QCOMPARE(timeoutSpy.count(), 1); } void tst_QTimer::singleShotTimeout() { QTimer timer; timer.setSingleShot(true); QSignalSpy timeoutSpy(&timer, &QTimer::timeout); timer.start(100); QVERIFY(timeoutSpy.wait(500)); QCOMPARE(timeoutSpy.count(), 1); QTest::qWait(500); QCOMPARE(timeoutSpy.count(), 1); } #define TIMEOUT_TIMEOUT 200 void tst_QTimer::timeout() { QTimer timer; QSignalSpy timeoutSpy(&timer, &QTimer::timeout); timer.start(100); QCOMPARE(timeoutSpy.count(), 0); QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.count() > 0, TIMEOUT_TIMEOUT); int oldCount = timeoutSpy.count(); QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.count() > oldCount, TIMEOUT_TIMEOUT); } void tst_QTimer::remainingTime() { QTimer tested; tested.setTimerType(Qt::PreciseTimer); QTimer tester; tester.setTimerType(Qt::PreciseTimer); tester.setSingleShot(true); const int testedInterval = 200; const int testerInterval = 50; const int expectedRemainingTime = testedInterval - testerInterval; int testIteration = 0; const int desiredTestCount = 2; auto connection = QObject::connect(&tested, &QTimer::timeout, [&tester]() { // We let tested (which isn't a single-shot) run repeatedly, to verify // it *does* repeat, and check that the single-shot tester, starting // at the same time, does finish first each time, by about the right duration. tester.start(); // Start tester again. }); QObject::connect(&tester, &QTimer::timeout, [&]() { const int remainingTime = tested.remainingTime(); // We expect that remainingTime is at most 150 and not overdue. const bool remainingTimeInRange = remainingTime > 0 && remainingTime <= expectedRemainingTime; if (remainingTimeInRange) ++testIteration; else testIteration = desiredTestCount; // We are going to fail on QVERIFY2() // below, so we don't want to iterate // anymore and quickly exit the QTRY_...() // with this failure. if (testIteration == desiredTestCount) QObject::disconnect(connection); // Last iteration, don't start tester again. QVERIFY2(remainingTimeInRange, qPrintable("Remaining time " + QByteArray::number(remainingTime) + "ms outside expected range (0ms, " + QByteArray::number(expectedRemainingTime) + "ms]")); }); tested.start(testedInterval); tester.start(testerInterval); // Start tester for the 1st time. // Test it desiredTestCount times, give it reasonable amount of time // (twice as much as needed). QTRY_COMPARE_WITH_TIMEOUT(testIteration, desiredTestCount, testedInterval * desiredTestCount * 2); } void tst_QTimer::remainingTimeInitial_data() { QTest::addColumn("startTimeMs"); QTest::addColumn("timerType"); QTest::addRow("precise time 0ms") << 0 << Qt::PreciseTimer; QTest::addRow("precise time 1ms") << 1 << Qt::PreciseTimer; QTest::addRow("precise time 10ms") << 10 << Qt::PreciseTimer; QTest::addRow("coarse time 0ms") << 0 << Qt::CoarseTimer; QTest::addRow("coarse time 1ms") << 1 << Qt::CoarseTimer; QTest::addRow("coarse time 10ms") << 10 << Qt::CoarseTimer; } void tst_QTimer::remainingTimeInitial() { QFETCH(int, startTimeMs); QFETCH(Qt::TimerType, timerType); QTimer timer; timer.setTimerType(timerType); timer.start(startTimeMs); const int rt = timer.remainingTime(); QVERIFY2(rt >= 0 && rt <= startTimeMs, qPrintable(QString::number(rt))); } void tst_QTimer::remainingTimeDuringActivation_data() { QTest::addColumn("singleShot"); QTest::newRow("repeating") << false; QTest::newRow("single-shot") << true; } void tst_QTimer::remainingTimeDuringActivation() { QFETCH(bool, singleShot); QTimer timer; timer.setSingleShot(singleShot); int remainingTime = 0; // not the expected value in either case connect(&timer, &QTimer::timeout, [&]() { remainingTime = timer.remainingTime(); }); QSignalSpy timeoutSpy(&timer, &QTimer::timeout); const int timeout = 20; // 20 ms is short enough and should not round down to 0 in any timer mode timer.start(timeout); QVERIFY(timeoutSpy.wait()); if (singleShot) QCOMPARE(remainingTime, -1); // timer not running else QVERIFY2(remainingTime <= timeout && remainingTime > 0, qPrintable(QString::number(remainingTime))); if (!singleShot) { // do it again - see QTBUG-46940 remainingTime = -1; QVERIFY(timeoutSpy.wait()); QVERIFY2(remainingTime <= timeout && remainingTime > 0, qPrintable(QString::number(remainingTime))); } } namespace { #if __has_include() template std::chrono::milliseconds to_ms(T t) { return std::chrono::duration_cast(t); } #endif } // unnamed namespace void tst_QTimer::basic_chrono() { #if !__has_include() QSKIP("This test requires C++11 support"); #else // duplicates zeroTimer, singleShotTimeout, interval and remainingTime using namespace std::chrono; QTimer timer; QSignalSpy timeoutSpy(&timer, &QTimer::timeout); timer.setInterval(to_ms(nanoseconds(0))); timer.start(); QCOMPARE(timer.intervalAsDuration().count(), milliseconds::rep(0)); QCOMPARE(timer.remainingTimeAsDuration().count(), milliseconds::rep(0)); QCoreApplication::processEvents(); QCOMPARE(timeoutSpy.count(), 1); timeoutSpy.clear(); timer.start(milliseconds(100)); QCOMPARE(timeoutSpy.count(), 0); QVERIFY(timeoutSpy.wait(TIMEOUT_TIMEOUT)); QVERIFY(timeoutSpy.count() > 0); int oldCount = timeoutSpy.count(); QVERIFY(timeoutSpy.wait(TIMEOUT_TIMEOUT)); QVERIFY(timeoutSpy.count() > oldCount); timeoutSpy.clear(); timer.start(to_ms(microseconds(200000))); QCOMPARE(timer.intervalAsDuration().count(), milliseconds::rep(200)); QTest::qWait(50); QCOMPARE(timeoutSpy.count(), 0); milliseconds rt = timer.remainingTimeAsDuration(); QVERIFY2(rt.count() >= 50 && rt.count() <= 200, qPrintable(QString::number(rt.count()))); timeoutSpy.clear(); timer.setSingleShot(true); timer.start(milliseconds(100)); QVERIFY(timeoutSpy.wait(TIMEOUT_TIMEOUT)); QCOMPARE(timeoutSpy.count(), 1); QTest::qWait(500); QCOMPARE(timeoutSpy.count(), 1); #endif } void tst_QTimer::livelock_data() { QTest::addColumn("interval"); QTest::newRow("zero timer") << 0; QTest::newRow("non-zero timer") << 1; QTest::newRow("longer than sleep") << 20; } /*! * * DO NOT "FIX" THIS TEST! it is written like this for a reason, do * not *change it without first dicussing it with its maintainers. * */ class LiveLockTester : public QObject { public: LiveLockTester(int i) : interval(i), timeoutsForFirst(0), timeoutsForExtra(0), timeoutsForSecond(0), postEventAtRightTime(false) { firstTimerId = startTimer(interval); extraTimerId = startTimer(interval + 80); secondTimerId = -1; // started later } bool event(QEvent *e) override { if (e->type() == 4002) { // got the posted event if (timeoutsForFirst == 1 && timeoutsForSecond == 0) postEventAtRightTime = true; return true; } return QObject::event(e); } void timerEvent(QTimerEvent *te) override { if (te->timerId() == firstTimerId) { if (++timeoutsForFirst == 1) { killTimer(extraTimerId); extraTimerId = -1; QCoreApplication::postEvent(this, new QEvent(static_cast(4002))); secondTimerId = startTimer(interval); } } else if (te->timerId() == secondTimerId) { ++timeoutsForSecond; } else if (te->timerId() == extraTimerId) { ++timeoutsForExtra; } // sleep for 2ms QTest::qSleep(2); killTimer(te->timerId()); } const int interval; int firstTimerId; int secondTimerId; int extraTimerId; int timeoutsForFirst; int timeoutsForExtra; int timeoutsForSecond; bool postEventAtRightTime; }; void tst_QTimer::livelock() { /* New timers created in timer event handlers should not be sent until the next iteration of the eventloop. Note: this test depends on the fact that we send posted events before timer events (since new posted events are not sent until the next iteration of the eventloop either). */ QFETCH(int, interval); LiveLockTester tester(interval); QTest::qWait(180); // we have to use wait here, since we're testing timers with a non-zero timeout QTRY_COMPARE(tester.timeoutsForFirst, 1); QCOMPARE(tester.timeoutsForExtra, 0); QTRY_COMPARE(tester.timeoutsForSecond, 1); QVERIFY(tester.postEventAtRightTime); } class TimerInfiniteRecursionObject : public QObject { public: bool inTimerEvent; bool timerEventRecursed; int interval; TimerInfiniteRecursionObject(int interval) : inTimerEvent(false), timerEventRecursed(false), interval(interval) { } void timerEvent(QTimerEvent *timerEvent) override { timerEventRecursed = inTimerEvent; if (timerEventRecursed) { // bug detected! return; } inTimerEvent = true; QEventLoop eventLoop; QTimer::singleShot(qMax(100, interval * 2), &eventLoop, SLOT(quit())); eventLoop.exec(); inTimerEvent = false; killTimer(timerEvent->timerId()); } }; void tst_QTimer::timerInfiniteRecursion_data() { QTest::addColumn("interval"); QTest::newRow("zero timer") << 0; QTest::newRow("non-zero timer") << 1; QTest::newRow("10ms timer") << 10; QTest::newRow("11ms timer") << 11; QTest::newRow("100ms timer") << 100; QTest::newRow("1s timer") << 1000; } void tst_QTimer::timerInfiniteRecursion() { QFETCH(int, interval); TimerInfiniteRecursionObject object(interval); (void) object.startTimer(interval); QEventLoop eventLoop; QTimer::singleShot(qMax(100, interval * 2), &eventLoop, SLOT(quit())); eventLoop.exec(); QVERIFY(!object.timerEventRecursed); } class RecurringTimerObject : public QObject { Q_OBJECT public: int times; int target; bool recurse; RecurringTimerObject(int target) : times(0), target(target), recurse(false) { } void timerEvent(QTimerEvent *timerEvent) override { if (++times == target) { killTimer(timerEvent->timerId()); emit done(); } if (recurse) { QEventLoop eventLoop; QTimer::singleShot(100, &eventLoop, SLOT(quit())); eventLoop.exec(); } } signals: void done(); }; void tst_QTimer::recurringTimer_data() { QTest::addColumn("interval"); QTest::addColumn("recurse"); // make sure that eventloop recursion doesn't affect timer recurrence QTest::newRow("zero timer, don't recurse") << 0 << false; QTest::newRow("zero timer, recurse") << 0 << true; QTest::newRow("non-zero timer, don't recurse") << 1 << false; QTest::newRow("non-zero timer, recurse") << 1 << true; } void tst_QTimer::recurringTimer() { const int target = 5; QFETCH(int, interval); QFETCH(bool, recurse); RecurringTimerObject object(target); object.recurse = recurse; QSignalSpy doneSpy(&object, &RecurringTimerObject::done); (void) object.startTimer(interval); QVERIFY(doneSpy.wait()); QCOMPARE(object.times, target); } void tst_QTimer::deleteLaterOnQTimer() { QTimer *timer = new QTimer; connect(timer, SIGNAL(timeout()), timer, SLOT(deleteLater())); QSignalSpy destroyedSpy(timer, &QObject::destroyed); timer->setInterval(1); timer->setSingleShot(true); timer->start(); QPointer pointer = timer; QVERIFY(destroyedSpy.wait()); QVERIFY(pointer.isNull()); } #define MOVETOTHREAD_TIMEOUT 200 #define MOVETOTHREAD_WAIT 300 void tst_QTimer::moveToThread() { #if defined(Q_OS_WIN32) QSKIP("Does not work reliably on Windows :("); #elif defined(Q_OS_MACOS) QSKIP("Does not work reliably on macOS 10.12+ (QTBUG-59679)"); #endif QTimer ti1; QTimer ti2; ti1.start(MOVETOTHREAD_TIMEOUT); ti2.start(MOVETOTHREAD_TIMEOUT); QVERIFY((ti1.timerId() & 0xffffff) != (ti2.timerId() & 0xffffff)); QThread tr; ti1.moveToThread(&tr); connect(&ti1,SIGNAL(timeout()), &tr, SLOT(quit())); tr.start(); QTimer ti3; ti3.start(MOVETOTHREAD_TIMEOUT); QVERIFY((ti3.timerId() & 0xffffff) != (ti2.timerId() & 0xffffff)); QVERIFY((ti3.timerId() & 0xffffff) != (ti1.timerId() & 0xffffff)); QTest::qWait(MOVETOTHREAD_WAIT); QVERIFY(tr.wait()); ti2.stop(); QTimer ti4; ti4.start(MOVETOTHREAD_TIMEOUT); ti3.stop(); ti2.start(MOVETOTHREAD_TIMEOUT); ti3.start(MOVETOTHREAD_TIMEOUT); QVERIFY((ti4.timerId() & 0xffffff) != (ti2.timerId() & 0xffffff)); QVERIFY((ti3.timerId() & 0xffffff) != (ti2.timerId() & 0xffffff)); QVERIFY((ti3.timerId() & 0xffffff) != (ti1.timerId() & 0xffffff)); } class RestartedTimerFiresTooSoonObject : public QObject { Q_OBJECT public: QBasicTimer m_timer; int m_interval; QElapsedTimer m_elapsedTimer; QEventLoop eventLoop; inline RestartedTimerFiresTooSoonObject() : QObject(), m_interval(0) { } void timerFired() { static int interval = 1000; m_interval = interval; m_elapsedTimer.start(); m_timer.start(interval, this); // alternate between single-shot and 1 sec interval = interval ? 0 : 1000; } void timerEvent(QTimerEvent* ev) override { if (ev->timerId() != m_timer.timerId()) return; m_timer.stop(); int elapsed = m_elapsedTimer.elapsed(); if (elapsed < m_interval / 2) { // severely too early! m_timer.stop(); eventLoop.exit(-1); return; } timerFired(); // don't do this forever static int count = 0; if (count++ > 20) { m_timer.stop(); eventLoop.quit(); return; } } }; void tst_QTimer::restartedTimerFiresTooSoon() { RestartedTimerFiresTooSoonObject object; object.timerFired(); QCOMPARE(object.eventLoop.exec(), 0); } class LongLastingSlotClass : public QObject { Q_OBJECT public: LongLastingSlotClass(QTimer *timer) : count(0), timer(timer) {} public slots: void longLastingSlot() { // Don't use QTimer for this, because we are testing it. QElapsedTimer control; control.start(); while (control.elapsed() < 200) { for (int c = 0; c < 100000; c++) {} // Mindless looping. } if (++count >= 2) { timer->stop(); } } public: int count; QTimer *timer; }; void tst_QTimer::timerFiresOnlyOncePerProcessEvents_data() { QTest::addColumn("interval"); QTest::newRow("zero timer") << 0; QTest::newRow("non-zero timer") << 10; } void tst_QTimer::timerFiresOnlyOncePerProcessEvents() { QFETCH(int, interval); QTimer t; LongLastingSlotClass longSlot(&t); t.start(interval); connect(&t, SIGNAL(timeout()), &longSlot, SLOT(longLastingSlot())); // Loop because there may be other events pending. while (longSlot.count == 0) { QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } QCOMPARE(longSlot.count, 1); } class TimerIdPersistsAfterThreadExitThread : public QThread { public: QTimer *timer; int timerId, returnValue; TimerIdPersistsAfterThreadExitThread() : QThread(), timer(0), timerId(-1), returnValue(-1) { } ~TimerIdPersistsAfterThreadExitThread() { delete timer; } void run() override { QEventLoop eventLoop; timer = new QTimer; connect(timer, SIGNAL(timeout()), &eventLoop, SLOT(quit())); timer->start(100); timerId = timer->timerId(); returnValue = eventLoop.exec(); } }; void tst_QTimer::timerIdPersistsAfterThreadExit() { TimerIdPersistsAfterThreadExitThread thread; thread.start(); QVERIFY(thread.wait(30000)); QCOMPARE(thread.returnValue, 0); // even though the thread has exited, and the event dispatcher destroyed, the timer is still // "active", meaning the timer id should NOT be reused (i.e. the event dispatcher should not // have unregistered it) int timerId = thread.startTimer(100); QVERIFY((timerId & 0xffffff) != (thread.timerId & 0xffffff)); } void tst_QTimer::cancelLongTimer() { QTimer timer; timer.setSingleShot(true); timer.start(1000 * 60 * 60); //set timer for 1 hour QCoreApplication::processEvents(); QVERIFY(timer.isActive()); //if the timer completes immediately with an error, then this will fail timer.stop(); QVERIFY(!timer.isActive()); } class TimeoutCounter : public QObject { Q_OBJECT public slots: void timeout() { ++count; }; public: int count = 0; }; void tst_QTimer::singleShotStaticFunctionZeroTimeout() { { TimeoutCounter counter; QTimer::singleShot(0, &counter, SLOT(timeout())); QTRY_COMPARE(counter.count, 1); QTest::qWait(500); QCOMPARE(counter.count, 1); } { TimeoutCounter counter; QTimer::singleShot(0, &counter, &TimeoutCounter::timeout); QTRY_COMPARE(counter.count, 1); QTest::qWait(500); QCOMPARE(counter.count, 1); } } class RecursOnTimeoutAndStopTimerTimer : public QObject { Q_OBJECT public: QTimer *one; QTimer *two; public slots: void onetrigger() { QCoreApplication::processEvents(); } void twotrigger() { one->stop(); } }; void tst_QTimer::recurseOnTimeoutAndStopTimer() { QEventLoop eventLoop; QTimer::singleShot(1000, &eventLoop, SLOT(quit())); RecursOnTimeoutAndStopTimerTimer t; t.one = new QTimer(&t); t.two = new QTimer(&t); QObject::connect(t.one, SIGNAL(timeout()), &t, SLOT(onetrigger())); QObject::connect(t.two, SIGNAL(timeout()), &t, SLOT(twotrigger())); t.two->setSingleShot(true); t.one->start(); t.two->start(); (void) eventLoop.exec(); QVERIFY(!t.one->isActive()); QVERIFY(!t.two->isActive()); } struct CountedStruct { CountedStruct(int *count, QThread *t = nullptr) : count(count), thread(t) { } ~CountedStruct() { } void operator()() const { ++(*count); if (thread) QCOMPARE(QThread::currentThread(), thread); } int *count; QThread *thread; }; static QScopedPointer _e; static QThread *_t = nullptr; class StaticEventLoop { public: static void quitEventLoop() { quitEventLoop_noexcept(); } static void quitEventLoop_noexcept() noexcept { QVERIFY(!_e.isNull()); _e->quit(); if (_t) QCOMPARE(QThread::currentThread(), _t); } }; void tst_QTimer::singleShotToFunctors() { int count = 0; _e.reset(new QEventLoop); QEventLoop e; QTimer::singleShot(0, CountedStruct(&count)); QCoreApplication::processEvents(); QCOMPARE(count, 1); QTimer::singleShot(0, &StaticEventLoop::quitEventLoop); QCOMPARE(_e->exec(), 0); QTimer::singleShot(0, &StaticEventLoop::quitEventLoop_noexcept); QCOMPARE(_e->exec(), 0); QThread t1; QObject c1; c1.moveToThread(&t1); QObject::connect(&t1, SIGNAL(started()), &e, SLOT(quit())); t1.start(); QCOMPARE(e.exec(), 0); QTimer::singleShot(0, &c1, CountedStruct(&count, &t1)); QTRY_COMPARE(count, 2); t1.quit(); t1.wait(); _t = new QThread; QObject c2; c2.moveToThread(_t); QObject::connect(_t, SIGNAL(started()), &e, SLOT(quit())); _t->start(); QCOMPARE(e.exec(), 0); QTimer::singleShot(0, &c2, &StaticEventLoop::quitEventLoop); QCOMPARE(_e->exec(), 0); _t->quit(); _t->wait(); _t->deleteLater(); _t = nullptr; { QObject c3; QTimer::singleShot(500, &c3, CountedStruct(&count)); } QTest::qWait(800); // Wait until the singleshot timer would have timed out QCOMPARE(count, 2); QTimer::singleShot(0, [&count] { ++count; }); QTRY_COMPARE(count, 3); QObject context; QThread thread; context.moveToThread(&thread); QObject::connect(&thread, SIGNAL(started()), &e, SLOT(quit())); thread.start(); QCOMPARE(e.exec(), 0); QTimer::singleShot(0, &context, [&count, &thread] { ++count; QCOMPARE(QThread::currentThread(), &thread); }); QTRY_COMPARE(count, 4); thread.quit(); thread.wait(); struct MoveOnly : CountedStruct { Q_DISABLE_COPY(MoveOnly); MoveOnly(MoveOnly &&o) : CountedStruct(std::move(o)) {}; MoveOnly(int *c) : CountedStruct(c) {} }; QTimer::singleShot(0, MoveOnly(&count)); QTRY_COMPARE(count, 5); _e.reset(); _t = nullptr; } void tst_QTimer::singleShot_chrono() { #if !__has_include() QSKIP("This test requires C++11 support"); #else // duplicates singleShotStaticFunctionZeroTimeout and singleShotToFunctors using namespace std::chrono; { TimeoutCounter counter; QTimer::singleShot(hours(0), &counter, SLOT(timeout())); QTRY_COMPARE(counter.count, 1); QTest::qWait(500); QCOMPARE(counter.count, 1); } { TimeoutCounter counter; QTimer::singleShot(hours(0), &counter, &TimeoutCounter::timeout); QTRY_COMPARE(counter.count, 1); QTest::qWait(500); QCOMPARE(counter.count, 1); } int count = 0; QTimer::singleShot(to_ms(microseconds(0)), CountedStruct(&count)); QTRY_COMPARE(count, 1); _e.reset(new QEventLoop); QTimer::singleShot(0, &StaticEventLoop::quitEventLoop); QCOMPARE(_e->exec(), 0); QObject c3; QTimer::singleShot(milliseconds(500), &c3, CountedStruct(&count)); QTRY_COMPARE(count, 2); QTimer::singleShot(0, [&count] { ++count; }); QTRY_COMPARE(count, 3); _e.reset(); #endif } class DontBlockEvents : public QObject { Q_OBJECT public: DontBlockEvents(); void timerEvent(QTimerEvent*) override; int count; int total; QBasicTimer m_timer; public slots: void paintEvent(); }; DontBlockEvents::DontBlockEvents() { count = 0; total = 0; // need a few unrelated timers running to reproduce the bug. (new QTimer(this))->start(2000); (new QTimer(this))->start(2500); (new QTimer(this))->start(3000); (new QTimer(this))->start(5000); (new QTimer(this))->start(1000); (new QTimer(this))->start(2000); m_timer.start(1, this); } void DontBlockEvents::timerEvent(QTimerEvent* event) { if (event->timerId() == m_timer.timerId()) { QMetaObject::invokeMethod(this, "paintEvent", Qt::QueuedConnection); m_timer.start(0, this); count++; QCOMPARE(count, 1); total++; } } void DontBlockEvents::paintEvent() { count--; QCOMPARE(count, 0); } // This is a regression test for QTBUG-13633, where a timer with a zero // timeout that was restarted by the event handler could starve other timers. void tst_QTimer::dontBlockEvents() { DontBlockEvents t; QTest::qWait(60); QTRY_VERIFY(t.total > 2); } class SlotRepeater : public QObject { Q_OBJECT public: SlotRepeater() {} public slots: void repeatThisSlot() { QMetaObject::invokeMethod(this, "repeatThisSlot", Qt::QueuedConnection); } }; void tst_QTimer::postedEventsShouldNotStarveTimers() { QTimer timer; timer.setInterval(0); timer.setSingleShot(false); QSignalSpy timeoutSpy(&timer, &QTimer::timeout); timer.start(); SlotRepeater slotRepeater; slotRepeater.repeatThisSlot(); QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.count() > 5, 100); } struct DummyFunctor { void operator()() {} }; void tst_QTimer::crossThreadSingleShotToFunctor() { // We're testing for crashes here, so the test simply running to // completion is considered a success QThread t; t.start(); QObject* o = new QObject(); o->moveToThread(&t); for (int i = 0; i < 10000; i++) { QTimer::singleShot(0, o, DummyFunctor()); } t.quit(); t.wait(); delete o; } void tst_QTimer::callOnTimeout() { QTimer timer; QSignalSpy timeoutSpy(&timer, &QTimer::timeout); timer.setInterval(0); timer.start(); auto context = new QObject(); int count = 0; timer.callOnTimeout([&count] { count++; }); QMetaObject::Connection connection = timer.callOnTimeout(context, [&count] { count++; }); timer.callOnTimeout(&timer, &QTimer::stop); QTest::qWait(100); QCOMPARE(count, 2); QCOMPARE(timeoutSpy.count(), 1); // Test that connection is bound to context lifetime QVERIFY(connection); delete context; QVERIFY(!connection); } class OrderHelper : public QObject { Q_OBJECT public: enum CallType { String, PMF, Functor, FunctorNoCtx }; Q_ENUM(CallType) QList calls; void triggerCall(CallType callType) { switch (callType) { case String: QTimer::singleShot(0, this, SLOT(stringSlot())); break; case PMF: QTimer::singleShot(0, this, &OrderHelper::pmfSlot); break; case Functor: QTimer::singleShot(0, this, [this]() { functorSlot(); }); break; case FunctorNoCtx: QTimer::singleShot(0, [this]() { functorNoCtxSlot(); }); break; } } public slots: void stringSlot() { calls << String; } void pmfSlot() { calls << PMF; } void functorSlot() { calls << Functor; } void functorNoCtxSlot() { calls << FunctorNoCtx; } }; Q_DECLARE_METATYPE(OrderHelper::CallType) void tst_QTimer::timerOrder() { QFETCH(QList, calls); OrderHelper helper; for (const auto call : calls) helper.triggerCall(call); QTRY_COMPARE(helper.calls, calls); } void tst_QTimer::timerOrder_data() { QTest::addColumn>("calls"); QList calls = { OrderHelper::String, OrderHelper::PMF, OrderHelper::Functor, OrderHelper::FunctorNoCtx }; std::sort(calls.begin(), calls.end()); int permutation = 0; do { QTest::addRow("permutation=%d", permutation) << calls; ++permutation; } while (std::next_permutation(calls.begin(), calls.end())); } void tst_QTimer::timerOrderBackgroundThread() { #if !QT_CONFIG(cxx11_future) QSKIP("This test requires QThread::create"); #else auto *thread = QThread::create([this]() { timerOrder(); }); thread->start(); QVERIFY(thread->wait()); delete thread; #endif } struct StaticSingleShotUser { StaticSingleShotUser() { for (auto call : calls()) helper.triggerCall(call); } OrderHelper helper; static QList calls() { return {OrderHelper::String, OrderHelper::PMF, OrderHelper::Functor, OrderHelper::FunctorNoCtx}; } }; // NOTE: to prevent any static initialization order fiasco, we implement // initMain() to instantiate staticSingleShotUser before qApp static StaticSingleShotUser *s_staticSingleShotUser = nullptr; void tst_QTimer::initMain() { s_staticSingleShotUser = new StaticSingleShotUser; } void tst_QTimer::cleanupTestCase() { delete s_staticSingleShotUser; } void tst_QTimer::singleShot_static() { QCoreApplication::processEvents(); QCOMPARE(s_staticSingleShotUser->helper.calls, s_staticSingleShotUser->calls()); } QTEST_MAIN(tst_QTimer) #include "tst_qtimer.moc"