diff options
author | Morten Sørvig <morten.sorvig@qt.io> | 2022-07-06 23:01:59 +0200 |
---|---|---|
committer | Morten Sørvig <morten.sorvig@qt.io> | 2022-08-08 18:14:28 +0200 |
commit | 964765f686bca3ba62833e76e5ed689fce9a62bb (patch) | |
tree | fc0b7cfae17f7a930c07003562115c78548255bc | |
parent | 2a17034ddc513870bda49e5d76cb0b1bf358c754 (diff) |
wasm: add event loop auto test
Add basic tests for timers and event processing, for
different use cases such as on the main thread, on
a secondary thread, and with asyncify.
Pick-to: 6.4
Change-Id: Ie0f82b5de97f639867b1e65dbb0ab8b11db86f85
Reviewed-by: Lorn Potter <lorn.potter@gmail.com>
-rw-r--r-- | tests/manual/wasm/eventloop/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/manual/wasm/eventloop/README.md | 1 | ||||
-rw-r--r-- | tests/manual/wasm/eventloop/eventloop_auto/CMakeLists.txt | 21 | ||||
-rw-r--r-- | tests/manual/wasm/eventloop/eventloop_auto/eventloop_auto.html | 10 | ||||
-rw-r--r-- | tests/manual/wasm/eventloop/eventloop_auto/main.cpp | 312 | ||||
-rw-r--r-- | tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp | 7 | ||||
-rw-r--r-- | tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.h | 1 |
7 files changed, 352 insertions, 1 deletions
diff --git a/tests/manual/wasm/eventloop/CMakeLists.txt b/tests/manual/wasm/eventloop/CMakeLists.txt index 4c754deafe..aadee6af52 100644 --- a/tests/manual/wasm/eventloop/CMakeLists.txt +++ b/tests/manual/wasm/eventloop/CMakeLists.txt @@ -2,6 +2,7 @@ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 add_subdirectory(asyncify_exec) +add_subdirectory(eventloop_auto) add_subdirectory(main_exec) add_subdirectory(main_noexec) add_subdirectory(thread_exec) diff --git a/tests/manual/wasm/eventloop/README.md b/tests/manual/wasm/eventloop/README.md index e5d4b92306..e1a5a1a3b7 100644 --- a/tests/manual/wasm/eventloop/README.md +++ b/tests/manual/wasm/eventloop/README.md @@ -12,3 +12,4 @@ Contents main_noexec Qt main() without QApplication::exec() dialog_exec Shows how QDialog::exec() also does not return thread_exec Shows how to use QThread::exec() + eventloop_auto Event loop autotest (manually run) diff --git a/tests/manual/wasm/eventloop/eventloop_auto/CMakeLists.txt b/tests/manual/wasm/eventloop/eventloop_auto/CMakeLists.txt new file mode 100644 index 0000000000..4212cb832b --- /dev/null +++ b/tests/manual/wasm/eventloop/eventloop_auto/CMakeLists.txt @@ -0,0 +1,21 @@ +qt_internal_add_manual_test(eventloop_auto + SOURCES + main.cpp + ../../qtwasmtestlib/qtwasmtestlib.cpp + PUBLIC_LIBRARIES + Qt::Core +) + +include_directories(../../qtwasmtestlib/) + +add_custom_command( + TARGET eventloop_auto POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/eventloop_auto.html + ${CMAKE_CURRENT_BINARY_DIR}/eventloop_auto.html) + +add_custom_command( + TARGET eventloop_auto POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/../../qtwasmtestlib/qtwasmtestlib.js + ${CMAKE_CURRENT_BINARY_DIR}/qtwasmtestlib.js) diff --git a/tests/manual/wasm/eventloop/eventloop_auto/eventloop_auto.html b/tests/manual/wasm/eventloop/eventloop_auto/eventloop_auto.html new file mode 100644 index 0000000000..7ff9d8e7f2 --- /dev/null +++ b/tests/manual/wasm/eventloop/eventloop_auto/eventloop_auto.html @@ -0,0 +1,10 @@ +<!doctype html> +<script type="text/javascript" src="qtwasmtestlib.js"></script> +<script type="text/javascript" src="eventloop_auto.js"></script> +<script> + window.onload = () => { + runTestCase(document.getElementById("log")); + }; +</script> +<p>Running event dispatcher auto test.</p> +<div id="log"></div> diff --git a/tests/manual/wasm/eventloop/eventloop_auto/main.cpp b/tests/manual/wasm/eventloop/eventloop_auto/main.cpp new file mode 100644 index 0000000000..c6e8bad987 --- /dev/null +++ b/tests/manual/wasm/eventloop/eventloop_auto/main.cpp @@ -0,0 +1,312 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <QtCore/QCoreApplication> +#include <QtCore/QEvent> +#include <QtCore/QMutex> +#include <QtCore/QObject> +#include <QtCore/QThread> +#include <QtCore/QTimer> + +#include <qtwasmtestlib.h> + +const int timerTimeout = 10; + +class WasmEventDispatcherTest: public QObject +{ + Q_OBJECT +private slots: + void postEventMainThread(); + void timerMainThread(); + void timerMainThreadMultiple(); + +#if QT_CONFIG(thread) + void postEventSecondaryThread(); + void postEventSecondaryThreads(); + void postEventToSecondaryThread(); + void timerSecondaryThread(); +#endif + +#ifdef QT_HAVE_EMSCRIPTEN_ASYNCIFY + void postEventAsyncify(); + void timerAsyncify(); + void postEventAsyncifyLoop(); +#endif + +private: +// Disabled test function: Asyncify wait on pthread_join is not supported, +// see https://github.com/emscripten-core/emscripten/issues/9910 +#if QT_CONFIG(thread) && defined(QT_HAVE_EMSCRIPTEN_ASYNCIFY) + void threadAsyncifyWait(); +#endif +}; + +class EventTarget : public QObject +{ + Q_OBJECT + +public: + static EventTarget *create(std::function<void()> callback) + { + return new EventTarget(callback); + } + + static QEvent *createEvent() + { + return new QEvent(QEvent::User); + } + +protected: + EventTarget(std::function<void()> callback) + : m_callback(callback) { } + + bool event(QEvent *evt) + { + if (evt->type() == QEvent::User) { + m_callback(); + deleteLater(); + return true; + } + return QObject::event(evt); + } + +private: + std::function<void()> m_callback; +}; + +class CompleteTestFunctionRefGuard { +public: + CompleteTestFunctionRefGuard(CompleteTestFunctionRefGuard const&) = delete; + CompleteTestFunctionRefGuard& operator=(CompleteTestFunctionRefGuard const&) = delete; + + static CompleteTestFunctionRefGuard *create() { + return new CompleteTestFunctionRefGuard(); + } + + void ref() { + QMutexLocker lock(&mutex); + ++m_counter; + } + + void deref() { + const bool finalDeref = [this] { + QMutexLocker lock(&mutex); + return --m_counter == 0; + }(); + + if (finalDeref) + QtWasmTest::completeTestFunction(); + } +private: + CompleteTestFunctionRefGuard() { }; + + QMutex mutex; + int m_counter = 0; +}; + +#if QT_CONFIG(thread) + +class TestThread : public QThread +{ +public: + static QThread *create(std::function<void()> started, std::function<void()> finished) + { + TestThread *thread = new TestThread(); + connect(thread, &QThread::started, [started]() { + started(); + }); + connect(thread, &QThread::finished, [thread, finished]() { + finished(); + thread->deleteLater(); + }); + thread->start(); + return thread; + } +}; + +#endif + +// Post event to the main thread and verify that it is processed. +void WasmEventDispatcherTest::postEventMainThread() +{ + QCoreApplication::postEvent(EventTarget::create([](){ + QtWasmTest::completeTestFunction(); + }), EventTarget::createEvent()); +} + +// Create a timer on the main thread and verify that it fires +void WasmEventDispatcherTest::timerMainThread() +{ + QTimer::singleShot(timerTimeout, [](){ + QtWasmTest::completeTestFunction(); + }); +} + +void WasmEventDispatcherTest::timerMainThreadMultiple() +{ + CompleteTestFunctionRefGuard *completeGuard = CompleteTestFunctionRefGuard::create(); + int timers = 10; + for (int i = 0; i < timers; ++i) { + completeGuard->ref(); + QTimer::singleShot(timerTimeout * i, [completeGuard](){ + completeGuard->deref(); + }); + } +} + +#if QT_CONFIG(thread) + +// Post event on a secondary thread and verify that it is processed. +void WasmEventDispatcherTest::postEventSecondaryThread() +{ + auto started = [](){ + QCoreApplication::postEvent(EventTarget::create([](){ + QThread::currentThread()->quit(); + }), EventTarget::createEvent()); + }; + + auto finished = [](){ + QtWasmTest::completeTestFunction(); + }; + + TestThread::create(started, finished); +} + +// Post event _to_ a secondary thread and verify that it is processed. +void WasmEventDispatcherTest::postEventToSecondaryThread() +{ + auto started = [](){}; + auto finished = [](){ + QtWasmTest::completeTestFunction(); + }; + + QThread *t = TestThread::create(started, finished); + EventTarget *target = EventTarget::create([](){ + QThread::currentThread()->quit(); + }); + target->moveToThread(t); + QCoreApplication::postEvent(target, EventTarget::createEvent()); +} + +// Post events to many secondary threads and verify that they are processed. +void WasmEventDispatcherTest::postEventSecondaryThreads() +{ + // This test completes afer all threads has finished + CompleteTestFunctionRefGuard *completeGuard = CompleteTestFunctionRefGuard::create(); + completeGuard->ref(); // including this thread + + auto started = [](){ + QCoreApplication::postEvent(EventTarget::create([](){ + QThread::currentThread()->quit(); + }), EventTarget::createEvent()); + }; + + auto finished = [completeGuard](){ + completeGuard->deref(); + }; + + // Start a nymber of threads in parallel, keeping in mind that the browser + // has some max number of concurrent web workers (maybe 20), and that starting + // a new web worker requires completing a GET network request for the worker's JS. + const int numThreads = 10; + for (int i = 0; i < numThreads; ++i) { + completeGuard->ref(); + TestThread::create(started, finished); + } + + completeGuard->deref(); +} + +// Create a timer a secondary thread and verify that it fires +void WasmEventDispatcherTest::timerSecondaryThread() +{ + auto started = [](){ + QTimer::singleShot(timerTimeout, [](){ + QThread::currentThread()->quit(); + }); + }; + + auto finished = [](){ + QtWasmTest::completeTestFunction(); + }; + + TestThread::create(started, finished); +} + +#endif + +#ifdef QT_HAVE_EMSCRIPTEN_ASYNCIFY + +// Post an event to the main thread and asyncify wait for it +void WasmEventDispatcherTest::postEventAsyncify() +{ + QEventLoop loop; + QCoreApplication::postEvent(EventTarget::create([&loop](){ + loop.quit(); + }), EventTarget::createEvent()); + loop.exec(); + + QtWasmTest::completeTestFunction(); +} + +// Create a timer on the main thread and asyncify wait for it +void WasmEventDispatcherTest::timerAsyncify() +{ + QEventLoop loop; + QTimer::singleShot(timerTimeout, [&loop](){ + loop.quit(); + }); + loop.exec(); + + QtWasmTest::completeTestFunction(); +} + +// Asyncify wait in a loop +void WasmEventDispatcherTest::postEventAsyncifyLoop() +{ + for (int i = 0; i < 10; ++i) { + QEventLoop loop; + QCoreApplication::postEvent(EventTarget::create([&loop]() { + loop.quit(); + }), EventTarget::createEvent()); + loop.exec(); + } + + QtWasmTest::completeTestFunction(); +} + +#if QT_CONFIG(thread) +// Asyncify wait for QThread::wait() / pthread_join() +void WasmEventDispatcherTest::threadAsyncifyWait() +{ + const int threadCount = 15; + + QVector<QThread *> threads; + threads.reserve(threadCount); + + for (int i = 0; i < threadCount; ++i) { + QThread *thread = new QThread(); + threads.push_back(thread); + thread->start(); + } + + for (int i = 0; i < threadCount; ++i) { + QThread *thread = threads[i]; + thread->wait(); + delete thread; + } + + QtWasmTest::completeTestFunction(); +} +#endif + +#endif // QT_HAVE_EMSCRIPTEN_ASYNCIFY + +int main(int argc, char **argv) +{ + auto testObject = std::make_shared<WasmEventDispatcherTest>(); + QtWasmTest::initTestCase<QCoreApplication>(argc, argv, testObject); + return 0; +} + +#include "main.moc" diff --git a/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp b/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp index dd7d11c398..c70c390249 100644 --- a/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp +++ b/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp @@ -45,7 +45,6 @@ void verify(bool condition, std::string_view conditionString, std::string_view f // thread-safe and call be called from any thread. void completeTestFunction(TestResult result, std::string message) { - // Report test result to JavaScript test runner, on the main thread runOnMainThread([resultString = result == TestResult::Pass ? "PASS" : "FAIL", message](){ EM_ASM({ @@ -54,6 +53,12 @@ void completeTestFunction(TestResult result, std::string message) }); } +// Completes the currently running test function with a Pass result. +void completeTestFunction() +{ + completeTestFunction(TestResult::Pass, std::string()); +} + // // Private API for the Javascript test runnner // diff --git a/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.h b/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.h index f4b0f75241..c691f44600 100644 --- a/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.h +++ b/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.h @@ -20,6 +20,7 @@ std::string formatMessage(std::string_view file, std::string_view message); void completeTestFunction(TestResult result, std::string message); +void completeTestFunction(); void initTestCase(QObject *testObject, std::function<void ()> cleanup); template <typename App> void initTestCase(int argc, |