diff options
Diffstat (limited to 'tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp')
-rw-r--r-- | tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp | 408 |
1 files changed, 331 insertions, 77 deletions
diff --git a/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp b/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp index b3a500fbd8..40190ca465 100644 --- a/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp +++ b/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp @@ -1,41 +1,27 @@ -/**************************************************************************** -** -** Copyright (C) 2020 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$ -** -****************************************************************************/ +// Copyright (C) 2020 The Qt Company Ltd. +// Copyright (C) 2016 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +/* WARNING: this source-code is reused by another test. + + As Qt built with GUI support may use a different backend for its event loops + and other timer-related matters, it is important to test it in that form, as + well as in its GUI-less form. So this source file is reused by a build config + in the GUI module. Similarly, testing with and without glib is supported, + where relevant (see DISABLE_GLIB below). +*/ #ifdef QT_GUI_LIB +// When compiled as tests/auto/gui/kernel/qguitimer/'s source-code: # include <QtGui/QGuiApplication> #else +// When compiled as tests/auto/corelib/kernel/qtimer/'s source-code: # include <QtCore/QCoreApplication> #endif #include <QtCore/private/qglobal_p.h> #include <QTest> #include <QSignalSpy> +#include <QtTest/private/qpropertytesthelper_p.h> #include <qtimer.h> #include <qthread.h> @@ -46,6 +32,15 @@ #include <unistd.h> #endif +#ifdef DISABLE_GLIB +static bool glibDisabled = []() { + qputenv("QT_NO_GLIB", "1"); + return true; +}(); +#endif + +using namespace std::chrono_literals; + class tst_QTimer : public QObject { Q_OBJECT @@ -57,6 +52,12 @@ private slots: void zeroTimer(); void singleShotTimeout(); void timeout(); + void singleShotNormalizes_data(); + void singleShotNormalizes(); + void sequentialTimers_data(); + void sequentialTimers(); + void singleShotSequentialTimers_data(); + void singleShotSequentialTimers(); void remainingTime(); void remainingTimeInitial_data(); void remainingTimeInitial(); @@ -81,6 +82,7 @@ private slots: void singleShotToFunctors(); void singleShot_chrono(); void singleShot_static(); + void crossThreadSingleShotToFunctor_data(); void crossThreadSingleShotToFunctor(); void timerOrder(); void timerOrder_data(); @@ -93,6 +95,10 @@ private slots: void bindToTimer(); void bindTimer(); + void automatedBindingTests(); + + void negativeInterval(); + void testTimerId(); }; void tst_QTimer::zeroTimer() @@ -109,7 +115,7 @@ void tst_QTimer::zeroTimer() // Pass timeout to work round glib issue, see QTBUG-84291. QCoreApplication::processEvents(QEventLoop::AllEvents, INT_MAX); - QCOMPARE(timeoutSpy.count(), 1); + QCOMPARE(timeoutSpy.size(), 1); } void tst_QTimer::singleShotTimeout() @@ -123,9 +129,9 @@ void tst_QTimer::singleShotTimeout() timer.start(100); QVERIFY(timeoutSpy.wait(500)); - QCOMPARE(timeoutSpy.count(), 1); + QCOMPARE(timeoutSpy.size(), 1); QTest::qWait(500); - QCOMPARE(timeoutSpy.count(), 1); + QCOMPARE(timeoutSpy.size(), 1); } #define TIMEOUT_TIMEOUT 200 @@ -136,12 +142,147 @@ void tst_QTimer::timeout() QSignalSpy timeoutSpy(&timer, &QTimer::timeout); timer.start(100); - QCOMPARE(timeoutSpy.count(), 0); + QCOMPARE(timeoutSpy.size(), 0); - QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.count() > 0, TIMEOUT_TIMEOUT); - int oldCount = timeoutSpy.count(); + QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.size() > 0, TIMEOUT_TIMEOUT); + int oldCount = timeoutSpy.size(); - QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.count() > oldCount, TIMEOUT_TIMEOUT); + QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.size() > oldCount, TIMEOUT_TIMEOUT); +} + +void tst_QTimer::singleShotNormalizes_data() +{ + QTest::addColumn<QByteArray>("slotName"); + + QTest::newRow("normalized") << QByteArray(SLOT(exitLoop())); + + QTest::newRow("space-before") << QByteArray(SLOT( exitLoop())); + QTest::newRow("space-after") << QByteArray(SLOT(exitLoop ())); + QTest::newRow("space-around") << QByteArray(SLOT( exitLoop ())); + QTest::newRow("spaces-before") << QByteArray(SLOT( exitLoop())); + QTest::newRow("spaces-after") << QByteArray(SLOT(exitLoop ())); + QTest::newRow("spaces-around") << QByteArray(SLOT( exitLoop ())); + + QTest::newRow("space-in-parens") << QByteArray(SLOT(exitLoop( ))); + QTest::newRow("spaces-in-parens") << QByteArray(SLOT(exitLoop( ))); + QTest::newRow("space-after-parens") << QByteArray(SLOT(exitLoop() )); + QTest::newRow("spaces-after-parens") << QByteArray(SLOT(exitLoop() )); +} + +void tst_QTimer::singleShotNormalizes() +{ + static constexpr auto TestTimeout = 250ms; + QFETCH(QByteArray, slotName); + QEventLoop loop; + + // control test: regular connection + { + QTimer timer; + QVERIFY(QObject::connect(&timer, SIGNAL(timeout()), &QTestEventLoop::instance(), slotName)); + timer.setSingleShot(true); + timer.start(1); + QTestEventLoop::instance().enterLoop(TestTimeout); + QVERIFY(!QTestEventLoop::instance().timeout()); + } + + // non-zero time + QTimer::singleShot(1, &QTestEventLoop::instance(), slotName); + QTestEventLoop::instance().enterLoop(TestTimeout); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QTimer::singleShot(1ms, &QTestEventLoop::instance(), slotName); + QTestEventLoop::instance().enterLoop(TestTimeout); + QVERIFY(!QTestEventLoop::instance().timeout()); + + // zero time + QTimer::singleShot(0, &QTestEventLoop::instance(), slotName); + QTestEventLoop::instance().enterLoop(TestTimeout); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QTimer::singleShot(0ms, &QTestEventLoop::instance(), slotName); + QTestEventLoop::instance().enterLoop(TestTimeout); + QVERIFY(!QTestEventLoop::instance().timeout()); +} + +void tst_QTimer::sequentialTimers_data() +{ +#ifdef Q_OS_WIN + QSKIP("The API used by QEventDispatcherWin32 doesn't respect the order"); +#endif + QTest::addColumn<QList<int>>("timeouts"); + auto addRow = [](const QList<int> &l) { + QByteArray name; + int last = -1; + for (int i = 0; i < l.size(); ++i) { + Q_ASSERT_X(l[i] >= last, "tst_QTimer", "input list must be sorted"); + name += QByteArray::number(l[i]) + ','; + } + name.chop(1); + QTest::addRow("%s", name.constData()) << l; + }; + // PreciseTimers + addRow({0, 0, 0, 0, 0, 0}); + addRow({0, 1, 2}); + addRow({1, 1, 1, 2, 2, 2, 2}); + addRow({1, 2, 3}); + addRow({19, 19, 19}); + // CoarseTimer for setInterval + addRow({20, 20, 20, 20, 20}); + addRow({25, 25, 25, 25, 25, 25, 50}); +} + +void tst_QTimer::sequentialTimers() +{ + QFETCH(const QList<int>, timeouts); + QByteArray result, expected; + std::vector<std::unique_ptr<QTimer>> timers; + expected.resize(timeouts.size()); + result.reserve(timeouts.size()); + timers.reserve(timeouts.size()); + for (int i = 0; i < timeouts.size(); ++i) { + auto timer = std::make_unique<QTimer>(); + timer->setSingleShot(true); + timer->setInterval(timeouts[i]); + + char c = 'A' + i; + expected[i] = c; + QObject::connect(timer.get(), &QTimer::timeout, this, [&result, c = c]() { + result.append(c); + }); + timers.push_back(std::move(timer)); + } + + // start the timers + for (auto &timer : timers) + timer->start(); + + QTestEventLoop::instance().enterLoopMSecs(timeouts.last() * 2 + 10); + + QCOMPARE(result, expected); +} + +void tst_QTimer::singleShotSequentialTimers_data() +{ + sequentialTimers_data(); +} + +void tst_QTimer::singleShotSequentialTimers() +{ + QFETCH(const QList<int>, timeouts); + QByteArray result, expected; + expected.resize(timeouts.size()); + result.reserve(timeouts.size()); + for (int i = 0; i < timeouts.size(); ++i) { + char c = 'A' + i; + expected[i] = c; + QTimer::singleShot(timeouts[i], this, [&result, c = c]() { + result.append(c); + }); + } + + QTestEventLoop::instance().enterLoopMSecs(timeouts.last() * 2 + 10); + + QCOMPARE(result, expected); } void tst_QTimer::remainingTime() @@ -265,19 +406,14 @@ void tst_QTimer::remainingTimeDuringActivation() namespace { -#if __has_include(<chrono>) template <typename T> std::chrono::milliseconds to_ms(T t) { return std::chrono::duration_cast<std::chrono::milliseconds>(t); } -#endif } // unnamed namespace void tst_QTimer::basic_chrono() { -#if !__has_include(<chrono>) - QSKIP("This test requires C++11 <chrono> support"); -#else // duplicates zeroTimer, singleShotTimeout, interval and remainingTime using namespace std::chrono; QTimer timer; @@ -289,24 +425,24 @@ void tst_QTimer::basic_chrono() QCoreApplication::processEvents(); - QCOMPARE(timeoutSpy.count(), 1); + QCOMPARE(timeoutSpy.size(), 1); timeoutSpy.clear(); timer.start(milliseconds(100)); - QCOMPARE(timeoutSpy.count(), 0); + QCOMPARE(timeoutSpy.size(), 0); QVERIFY(timeoutSpy.wait(TIMEOUT_TIMEOUT)); - QVERIFY(timeoutSpy.count() > 0); - int oldCount = timeoutSpy.count(); + QVERIFY(timeoutSpy.size() > 0); + int oldCount = timeoutSpy.size(); QVERIFY(timeoutSpy.wait(TIMEOUT_TIMEOUT)); - QVERIFY(timeoutSpy.count() > oldCount); + QVERIFY(timeoutSpy.size() > oldCount); timeoutSpy.clear(); timer.start(to_ms(microseconds(200000))); QCOMPARE(timer.intervalAsDuration().count(), milliseconds::rep(200)); QTest::qWait(50); - QCOMPARE(timeoutSpy.count(), 0); + QCOMPARE(timeoutSpy.size(), 0); milliseconds rt = timer.remainingTimeAsDuration(); QVERIFY2(rt.count() >= 50 && rt.count() <= 200, qPrintable(QString::number(rt.count()))); @@ -315,10 +451,9 @@ void tst_QTimer::basic_chrono() timer.setSingleShot(true); timer.start(milliseconds(100)); QVERIFY(timeoutSpy.wait(TIMEOUT_TIMEOUT)); - QCOMPARE(timeoutSpy.count(), 1); + QCOMPARE(timeoutSpy.size(), 1); QTest::qWait(500); - QCOMPARE(timeoutSpy.count(), 1); -#endif + QCOMPARE(timeoutSpy.size(), 1); } void tst_QTimer::livelock_data() @@ -543,6 +678,7 @@ void tst_QTimer::moveToThread() #endif QTimer ti1; QTimer ti2; + ti1.setSingleShot(true); ti1.start(MOVETOTHREAD_TIMEOUT); ti2.start(MOVETOTHREAD_TIMEOUT); QVERIFY((ti1.timerId() & 0xffffff) != (ti2.timerId() & 0xffffff)); @@ -681,12 +817,10 @@ void tst_QTimer::timerFiresOnlyOncePerProcessEvents() class TimerIdPersistsAfterThreadExitThread : public QThread { public: - QTimer *timer; - int timerId, returnValue; + QTimer *timer = nullptr; + Qt::TimerId timerId = Qt::TimerId::Invalid; + int returnValue = -1; - TimerIdPersistsAfterThreadExitThread() - : QThread(), timer(0), timerId(-1), returnValue(-1) - { } ~TimerIdPersistsAfterThreadExitThread() { delete timer; @@ -698,11 +832,15 @@ public: timer = new QTimer; connect(timer, SIGNAL(timeout()), &eventLoop, SLOT(quit())); timer->start(100); - timerId = timer->timerId(); + timerId = timer->id(); returnValue = eventLoop.exec(); } }; +namespace { +int operator&(Qt::TimerId id, int i) { return qToUnderlying(id) & i; } +} + void tst_QTimer::timerIdPersistsAfterThreadExit() { TimerIdPersistsAfterThreadExitThread thread; @@ -728,6 +866,19 @@ void tst_QTimer::cancelLongTimer() QVERIFY(!timer.isActive()); } +void tst_QTimer::testTimerId() +{ + QTimer timer; + timer.start(100ms); + QVERIFY(timer.isActive()); + QCOMPARE_GT(timer.timerId(), 0); + QCOMPARE_GT(timer.id(), Qt::TimerId::Invalid); + timer.stop(); + QVERIFY(!timer.isActive()); + QCOMPARE(timer.timerId(), -1); + QCOMPARE(timer.id(), Qt::TimerId::Invalid); +} + class TimeoutCounter : public QObject { Q_OBJECT @@ -902,7 +1053,7 @@ void tst_QTimer::singleShotToFunctors() thread.wait(); struct MoveOnly : CountedStruct { - Q_DISABLE_COPY(MoveOnly); + Q_DISABLE_COPY(MoveOnly) MoveOnly(MoveOnly &&o) : CountedStruct(std::move(o)) {}; MoveOnly(int *c) : CountedStruct(c) {} }; @@ -915,9 +1066,6 @@ void tst_QTimer::singleShotToFunctors() void tst_QTimer::singleShot_chrono() { -#if !__has_include(<chrono>) - QSKIP("This test requires C++11 <chrono> support"); -#else // duplicates singleShotStaticFunctionZeroTimeout and singleShotToFunctors using namespace std::chrono; { @@ -954,7 +1102,6 @@ void tst_QTimer::singleShot_chrono() QTRY_COMPARE(count, 3); _e.reset(); -#endif } class DontBlockEvents : public QObject @@ -1036,30 +1183,57 @@ void tst_QTimer::postedEventsShouldNotStarveTimers() timer.start(); SlotRepeater slotRepeater; slotRepeater.repeatThisSlot(); - QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.count() > 5, 100); + QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.size() > 5, 100); } struct DummyFunctor { - void operator()() {} + static QThread *callThread; + void operator()() { + callThread = QThread::currentThread(); + callThread->quit(); + } }; +QThread *DummyFunctor::callThread = nullptr; + +void tst_QTimer::crossThreadSingleShotToFunctor_data() +{ + QTest::addColumn<int>("timeout"); + + QTest::addRow("zero-timer") << 0; + QTest::addRow("1ms") << 1; +} 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(); + QFETCH(int, timeout); + // We're also testing for crashes here, so the test simply running to + // completion is part of the success + DummyFunctor::callThread = nullptr; - QObject* o = new QObject(); + QThread t; + std::unique_ptr<QObject> o(new QObject()); o->moveToThread(&t); - for (int i = 0; i < 10000; i++) { - QTimer::singleShot(0, o, DummyFunctor()); - } + QTimer::singleShot(timeout, o.get(), DummyFunctor()); + + // wait enough time for the timer to have timed out before the timer + // could be start in the receiver's thread. + QTest::qWait(10 + timeout * 10); + t.start(); + t.wait(); + QCOMPARE(DummyFunctor::callThread, &t); + + // continue with a stress test - the calling thread is busy, the + // timer should still fire and no crashes. + DummyFunctor::callThread = nullptr; + t.start(); + for (int i = 0; i < 10000; i++) + QTimer::singleShot(timeout, o.get(), DummyFunctor()); - t.quit(); t.wait(); - delete o; + o.reset(); + + QCOMPARE(DummyFunctor::callThread, &t); } void tst_QTimer::callOnTimeout() @@ -1079,7 +1253,7 @@ void tst_QTimer::callOnTimeout() QTest::qWait(100); QCOMPARE(count, 2); - QCOMPARE(timeoutSpy.count(), 1); + QCOMPARE(timeoutSpy.size(), 1); // Test that connection is bound to context lifetime QVERIFY(connection); @@ -1132,6 +1306,23 @@ void tst_QTimer::bindToTimer() timer.stop(); QVERIFY(!active); + + auto ignoreMsg = [] { + QTest::ignoreMessage(QtWarningMsg, + "QObject::startTimer: Timers cannot have negative intervals"); + }; + + // also test that using negative interval updates the binding correctly + timer.start(100); + QVERIFY(active); + ignoreMsg(); + timer.setInterval(-100); + QVERIFY(!active); + timer.start(100); + QVERIFY(active); + ignoreMsg(); + timer.start(-100); + QVERIFY(!active); } void tst_QTimer::bindTimer() @@ -1176,6 +1367,73 @@ void tst_QTimer::bindTimer() QCOMPARE(timer.timerType(), Qt::VeryCoarseTimer); } +void tst_QTimer::automatedBindingTests() +{ + QTimer timer; + + QVERIFY(!timer.isSingleShot()); + QTestPrivate::testReadWritePropertyBasics(timer, true, false, "singleShot"); + if (QTest::currentTestFailed()) { + qDebug("Failed property test for QTimer::singleShot"); + return; + } + + QCOMPARE_NE(timer.interval(), 10); + QTestPrivate::testReadWritePropertyBasics(timer, 10, 20, "interval"); + if (QTest::currentTestFailed()) { + qDebug("Failed property test for QTimer::interval"); + return; + } + + QCOMPARE_NE(timer.timerType(), Qt::PreciseTimer); + QTestPrivate::testReadWritePropertyBasics(timer, Qt::PreciseTimer, Qt::CoarseTimer, + "timerType"); + if (QTest::currentTestFailed()) { + qDebug("Failed property test for QTimer::timerType"); + return; + } + + timer.start(1000); + QVERIFY(timer.isActive()); + QTestPrivate::testReadOnlyPropertyBasics(timer, true, false, "active", + [&timer]() { timer.stop(); }); + if (QTest::currentTestFailed()) { + qDebug("Failed property test for QTimer::active"); + return; + } +} + +void tst_QTimer::negativeInterval() +{ + auto ignoreMsg = [] { + QTest::ignoreMessage(QtWarningMsg, + "QObject::startTimer: Timers cannot have negative intervals"); + }; + + QTimer timer; + + // Starting with a negative interval does not change active state. + ignoreMsg(); + timer.start(-100ms); + QVERIFY(!timer.isActive()); + + // Updating the interval to a negative value stops the timer and changes + // the active state. + timer.start(100ms); + QVERIFY(timer.isActive()); + ignoreMsg(); + timer.setInterval(-100); + QVERIFY(!timer.isActive()); + + // Starting with a negative interval when already started leads to stop + // and inactive state. + timer.start(100); + QVERIFY(timer.isActive()); + ignoreMsg(); + timer.start(-100ms); + QVERIFY(!timer.isActive()); +} + class OrderHelper : public QObject { Q_OBJECT @@ -1249,14 +1507,10 @@ void tst_QTimer::timerOrder_data() 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 |