| 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 | |