summaryrefslogtreecommitdiffstats
path: root/tests/auto/corelib/kernel/qeventdispatcher/tst_qeventdispatcher.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/corelib/kernel/qeventdispatcher/tst_qeventdispatcher.cpp')
-rw-r--r--tests/auto/corelib/kernel/qeventdispatcher/tst_qeventdispatcher.cpp314
1 files changed, 229 insertions, 85 deletions
diff --git a/tests/auto/corelib/kernel/qeventdispatcher/tst_qeventdispatcher.cpp b/tests/auto/corelib/kernel/qeventdispatcher/tst_qeventdispatcher.cpp
index fa5c78bceb..285d080960 100644
--- a/tests/auto/corelib/kernel/qeventdispatcher/tst_qeventdispatcher.cpp
+++ b/tests/auto/corelib/kernel/qeventdispatcher/tst_qeventdispatcher.cpp
@@ -1,44 +1,49 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** 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) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifdef QT_GUI_LIB
# include <QtGui/QGuiApplication>
-# define tst_QEventDispatcher tst_QGuiEventDispatcher
#else
# include <QtCore/QCoreApplication>
#endif
-#include <QtTest/QtTest>
+#include <QTest>
+#include <QAbstractEventDispatcher>
+#include <QTimer>
+#include <QThreadPool>
+
+#ifdef DISABLE_GLIB
+static bool glibDisabled = []() {
+ qputenv("QT_NO_GLIB", "1");
+ return true;
+}();
+#endif
-enum {
- PreciseTimerInterval = 10,
- CoarseTimerInterval = 200,
- VeryCoarseTimerInterval = 1000
-};
+#include <chrono>
+
+using namespace std::chrono_literals;
+
+static constexpr auto PreciseTimerInterval = 10ms;
+static constexpr auto CoarseTimerInterval = 200ms;
+static constexpr auto VeryCoarseTimerInterval = 1s;
+
+static constexpr
+std::chrono::nanoseconds fudgeInterval(std::chrono::nanoseconds interval, Qt::TimerType timerType)
+{
+ // Make the intervals have have fractions of milliseconds so we can check
+ // that they have been rounded & stored properly (where applicable).
+ switch (timerType) {
+ case Qt::VeryCoarseTimer:
+ // rounds down (floor) to seconds
+ return interval + 1010us;
+ case Qt::CoarseTimer:
+ // rounds up (ceil) to milliseconds
+ return interval - 10us;
+ case Qt::PreciseTimer:
+ // not rounded using QAbstractEventDispatcherV2; rounded up (ceil) on V1
+ return interval - 10us;
+ }
+ Q_UNREACHABLE_RETURN(std::chrono::nanoseconds::min());
+}
class tst_QEventDispatcher : public QObject
{
@@ -56,19 +61,31 @@ protected:
public:
inline tst_QEventDispatcher()
: QObject(),
- eventDispatcher(QAbstractEventDispatcher::instance(thread()))
+ eventDispatcher(QAbstractEventDispatcher::instance(thread())),
+ isGuiEventDispatcher(QCoreApplication::instance()->inherits("QGuiApplication"))
{ }
private slots:
void initTestCase();
+ void cleanup();
+
void registerTimer();
+
/* void registerSocketNotifier(); */ // Not implemented here, see tst_QSocketNotifier instead
/* void registerEventNotifiier(); */ // Not implemented here, see tst_QWinEventNotifier instead
void sendPostedEvents_data();
void sendPostedEvents();
void processEventsOnlySendsQueuedEvents();
+ // these two tests need to run before postedEventsPingPong
+ void postEventFromThread();
+ void postEventFromEventHandler();
+ // these tests don't leave the event dispatcher in a reliable state
void postedEventsPingPong();
void eventLoopExit();
+ void interruptTrampling();
+
+private:
+ const bool isGuiEventDispatcher;
};
bool tst_QEventDispatcher::event(QEvent *e)
@@ -91,11 +108,15 @@ bool tst_QEventDispatcher::event(QEvent *e)
// drain the system event queue after the test starts to avoid destabilizing the test functions
void tst_QEventDispatcher::initTestCase()
{
- QElapsedTimer elapsedTimer;
- elapsedTimer.start();
- while (!elapsedTimer.hasExpired(CoarseTimerInterval) && eventDispatcher->processEvents(QEventLoop::AllEvents)) {
- ;
- }
+ QDeadlineTimer deadline(CoarseTimerInterval);
+ while (!deadline.hasExpired() && eventDispatcher->processEvents(QEventLoop::AllEvents))
+ ;
+}
+
+// consume pending posted events to avoid impact on the next test function
+void tst_QEventDispatcher::cleanup()
+{
+ eventDispatcher->processEvents(QEventLoop::AllEvents);
}
class TimerManager {
@@ -116,35 +137,36 @@ public:
TimerManager(TimerManager &&) = delete;
TimerManager &operator=(TimerManager &&) = delete;
- int preciseTimerId() const { return m_preciseTimerId; }
- int coarseTimerId() const { return m_coarseTimerId; }
- int veryCoarseTimerId() const { return m_veryCoarseTimerId; }
+ int preciseTimerId() const { return int(m_preciseTimerId); }
+ int coarseTimerId() const { return int(m_coarseTimerId); }
+ int veryCoarseTimerId() const { return int(m_veryCoarseTimerId); }
- bool foundPrecise() const { return m_preciseTimerId > 0; }
- bool foundCoarse() const { return m_coarseTimerId > 0; }
- bool foundVeryCoarse() const { return m_veryCoarseTimerId > 0; }
+ bool foundPrecise() const { return preciseTimerId() > 0; }
+ bool foundCoarse() const { return coarseTimerId() > 0; }
+ bool foundVeryCoarse() const { return veryCoarseTimerId() > 0; }
- QList<QAbstractEventDispatcher::TimerInfo> registeredTimers() const
+ QList<QAbstractEventDispatcher::TimerInfoV2> registeredTimers() const
{
- return m_eventDispatcher->registeredTimers(m_parent);
+ return m_eventDispatcher->timersForObject(m_parent);
}
void registerAll()
{
// start 3 timers, each with the different timer types and different intervals
- m_preciseTimerId = m_eventDispatcher->registerTimer(
- PreciseTimerInterval, Qt::PreciseTimer, m_parent);
- m_coarseTimerId = m_eventDispatcher->registerTimer(
- CoarseTimerInterval, Qt::CoarseTimer, m_parent);
- m_veryCoarseTimerId = m_eventDispatcher->registerTimer(
- VeryCoarseTimerInterval, Qt::VeryCoarseTimer, m_parent);
- QVERIFY(m_preciseTimerId > 0);
- QVERIFY(m_coarseTimerId > 0);
- QVERIFY(m_veryCoarseTimerId > 0);
+ auto registerTimer = [&](std::chrono::milliseconds interval, Qt::TimerType timerType) {
+ return m_eventDispatcher->registerTimer(fudgeInterval(interval, timerType), timerType,
+ m_parent);
+ };
+ m_preciseTimerId = registerTimer(PreciseTimerInterval, Qt::PreciseTimer);
+ m_coarseTimerId = registerTimer(CoarseTimerInterval, Qt::CoarseTimer);
+ m_veryCoarseTimerId = registerTimer(VeryCoarseTimerInterval, Qt::VeryCoarseTimer);
+ QVERIFY(foundPrecise());
+ QVERIFY(foundCoarse());
+ QVERIFY(foundVeryCoarse());
findTimers();
}
- void unregister(int timerId)
+ void unregister(Qt::TimerId timerId)
{
m_eventDispatcher->unregisterTimer(timerId);
findTimers();
@@ -162,36 +184,43 @@ private:
bool foundPrecise = false;
bool foundCoarse = false;
bool foundVeryCoarse = false;
- const QList<QAbstractEventDispatcher::TimerInfo> timers = registeredTimers();
- for (int i = 0; i < timers.count(); ++i) {
- const QAbstractEventDispatcher::TimerInfo &timerInfo = timers.at(i);
+ const QList<QAbstractEventDispatcher::TimerInfoV2> timers = registeredTimers();
+ for (const QAbstractEventDispatcher::TimerInfoV2 &timerInfo : timers) {
if (timerInfo.timerId == m_preciseTimerId) {
- QCOMPARE(timerInfo.interval, int(PreciseTimerInterval));
+ // For precise timers, we expect the fudge factor to be present
+ QAbstractEventDispatcher::Duration interval =
+ fudgeInterval(PreciseTimerInterval, Qt::PreciseTimer);
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
+ if (!qobject_cast<QAbstractEventDispatcherV2 *>(m_eventDispatcher))
+ interval = PreciseTimerInterval;
+#endif
+ QCOMPARE(timerInfo.interval, interval);
QCOMPARE(timerInfo.timerType, Qt::PreciseTimer);
foundPrecise = true;
} else if (timerInfo.timerId == m_coarseTimerId) {
- QCOMPARE(timerInfo.interval, int(CoarseTimerInterval));
+ // For other timers, the fudge factors ought to have been rounded away
+ QCOMPARE(timerInfo.interval, CoarseTimerInterval);
QCOMPARE(timerInfo.timerType, Qt::CoarseTimer);
foundCoarse = true;
} else if (timerInfo.timerId == m_veryCoarseTimerId) {
- QCOMPARE(timerInfo.interval, int(VeryCoarseTimerInterval));
+ QCOMPARE(timerInfo.interval, VeryCoarseTimerInterval);
QCOMPARE(timerInfo.timerType, Qt::VeryCoarseTimer);
foundVeryCoarse = true;
}
}
if (!foundPrecise)
- m_preciseTimerId = -1;
+ m_preciseTimerId = Qt::TimerId::Invalid;
if (!foundCoarse)
- m_coarseTimerId = -1;
+ m_coarseTimerId = Qt::TimerId::Invalid;
if (!foundVeryCoarse)
- m_veryCoarseTimerId = -1;
+ m_veryCoarseTimerId = Qt::TimerId::Invalid;
}
QAbstractEventDispatcher *m_eventDispatcher = nullptr;
- int m_preciseTimerId = -1;
- int m_coarseTimerId = -1;
- int m_veryCoarseTimerId = -1;
+ Qt::TimerId m_preciseTimerId = Qt::TimerId::Invalid;
+ Qt::TimerId m_coarseTimerId = Qt::TimerId::Invalid;
+ Qt::TimerId m_veryCoarseTimerId = Qt::TimerId::Invalid;
QObject *m_parent = nullptr;
};
@@ -205,7 +234,7 @@ void tst_QEventDispatcher::registerTimer()
return;
// check that all 3 are present in the eventDispatcher's registeredTimer() list
- QCOMPARE(timers.registeredTimers().count(), 3);
+ QCOMPARE(timers.registeredTimers().size(), 3);
QVERIFY(timers.foundPrecise());
QVERIFY(timers.foundCoarse());
QVERIFY(timers.foundVeryCoarse());
@@ -231,16 +260,16 @@ void tst_QEventDispatcher::registerTimer()
if (doubleTimer)
QSKIP("Double timer during a single timeout - aborting test as flaky on macOS");
if (timerIdFromEvent != timers.preciseTimerId()
- && elapsedTimer.elapsed() > PreciseTimerInterval * 3)
+ && elapsedTimer.durationElapsed() > PreciseTimerInterval * 3)
QSKIP("Ignore flaky test behavior due to VM scheduling on macOS");
#endif
QCOMPARE(timerIdFromEvent, timers.preciseTimerId());
// now unregister it and make sure it's gone
- timers.unregister(timers.preciseTimerId());
+ timers.unregister(Qt::TimerId(timers.preciseTimerId()));
if (QTest::currentTestFailed())
return;
- QCOMPARE(timers.registeredTimers().count(), 2);
+ QCOMPARE(timers.registeredTimers().size(), 2);
QVERIFY(!timers.foundPrecise());
QVERIFY(timers.foundCoarse());
QVERIFY(timers.foundVeryCoarse());
@@ -255,16 +284,16 @@ void tst_QEventDispatcher::registerTimer()
if (doubleTimer)
QSKIP("Double timer during a single timeout - aborting test as flaky on macOS");
if (timerIdFromEvent != timers.coarseTimerId()
- && elapsedTimer.elapsed() > CoarseTimerInterval * 3)
+ && elapsedTimer.durationElapsed() > CoarseTimerInterval * 3)
QSKIP("Ignore flaky test behavior due to VM scheduling on macOS");
#endif
QCOMPARE(timerIdFromEvent, timers.coarseTimerId());
// now unregister it and make sure it's gone
- timers.unregister(timers.coarseTimerId());
+ timers.unregister(Qt::TimerId(timers.coarseTimerId()));
if (QTest::currentTestFailed())
return;
- QCOMPARE(timers.registeredTimers().count(), 1);
+ QCOMPARE(timers.registeredTimers().size(), 1);
QVERIFY(!timers.foundPrecise());
QVERIFY(!timers.foundCoarse());
QVERIFY(timers.foundVeryCoarse());
@@ -290,9 +319,8 @@ void tst_QEventDispatcher::sendPostedEvents()
QFETCH(int, processEventsFlagsInt);
QEventLoop::ProcessEventsFlags processEventsFlags = QEventLoop::ProcessEventsFlags(processEventsFlagsInt);
- QElapsedTimer elapsedTimer;
- elapsedTimer.start();
- while (!elapsedTimer.hasExpired(200)) {
+ QDeadlineTimer deadline(200ms);
+ while (!deadline.hasExpired()) {
receivedEventType = -1;
QCoreApplication::postEvent(this, new QEvent(QEvent::User));
@@ -350,6 +378,96 @@ void tst_QEventDispatcher::processEventsOnlySendsQueuedEvents()
QCOMPARE(object.eventsReceived, 4);
}
+void tst_QEventDispatcher::postEventFromThread()
+{
+ QThreadPool *threadPool = QThreadPool::globalInstance();
+ QAtomicInt hadToQuit = false;
+ QAtomicInt done = false;
+
+ threadPool->start([&]{
+ int loop = 1000 / 10; // give it a second
+ while (!done && --loop)
+ QThread::sleep(std::chrono::milliseconds{10});
+ if (done)
+ return;
+ hadToQuit = true;
+ QCoreApplication::eventDispatcher()->wakeUp();
+ });
+
+ struct EventReceiver : public QObject {
+ bool event(QEvent* event) override {
+ if (event->type() == QEvent::User)
+ return true;
+ return QObject::event(event);
+ }
+ } receiver;
+
+ int count = 500;
+ while (!hadToQuit && --count) {
+ threadPool->start([&receiver]{
+ QCoreApplication::postEvent(&receiver, new QEvent(QEvent::User));
+ });
+
+ QAbstractEventDispatcher::instance()->processEvents(QEventLoop::WaitForMoreEvents);
+ }
+ done = true;
+
+ QVERIFY(!hadToQuit);
+ QVERIFY(threadPool->waitForDone());
+}
+
+void tst_QEventDispatcher::postEventFromEventHandler()
+{
+ QThreadPool *threadPool = QThreadPool::globalInstance();
+ QAtomicInt hadToQuit = false;
+ QAtomicInt done = false;
+
+ threadPool->start([&]{
+ int loop = 250 / 10; // give it 250ms
+ while (!done && --loop)
+ QThread::sleep(std::chrono::milliseconds{10});
+ if (done)
+ return;
+ hadToQuit = true;
+ QCoreApplication::eventDispatcher()->wakeUp();
+ });
+
+ struct EventReceiver : public QObject {
+ int i = 0;
+ bool event(QEvent* event) override
+ {
+ if (event->type() == QEvent::User) {
+ ++i;
+ if (i < 2)
+ QCoreApplication::postEvent(this, new QEvent(QEvent::User));
+ return true;
+ }
+ return QObject::event(event);
+ }
+ } receiver;
+ QCoreApplication::postEvent(&receiver, new QEvent(QEvent::User));
+ while (receiver.i < 2)
+ QAbstractEventDispatcher::instance()->processEvents(QEventLoop::WaitForMoreEvents);
+ done = true;
+
+ const QByteArrayView eventDispatcherName(QAbstractEventDispatcher::instance()->metaObject()->className());
+ qDebug() << eventDispatcherName;
+ // QXcbUnixEventDispatcher and QEventDispatcherUNIX do not do this correctly on any platform;
+ // both Windows event dispatchers fail as well.
+ const bool knownToFail = eventDispatcherName.contains("UNIX")
+ || eventDispatcherName.contains("Unix")
+ || eventDispatcherName.contains("Win32")
+ || eventDispatcherName.contains("WindowsGui")
+ || eventDispatcherName.contains("Android");
+
+ if (knownToFail)
+ QEXPECT_FAIL("", eventDispatcherName.constData(), Continue);
+
+ QVERIFY(!hadToQuit);
+ QVERIFY(threadPool->waitForDone());
+}
+
+
void tst_QEventDispatcher::postedEventsPingPong()
{
QEventLoop mainLoop;
@@ -368,7 +486,7 @@ void tst_QEventDispatcher::postedEventsPingPong()
// We should use Qt::CoarseTimer on Windows, to prevent event
// dispatcher from sending a posted event.
- QTimer::singleShot(500, Qt::CoarseTimer, [&mainLoop]() {
+ QTimer::singleShot(500, Qt::CoarseTimer, &mainLoop, [&mainLoop]() {
mainLoop.exit(1);
});
@@ -385,12 +503,12 @@ void tst_QEventDispatcher::eventLoopExit()
// Imitates QApplication::exec():
QEventLoop mainLoop;
// The test itself is a lambda:
- QTimer::singleShot(0, [&mainLoop]() {
+ QTimer::singleShot(0, &mainLoop, [&mainLoop]() {
// Two more single shots, both will be posted as events
// (zero timeout) and supposed to be processes by the
// mainLoop:
- QTimer::singleShot(0, [&mainLoop]() {
+ QTimer::singleShot(0, &mainLoop, [&mainLoop]() {
// wakeUp triggers QCocoaEventDispatcher into incrementing
// its 'serialNumber':
mainLoop.wakeUp();
@@ -399,7 +517,7 @@ void tst_QEventDispatcher::eventLoopExit()
QCoreApplication::processEvents();
});
- QTimer::singleShot(0, [&mainLoop]() {
+ QTimer::singleShot(0, &mainLoop, [&mainLoop]() {
// With QCocoaEventDispatcher this is executed while in the
// processEvents (see above) and would fail to actually
// interrupt the loop.
@@ -408,7 +526,7 @@ void tst_QEventDispatcher::eventLoopExit()
});
bool timeoutObserved = false;
- QTimer::singleShot(500, [&timeoutObserved, &mainLoop]() {
+ QTimer::singleShot(500, &mainLoop, [&timeoutObserved, &mainLoop]() {
// In case the QEventLoop::exit above failed, we have to bail out
// early, not wasting time:
mainLoop.exit();
@@ -419,5 +537,31 @@ void tst_QEventDispatcher::eventLoopExit()
QVERIFY(!timeoutObserved);
}
+// Based on QTBUG-91539: In the event dispatcher on Windows we overwrite the
+// interrupt once we start processing events (this pattern is also in the 'unix' dispatcher)
+// which would lead the dispatcher to accidentally ignore certain interrupts and,
+// as in the bug report, would not quit, leaving the thread alive and running.
+void tst_QEventDispatcher::interruptTrampling()
+{
+ class WorkerThread : public QThread
+ {
+ void run() override {
+ auto dispatcher = eventDispatcher();
+ QVERIFY(dispatcher);
+ dispatcher->processEvents(QEventLoop::AllEvents);
+ QTimer::singleShot(0, dispatcher, [dispatcher]() {
+ dispatcher->wakeUp();
+ });
+ dispatcher->processEvents(QEventLoop::WaitForMoreEvents);
+ dispatcher->interrupt();
+ dispatcher->processEvents(QEventLoop::WaitForMoreEvents);
+ }
+ };
+ WorkerThread thread;
+ thread.start();
+ QVERIFY(thread.wait(1000));
+ QVERIFY(thread.isFinished());
+}
+
QTEST_MAIN(tst_QEventDispatcher)
#include "tst_qeventdispatcher.moc"