qt 6.5.1 original

This commit is contained in:
kleuter
2023-10-29 23:33:08 +01:00
parent 71d22ab6b0
commit 85d238dfda
21202 changed files with 5499099 additions and 0 deletions

View File

@ -0,0 +1,75 @@
QtWasmTestLib - async auto tests for WebAssembly
================================================
QtWasmTestLib supports auto-test cases in the web browser. Like QTestLib, each
test case is defined by a QObject subclass with one or more test functions. The
test functions may be asynchronous, where they return early and then complete
at some later point.
The test lib is implemented as a C++ and JavaScript library, where the test is written
using C++ and a hosting html page calls JavaScript API to run the test.
Implementing a basic test case
------------------------------
In the test cpp file, define the test functions as private slots. All test
functions must call completeTestFunction() exactly once, or will time out
otherwise. Subsequent calls to completeTestFunction will be disregarded.
It is advised to use QWASMSUCCESS/QWASMFAIL for reporting the test execution
status and QWASMCOMPARE/QWASMVERIFY to assert on test conditions. The call can
be made after the test function itself has returned.
class TestTest: public QObject
{
Q_OBJECT
private slots:
void timerTest() {
QTimer::singleShot(timeout, [](){
completeTestFunction();
});
}
};
Then define a main() function which calls initTestCase(). The main()
function is async too, as per Emscripten default. Build the .cpp file
as a normal Qt for WebAssembly app.
int main(int argc, char **argv)
{
auto testObject = std::make_shared<TestTest>();
initTestCase<QCoreApplication>(argc, argv, testObject);
return 0;
}
Finally provide an html file which hosts the test runner and calls runTestCase()
<!doctype html>
<script type="text/javascript" src="qtwasmtestlib.js"></script>
<script type="text/javascript" src="test_case.js"></script>
<script>
window.onload = async () => {
runTestCase(document.getElementById("log"));
};
</script>
<p>Running Foo auto test.</p>
<div id="log"></div>
Implementing a GUI test case
----------------------------
This is similar to implementing a basic test case, with the difference that the hosting
html file provides container elements which becomes QScreens for the test code.
<!doctype html>
<script type="text/javascript" src="qtwasmtestlib.js"></script>
<script type="text/javascript" src="test_case.js"></script>
<script>
window.onload = async () => {
let log = document.getElementById("log")
let containers = [document.getElementById("container")];
runTestCase(log, containers);
};
</script>
<p>Running Foo auto test.</p>
<div id="container"></div>
<div id="log"></div>

View File

@ -0,0 +1,175 @@
// 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

View File

@ -0,0 +1,73 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#ifndef QT_WASM_TESTRUNNER_H
#define QT_WASM_TESTRUNNER_H
#include <QtCore/qobject.h>
#include <functional>
namespace QtWasmTest {
enum TestResult {
Pass,
Fail,
Skip,
};
std::string formatMessage(std::string_view file,
int line,
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,
char **argv,
std::shared_ptr<QObject> testObject)
{
auto app = std::make_shared<App>(argc, argv);
auto cleanup = [testObject, app]() mutable {
// C++ lambda capture destruction order is unspecified;
// delete test before app by calling reset().
testObject.reset();
app.reset();
};
initTestCase(testObject.get(), cleanup);
}
void verify(bool condition,
std::string_view conditionString,
std::string_view file,
int line);
template<class L, class R>
void compare(const L& lhs,
const R& rhs,
std::string_view lhsString,
std::string_view rhsString,
std::string_view file,
int line) {
if (lhs != rhs) {
completeTestFunction(
TestResult::Fail,
formatMessage(file, line, "Comparison failed: " + std::string(lhsString) + " == " + std::string(rhsString)));
}
}
} // namespace QtWasmTest
#define QWASMVERIFY(condition) \
QtWasmTest::verify((condition), #condition, __FILE__, __LINE__);
#define QWASMCOMPARE(left, right) \
QtWasmTest::compare((left), (right), #left, #right, __FILE__, __LINE__);
#define QWASMSUCCESS() \
QtWasmTest::completeTestFunction(QtWasmTest::Pass, "")
#define QWASMFAIL(message) \
QtWasmTest::completeTestFunction(QtWasmTest::Fail, QtWasmTest::formatMessage(__FILE__, __LINE__, message))
#endif

View File

@ -0,0 +1,135 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
// A minimal async test runner for Qt async auto tests.
//
// Usage: Call runTest(name, testFunctionCompleted), where "name" is the name of the app
// (the .wasm file name), and testFunctionCompleted is a test-function-complete
// callback. The test runner will then instantiate the app and run tests.
//
// The test runner expects that the app instance defines the following
// functions:
//
// void cleanupTestCase()
// string getTestFunctions()
// runTestFunction(string)
//
// Further, the test runner expects that the app instance calls
// completeTestFunction() (below - note that both the instance and this
// file have a function with that name) when a test function finishes. This
// can be done during runTestFunction(), or after it has returned (this
// is the part which enables async testing). Test functions which fail
// to call completeTestFunction() will time out after 2000ms.
//
const g_maxTime = 2000;
class TestFunction {
constructor(instance, name) {
this.instance = instance;
this.name = name;
this.resolve = undefined;
this.reject = undefined;
this.timeoutId = undefined;
}
complete(result, details) {
// Reset timeout
clearTimeout(this.timeoutId);
this.timeoutId = undefined;
const callback = result.startsWith('FAIL') ? this.reject : this.resolve;
callback(`${result}${details ? ': ' + details : ''}`);
}
run() {
// Set timer which will catch test functions
// which fail to call completeTestFunction()
this.timeoutId = setTimeout(() => {
completeTestFunction(this.name, 'FAIL', `Timeout after ${g_maxTime} ms`)
}, g_maxTime);
return new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
this.instance.runTestFunction(this.name);
});
}
};
function completeTestFunction(testFunctionName, result, details) {
if (!window.currentTestFunction || testFunctionName !== window.currentTestFunction.name)
return;
window.currentTestFunction.complete(result, details);
}
async function runTestFunction(instance, name) {
if (window.currentTestFunction) {
throw new Error(`While trying to run ${name}: Last function hasn't yet finished`);
}
window.currentTestFunction = new TestFunction(instance, name);
try {
const result = await window.currentTestFunction.run();
return result;
} finally {
delete window.currentTestFunction;
}
}
async function runTestCaseImpl(testFunctionStarted, testFunctionCompleted, qtContainers) {
// Create test case instance
const config = {
qtContainerElements: qtContainers || []
}
const instance = await createQtAppInstance(config);
// Run all test functions
const functionsString = instance.getTestFunctions();
const functions = functionsString.split(" ").filter(Boolean);
for (const name of functions) {
testFunctionStarted(name);
try {
const result = await runTestFunction(instance, name);
testFunctionCompleted(result);
} catch (err) {
testFunctionCompleted(err.message ?? err);
}
}
// Cleanup
instance.cleanupTestCase();
}
var g_htmlLogElement = undefined;
function testFunctionStarted(name) {
let line = name + ": ";
g_htmlLogElement.innerHTML += line;
}
function testFunctionCompleted(status) {
const color = (status) => {
if (status.startsWith("PASS"))
return "green";
if (status.startsWith("FAIL"))
return "red";
if (status.startsWith("SKIP"))
return "tan";
return "black";
};
const line = `<span style='color: ${color(status)};'>${status}</text><br>`;
g_htmlLogElement.innerHTML += line;
}
async function runTestCase(htmlLogElement, qtContainers) {
g_htmlLogElement = htmlLogElement;
try {
await runTestCaseImpl(testFunctionStarted, testFunctionCompleted, qtContainers);
g_htmlLogElement.innerHTML += "<br> DONE"
} catch (err) {
g_htmlLogElement.innerHTML += err
}
}