qt5base-lts/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp

146 lines
4.1 KiB
C++
Raw Normal View History

// 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 "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)
{
// Report test result to JavaScript test runner, on the main thread
runOnMainThread([resultString = result == TestResult::Pass ? "PASS" : "FAIL", message](){
EM_ASM({
completeTestFunction(UTF8ToString($0), UTF8ToString($1), UTF8ToString($2));
}, g_currentTestName.c_str(), resultString, message.c_str());
});
}
//
// 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, "init");
QMetaObject::invokeMethod(g_testObject, name.c_str());
}
EMSCRIPTEN_BINDINGS(qtwebtestrunner) {
emscripten::function("cleanupTestCase", &cleanupTestCase);
emscripten::function("getTestFunctions", &getTestFunctions);
emscripten::function("runTestFunction", &runTestFunction);
}
//
// 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