1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#ifdef QT_GUI_LIB
30# include <QtGui/QGuiApplication>
31# define tst_QEventDispatcher tst_QGuiEventDispatcher
32#else
33# include <QtCore/QCoreApplication>
34#endif
35#include <QtTest/QtTest>
36
37enum {
38 PreciseTimerInterval = 10,
39 CoarseTimerInterval = 200,
40 VeryCoarseTimerInterval = 1000
41};
42
43class tst_QEventDispatcher : public QObject
44{
45 Q_OBJECT
46
47 QAbstractEventDispatcher *eventDispatcher;
48
49 int receivedEventType = -1;
50 int timerIdFromEvent = -1;
51 bool doubleTimer = false;
52
53protected:
54 bool event(QEvent *e);
55
56public:
57 inline tst_QEventDispatcher()
58 : QObject(),
59 eventDispatcher(QAbstractEventDispatcher::instance(thread: thread()))
60 { }
61
62private slots:
63 void initTestCase();
64 void registerTimer();
65 /* void registerSocketNotifier(); */ // Not implemented here, see tst_QSocketNotifier instead
66 /* void registerEventNotifiier(); */ // Not implemented here, see tst_QWinEventNotifier instead
67 void sendPostedEvents_data();
68 void sendPostedEvents();
69 void processEventsOnlySendsQueuedEvents();
70 void postedEventsPingPong();
71 void eventLoopExit();
72 void interruptTrampling();
73};
74
75bool tst_QEventDispatcher::event(QEvent *e)
76{
77 switch (receivedEventType = e->type()) {
78 case QEvent::Timer:
79 {
80 // sometimes, two timers fire during a single QTRY_xxx wait loop
81 if (timerIdFromEvent != -1)
82 doubleTimer = true;
83 timerIdFromEvent = static_cast<QTimerEvent *>(e)->timerId();
84 return true;
85 }
86 default:
87 break;
88 }
89 return QObject::event(event: e);
90}
91
92// drain the system event queue after the test starts to avoid destabilizing the test functions
93void tst_QEventDispatcher::initTestCase()
94{
95 QElapsedTimer elapsedTimer;
96 elapsedTimer.start();
97 while (!elapsedTimer.hasExpired(timeout: CoarseTimerInterval) && eventDispatcher->processEvents(flags: QEventLoop::AllEvents)) {
98 ;
99 }
100}
101
102class TimerManager {
103 Q_DISABLE_COPY(TimerManager)
104
105public:
106 TimerManager(QAbstractEventDispatcher *eventDispatcher, QObject *parent)
107 : m_eventDispatcher(eventDispatcher), m_parent(parent)
108 {
109 }
110
111 ~TimerManager()
112 {
113 if (!registeredTimers().isEmpty())
114 m_eventDispatcher->unregisterTimers(object: m_parent);
115 }
116
117 TimerManager(TimerManager &&) = delete;
118 TimerManager &operator=(TimerManager &&) = delete;
119
120 int preciseTimerId() const { return m_preciseTimerId; }
121 int coarseTimerId() const { return m_coarseTimerId; }
122 int veryCoarseTimerId() const { return m_veryCoarseTimerId; }
123
124 bool foundPrecise() const { return m_preciseTimerId > 0; }
125 bool foundCoarse() const { return m_coarseTimerId > 0; }
126 bool foundVeryCoarse() const { return m_veryCoarseTimerId > 0; }
127
128 QList<QAbstractEventDispatcher::TimerInfo> registeredTimers() const
129 {
130 return m_eventDispatcher->registeredTimers(object: m_parent);
131 }
132
133 void registerAll()
134 {
135 // start 3 timers, each with the different timer types and different intervals
136 m_preciseTimerId = m_eventDispatcher->registerTimer(
137 interval: PreciseTimerInterval, timerType: Qt::PreciseTimer, object: m_parent);
138 m_coarseTimerId = m_eventDispatcher->registerTimer(
139 interval: CoarseTimerInterval, timerType: Qt::CoarseTimer, object: m_parent);
140 m_veryCoarseTimerId = m_eventDispatcher->registerTimer(
141 interval: VeryCoarseTimerInterval, timerType: Qt::VeryCoarseTimer, object: m_parent);
142 QVERIFY(m_preciseTimerId > 0);
143 QVERIFY(m_coarseTimerId > 0);
144 QVERIFY(m_veryCoarseTimerId > 0);
145 findTimers();
146 }
147
148 void unregister(int timerId)
149 {
150 m_eventDispatcher->unregisterTimer(timerId);
151 findTimers();
152 }
153
154 void unregisterAll()
155 {
156 m_eventDispatcher->unregisterTimers(object: m_parent);
157 findTimers();
158 }
159
160private:
161 void findTimers()
162 {
163 bool foundPrecise = false;
164 bool foundCoarse = false;
165 bool foundVeryCoarse = false;
166 const QList<QAbstractEventDispatcher::TimerInfo> timers = registeredTimers();
167 for (int i = 0; i < timers.count(); ++i) {
168 const QAbstractEventDispatcher::TimerInfo &timerInfo = timers.at(i);
169 if (timerInfo.timerId == m_preciseTimerId) {
170 QCOMPARE(timerInfo.interval, int(PreciseTimerInterval));
171 QCOMPARE(timerInfo.timerType, Qt::PreciseTimer);
172 foundPrecise = true;
173 } else if (timerInfo.timerId == m_coarseTimerId) {
174 QCOMPARE(timerInfo.interval, int(CoarseTimerInterval));
175 QCOMPARE(timerInfo.timerType, Qt::CoarseTimer);
176 foundCoarse = true;
177 } else if (timerInfo.timerId == m_veryCoarseTimerId) {
178 QCOMPARE(timerInfo.interval, int(VeryCoarseTimerInterval));
179 QCOMPARE(timerInfo.timerType, Qt::VeryCoarseTimer);
180 foundVeryCoarse = true;
181 }
182 }
183 if (!foundPrecise)
184 m_preciseTimerId = -1;
185 if (!foundCoarse)
186 m_coarseTimerId = -1;
187 if (!foundVeryCoarse)
188 m_veryCoarseTimerId = -1;
189 }
190
191 QAbstractEventDispatcher *m_eventDispatcher = nullptr;
192
193 int m_preciseTimerId = -1;
194 int m_coarseTimerId = -1;
195 int m_veryCoarseTimerId = -1;
196
197 QObject *m_parent = nullptr;
198};
199
200// test that the eventDispatcher's timer implementation is complete and working
201void tst_QEventDispatcher::registerTimer()
202{
203 TimerManager timers(eventDispatcher, this);
204 timers.registerAll();
205 if (QTest::currentTestFailed())
206 return;
207
208 // check that all 3 are present in the eventDispatcher's registeredTimer() list
209 QCOMPARE(timers.registeredTimers().count(), 3);
210 QVERIFY(timers.foundPrecise());
211 QVERIFY(timers.foundCoarse());
212 QVERIFY(timers.foundVeryCoarse());
213
214#ifdef Q_OS_DARWIN
215 /*
216 We frequently experience flaky failures on macOS. Assumption is that this is
217 due to undeterministic VM scheduling, making us process events for significantly
218 longer than expected and resulting in timers firing in undefined order.
219 To detect this condition, we use a QElapsedTimer, and skip the test.
220 */
221 QElapsedTimer elapsedTimer;
222 elapsedTimer.start();
223#endif
224
225 // process events, waiting for the next event... this should only fire the precise timer
226 receivedEventType = -1;
227 timerIdFromEvent = -1;
228 doubleTimer = false;
229 QTRY_COMPARE_WITH_TIMEOUT(receivedEventType, int(QEvent::Timer), PreciseTimerInterval * 2);
230
231#ifdef Q_OS_DARWIN
232 if (doubleTimer)
233 QSKIP("Double timer during a single timeout - aborting test as flaky on macOS");
234 if (timerIdFromEvent != timers.preciseTimerId()
235 && elapsedTimer.elapsed() > PreciseTimerInterval * 3)
236 QSKIP("Ignore flaky test behavior due to VM scheduling on macOS");
237#endif
238
239 QCOMPARE(timerIdFromEvent, timers.preciseTimerId());
240 // now unregister it and make sure it's gone
241 timers.unregister(timerId: timers.preciseTimerId());
242 if (QTest::currentTestFailed())
243 return;
244 QCOMPARE(timers.registeredTimers().count(), 2);
245 QVERIFY(!timers.foundPrecise());
246 QVERIFY(timers.foundCoarse());
247 QVERIFY(timers.foundVeryCoarse());
248
249 // do the same again for the coarse timer
250 receivedEventType = -1;
251 timerIdFromEvent = -1;
252 doubleTimer = false;
253 QTRY_COMPARE_WITH_TIMEOUT(receivedEventType, int(QEvent::Timer), CoarseTimerInterval * 2);
254
255#ifdef Q_OS_DARWIN
256 if (doubleTimer)
257 QSKIP("Double timer during a single timeout - aborting test as flaky on macOS");
258 if (timerIdFromEvent != timers.coarseTimerId()
259 && elapsedTimer.elapsed() > CoarseTimerInterval * 3)
260 QSKIP("Ignore flaky test behavior due to VM scheduling on macOS");
261#endif
262
263 QCOMPARE(timerIdFromEvent, timers.coarseTimerId());
264 // now unregister it and make sure it's gone
265 timers.unregister(timerId: timers.coarseTimerId());
266 if (QTest::currentTestFailed())
267 return;
268 QCOMPARE(timers.registeredTimers().count(), 1);
269 QVERIFY(!timers.foundPrecise());
270 QVERIFY(!timers.foundCoarse());
271 QVERIFY(timers.foundVeryCoarse());
272
273 // not going to wait for the VeryCoarseTimer, would take too long, just unregister it
274 timers.unregisterAll();
275 if (QTest::currentTestFailed())
276 return;
277 QVERIFY(timers.registeredTimers().isEmpty());
278}
279
280void tst_QEventDispatcher::sendPostedEvents_data()
281{
282 QTest::addColumn<int>(name: "processEventsFlagsInt");
283
284 QTest::newRow(dataTag: "WaitForMoreEvents") << int(QEventLoop::WaitForMoreEvents);
285 QTest::newRow(dataTag: "AllEvents") << int(QEventLoop::AllEvents);
286}
287
288// test that the eventDispatcher sends posted events correctly
289void tst_QEventDispatcher::sendPostedEvents()
290{
291 QFETCH(int, processEventsFlagsInt);
292 QEventLoop::ProcessEventsFlags processEventsFlags = QEventLoop::ProcessEventsFlags(processEventsFlagsInt);
293
294 QElapsedTimer elapsedTimer;
295 elapsedTimer.start();
296 while (!elapsedTimer.hasExpired(timeout: 200)) {
297 receivedEventType = -1;
298 QCoreApplication::postEvent(receiver: this, event: new QEvent(QEvent::User));
299
300 // event shouldn't be delivered as a result of posting
301 QCOMPARE(receivedEventType, -1);
302
303 // since there is a pending posted event, this should not actually block, it should send the posted event and return
304 QVERIFY(eventDispatcher->processEvents(processEventsFlags));
305 // event shouldn't be delivered as a result of posting
306 QCOMPARE(receivedEventType, int(QEvent::User));
307 }
308}
309
310class ProcessEventsOnlySendsQueuedEvents : public QObject
311{
312 Q_OBJECT
313public:
314 int eventsReceived;
315
316 inline ProcessEventsOnlySendsQueuedEvents() : eventsReceived(0) {}
317
318 bool event(QEvent *event)
319 {
320 ++eventsReceived;
321
322 if (event->type() == QEvent::User)
323 QCoreApplication::postEvent(receiver: this, event: new QEvent(QEvent::Type(QEvent::User + 1)));
324
325 return QObject::event(event);
326 }
327public slots:
328 void timerFired()
329 {
330 QCoreApplication::postEvent(receiver: this, event: new QEvent(QEvent::Type(QEvent::User + 1)));
331 }
332};
333
334void tst_QEventDispatcher::processEventsOnlySendsQueuedEvents()
335{
336 ProcessEventsOnlySendsQueuedEvents object;
337
338 // Posted events during event processing should be handled on
339 // the next processEvents iteration.
340 QCoreApplication::postEvent(receiver: &object, event: new QEvent(QEvent::User));
341 QCoreApplication::processEvents();
342 QCOMPARE(object.eventsReceived, 1);
343 QCoreApplication::processEvents();
344 QCOMPARE(object.eventsReceived, 2);
345
346 // The same goes for posted events during timer processing
347 QTimer::singleShot(msec: 0, receiver: &object, SLOT(timerFired()));
348 QCoreApplication::processEvents();
349 QCOMPARE(object.eventsReceived, 3);
350 QCoreApplication::processEvents();
351 QCOMPARE(object.eventsReceived, 4);
352}
353
354void tst_QEventDispatcher::postedEventsPingPong()
355{
356 QEventLoop mainLoop;
357
358 // We need to have at least two levels of nested loops
359 // for the posted event to get stuck (QTBUG-85981).
360 QMetaObject::invokeMethod(context: this, function: [this, &mainLoop]() {
361 QMetaObject::invokeMethod(context: this, function: [&mainLoop]() {
362 // QEventLoop::quit() should be invoked on the next
363 // iteration of mainLoop.exec().
364 QMetaObject::invokeMethod(object: &mainLoop, function: &QEventLoop::quit,
365 type: Qt::QueuedConnection);
366 }, type: Qt::QueuedConnection);
367 mainLoop.processEvents();
368 }, type: Qt::QueuedConnection);
369
370 // We should use Qt::CoarseTimer on Windows, to prevent event
371 // dispatcher from sending a posted event.
372 QTimer::singleShot(interval: 500, timerType: Qt::CoarseTimer, slot: [&mainLoop]() {
373 mainLoop.exit(returnCode: 1);
374 });
375
376 QCOMPARE(mainLoop.exec(), 0);
377}
378
379void tst_QEventDispatcher::eventLoopExit()
380{
381 // This test was inspired by QTBUG-79477. A particular
382 // implementation detail in QCocoaEventDispatcher allowed
383 // QEventLoop::exit() to fail to really exit the event loop.
384 // Thus this test is a part of the dispatcher auto-test.
385
386 // Imitates QApplication::exec():
387 QEventLoop mainLoop;
388 // The test itself is a lambda:
389 QTimer::singleShot(interval: 0, slot: [&mainLoop]() {
390 // Two more single shots, both will be posted as events
391 // (zero timeout) and supposed to be processes by the
392 // mainLoop:
393
394 QTimer::singleShot(interval: 0, slot: [&mainLoop]() {
395 // wakeUp triggers QCocoaEventDispatcher into incrementing
396 // its 'serialNumber':
397 mainLoop.wakeUp();
398 // QCocoaEventDispatcher::processEvents() will process
399 // posted events and execute the second lambda defined below:
400 QCoreApplication::processEvents();
401 });
402
403 QTimer::singleShot(interval: 0, slot: [&mainLoop]() {
404 // With QCocoaEventDispatcher this is executed while in the
405 // processEvents (see above) and would fail to actually
406 // interrupt the loop.
407 mainLoop.exit();
408 });
409 });
410
411 bool timeoutObserved = false;
412 QTimer::singleShot(interval: 500, slot: [&timeoutObserved, &mainLoop]() {
413 // In case the QEventLoop::exit above failed, we have to bail out
414 // early, not wasting time:
415 mainLoop.exit();
416 timeoutObserved = true;
417 });
418
419 mainLoop.exec();
420 QVERIFY(!timeoutObserved);
421}
422
423// Based on QTBUG-91539: In the event dispatcher on Windows we overwrite the
424// interrupt once we start processing events (this pattern is also in the 'unix' dispatcher)
425// which would lead the dispatcher to accidentally ignore certain interrupts and,
426// as in the bug report, would not quit, leaving the thread alive and running.
427void tst_QEventDispatcher::interruptTrampling()
428{
429 class WorkerThread : public QThread
430 {
431 void run() override {
432 auto dispatcher = eventDispatcher();
433 QVERIFY(dispatcher);
434 dispatcher->processEvents(flags: QEventLoop::AllEvents);
435 QTimer::singleShot(interval: 0, slot: [dispatcher]() {
436 dispatcher->wakeUp();
437 });
438 dispatcher->processEvents(flags: QEventLoop::WaitForMoreEvents);
439 dispatcher->interrupt();
440 dispatcher->processEvents(flags: QEventLoop::WaitForMoreEvents);
441 }
442 };
443 WorkerThread thread;
444 thread.start();
445 QVERIFY(thread.wait(1000));
446 QVERIFY(thread.isFinished());
447}
448
449QTEST_MAIN(tst_QEventDispatcher)
450#include "tst_qeventdispatcher.moc"
451

source code of qtbase/tests/auto/corelib/kernel/qeventdispatcher/tst_qeventdispatcher.cpp