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 | |
37 | enum { |
38 | PreciseTimerInterval = 10, |
39 | CoarseTimerInterval = 200, |
40 | VeryCoarseTimerInterval = 1000 |
41 | }; |
42 | |
43 | class 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 | |
53 | protected: |
54 | bool event(QEvent *e); |
55 | |
56 | public: |
57 | inline tst_QEventDispatcher() |
58 | : QObject(), |
59 | eventDispatcher(QAbstractEventDispatcher::instance(thread: thread())) |
60 | { } |
61 | |
62 | private 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 | |
75 | bool 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 |
93 | void 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 | |
102 | class TimerManager { |
103 | Q_DISABLE_COPY(TimerManager) |
104 | |
105 | public: |
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 | |
160 | private: |
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 |
201 | void 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 | |
280 | void 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 |
289 | void 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 | |
310 | class ProcessEventsOnlySendsQueuedEvents : public QObject |
311 | { |
312 | Q_OBJECT |
313 | public: |
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 | } |
327 | public slots: |
328 | void timerFired() |
329 | { |
330 | QCoreApplication::postEvent(receiver: this, event: new QEvent(QEvent::Type(QEvent::User + 1))); |
331 | } |
332 | }; |
333 | |
334 | void 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 | |
354 | void 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 | |
379 | void 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. |
427 | void 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 | |
449 | QTEST_MAIN(tst_QEventDispatcher) |
450 | #include "tst_qeventdispatcher.moc" |
451 | |