summaryrefslogtreecommitdiffstats
path: root/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp')
-rw-r--r--tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp175
1 files changed, 175 insertions, 0 deletions
diff --git a/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp b/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp
new file mode 100644
index 0000000000..ec03c7209a
--- /dev/null
+++ b/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp
@@ -0,0 +1,175 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qtwasmtestlib.h"
+
+#include <QtCore/qmetaobject.h>
+
+#include <emscripten/bind.h>
+#include <emscripten.h>
+#include <emscripten/threading.h>
+
+namespace QtWasmTest {
+namespace {
+QObject *g_testObject = nullptr;
+std::string g_currentTestName;
+std::function<void ()> g_cleanup;
+}
+
+void runOnMainThread(std::function<void(void)> fn);
+static bool isValidSlot(const QMetaMethod &sl);
+
+
+//
+// Public API
+//
+
+// Initializes the test case with a test object and cleanup function. The
+// cleanup function is called when all test functions have completed.
+void initTestCase(QObject *testObject, std::function<void ()> cleanup)
+{
+ g_testObject = testObject;
+ g_cleanup = cleanup;
+}
+
+void verify(bool condition, std::string_view conditionString, std::string_view file, int line)
+{
+ if (!condition) {
+ completeTestFunction(
+ TestResult::Fail,
+ formatMessage(file, line, "Condition failed: " + std::string(conditionString)));
+ }
+}
+
+// Completes the currently running test function with a result. This function is
+// thread-safe and call be called from any thread.
+void completeTestFunction(TestResult result, std::string message)
+{
+ auto resultString = [](TestResult result) {
+ switch (result) {
+ case TestResult::Pass:
+ return "PASS";
+ break;
+ case TestResult::Fail:
+ return "FAIL";
+ break;
+ case TestResult::Skip:
+ return "SKIP";
+ break;
+ }
+ };
+
+ // Report test result to JavaScript test runner, on the main thread
+ runOnMainThread([resultString = resultString(result), message](){
+ EM_ASM({
+ completeTestFunction(UTF8ToString($0), UTF8ToString($1), UTF8ToString($2));
+ }, g_currentTestName.c_str(), resultString, message.c_str());
+ });
+}
+
+// Completes the currently running test function with a Pass result.
+void completeTestFunction()
+{
+ completeTestFunction(TestResult::Pass, std::string());
+}
+
+//
+// Private API for the Javascript test runnner
+//
+
+std::string formatMessage(std::string_view file, int line, std::string_view message)
+{
+ return "[" + std::string(file) + ":" + QString::number(line).toStdString() + "] " + std::string(message);
+}
+
+void cleanupTestCase()
+{
+ g_testObject = nullptr;
+ g_cleanup();
+}
+
+std::string getTestFunctions()
+{
+ std::string testFunctions;
+
+ // Duplicate qPrintTestSlots (private QTestLib function) logic.
+ for (int i = 0; i < g_testObject->metaObject()->methodCount(); ++i) {
+ QMetaMethod sl = g_testObject->metaObject()->method(i);
+ if (!isValidSlot(sl))
+ continue;
+ QByteArray signature = sl.methodSignature();
+ Q_ASSERT(signature.endsWith("()"));
+ signature.chop(2);
+ if (!testFunctions.empty())
+ testFunctions += " ";
+ testFunctions += std::string(signature.constData());
+ }
+
+ return testFunctions;
+}
+
+void runTestFunction(std::string name)
+{
+ g_currentTestName = name;
+ QMetaObject::invokeMethod(g_testObject, name.c_str());
+}
+
+void failTest(std::string message)
+{
+ completeTestFunction(QtWasmTest::Fail, std::move(message));
+}
+
+void passTest()
+{
+ completeTestFunction(QtWasmTest::Pass, "");
+}
+
+EMSCRIPTEN_BINDINGS(qtwebtestrunner) {
+ emscripten::function("cleanupTestCase", &cleanupTestCase);
+ emscripten::function("getTestFunctions", &getTestFunctions);
+ emscripten::function("runTestFunction", &runTestFunction);
+ emscripten::function("qtWasmFail", &failTest);
+ emscripten::function("qtWasmPass", &passTest);
+}
+
+//
+// Test lib implementation
+//
+
+static bool isValidSlot(const QMetaMethod &sl)
+{
+ if (sl.access() != QMetaMethod::Private || sl.parameterCount() != 0
+ || sl.returnType() != QMetaType::Void || sl.methodType() != QMetaMethod::Slot)
+ return false;
+ const QByteArray name = sl.name();
+ return !(name.isEmpty() || name.endsWith("_data")
+ || name == "initTestCase" || name == "cleanupTestCase"
+ || name == "init" || name == "cleanup");
+}
+
+void trampoline(void *context)
+{
+ Q_ASSERT(emscripten_is_main_runtime_thread());
+
+ emscripten_async_call([](void *context) {
+ std::function<void(void)> *fn = reinterpret_cast<std::function<void(void)> *>(context);
+ (*fn)();
+ delete fn;
+ }, context, 0);
+}
+
+// Runs the given function on the main thread, asynchronously
+void runOnMainThread(std::function<void(void)> fn)
+{
+ void *context = new std::function<void(void)>(fn);
+ if (emscripten_is_main_runtime_thread()) {
+ trampoline(context);
+ } else {
+#if QT_CONFIG(thread)
+ emscripten_async_run_in_main_runtime_thread_(EM_FUNC_SIG_VI, reinterpret_cast<void *>(trampoline), context);
+#endif
+ }
+}
+
+} // namespace QtWasmTest
+