// UiThreadWatchdog unit tests. Lane GUI. // // Red when a genuinely blocked UI thread (a synchronous sleep with no processEvents in // between — the exact shape of the bug this exists to catch) stops producing a warning, // or a responsive one starts producing a false-positive one. #include #include #include #include #include "util/UiThreadWatchdog.hpp" using velox::gui::UiThreadWatchdog; namespace { QMutex g_mutex; QString g_lastWarning; QtMessageHandler g_prevHandler = nullptr; // Qt's own message handler is process-global and can run on any thread — the watchdog's // warning comes from its background thread while this test's main thread is deliberately // blocked, so this needs real synchronization, not just a plain global (this test runs // under the `tsan` preset too). void captureHandler(QtMsgType type, const QMessageLogContext &ctx, const QString &msg) { if (type == QtWarningMsg) { QMutexLocker locker(&g_mutex); g_lastWarning = msg; } if (g_prevHandler) { g_prevHandler(type, ctx, msg); } } QString lastWarning() { QMutexLocker locker(&g_mutex); return g_lastWarning; } } // namespace class TstUiThreadWatchdog : public QObject { Q_OBJECT private slots: void init(); void cleanup(); void firesOnABlockedUiThread(); void staysQuietWhenResponsive(); }; void TstUiThreadWatchdog::init() { QMutexLocker locker(&g_mutex); g_lastWarning.clear(); g_prevHandler = qInstallMessageHandler(captureHandler); } void TstUiThreadWatchdog::cleanup() { qInstallMessageHandler(g_prevHandler); } void TstUiThreadWatchdog::firesOnABlockedUiThread() { UiThreadWatchdog wd; wd.start(); // Block this thread (the watchdog's "UI thread" here) synchronously and well past // the 200 ms budget — no processEvents at all, exactly what a real stall looks like // and exactly what this exists to catch. QThread::msleep(500); // Let the event loop run so the queued ack the watchdog sent before the sleep started // finally lands (harmless — the warning it's checking for already fired mid-sleep, // from the watchdog's own background thread). QTest::qWait(150); QVERIFY2(lastWarning().contains(QStringLiteral("blocking")), qUtf8Printable(lastWarning())); } void TstUiThreadWatchdog::staysQuietWhenResponsive() { UiThreadWatchdog wd; wd.start(); QTest::qWait(400); // event loop stays responsive throughout — well past the budget QVERIFY(lastWarning().isEmpty()); } QTEST_MAIN(TstUiThreadWatchdog) #include "tst_uithreadwatchdog.moc"