mirror of
https://github.com/crystalidea/qt6windows7.git
synced 2024-11-26 22:27:06 +08:00
176 lines
4.8 KiB
C++
176 lines
4.8 KiB
C++
|
// 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)
|
||
|
{
|
||
|
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
|
||
|
|