summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorMikolaj Boc <mikolaj.boc@qt.io>2022-06-21 17:50:04 +0200
committerMikolaj Boc <mikolaj.boc@qt.io>2022-06-30 01:20:28 +0200
commitd0eba2449adcee6f1daa33426f9c9b8ebf7c28f5 (patch)
treec651a3f7759a0a46f3f792a79697d91e374994f7 /tests
parent97665c9615ff399e9a074b94926ab06e0c9619e5 (diff)
Create a promise wrapper for C++ and port existing uses
Currently, to use a promise from C++ we either have to use an ASM block (which does not work well with dynamic linking) or declare exports in the EMSCRIPTEN_BINDINGS block, which is cumbersome and cannot be chained. This solution makes it easy to use js promises by introducing the WebPromiseManager which dispatches callbacks to appropriate callers when available. This is a preliminary patch for FileSystem support, which will heavily use async APIs. Task-number: QTBUG-99611 Change-Id: I368a8f173027eaa883a9ca18d0ea6a3e99b86071 Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
Diffstat (limited to 'tests')
-rw-r--r--tests/auto/wasm/CMakeLists.txt17
-rw-r--r--tests/auto/wasm/tst_qstdweb.cpp629
2 files changed, 646 insertions, 0 deletions
diff --git a/tests/auto/wasm/CMakeLists.txt b/tests/auto/wasm/CMakeLists.txt
new file mode 100644
index 0000000000..2e6c6976fb
--- /dev/null
+++ b/tests/auto/wasm/CMakeLists.txt
@@ -0,0 +1,17 @@
+#####################################################################
+## tst_wasm Test:
+#####################################################################
+
+qt_internal_add_test(tst_qstdweb
+ SOURCES
+ tst_qstdweb.cpp
+ DEFINES
+ QT_NO_FOREACH
+ QT_NO_KEYWORDS
+ LIBRARIES
+ Qt::GuiPrivate
+ PUBLIC_LIBRARIES
+ Qt::Core
+ Qt::Gui
+ Qt::Widgets
+)
diff --git a/tests/auto/wasm/tst_qstdweb.cpp b/tests/auto/wasm/tst_qstdweb.cpp
new file mode 100644
index 0000000000..f074866a3d
--- /dev/null
+++ b/tests/auto/wasm/tst_qstdweb.cpp
@@ -0,0 +1,629 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// Copyright (C) 2016 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QtCore/qtimer.h>
+#include <QtCore/private/qstdweb_p.h>
+#include <QTest>
+#include <emscripten.h>
+#include <emscripten/bind.h>
+#include <emscripten/val.h>
+
+#if defined(QT_HAVE_EMSCRIPTEN_ASYNCIFY)
+#define SKIP_IF_NO_ASYNCIFY()
+#else
+#define SKIP_IF_NO_ASYNCIFY() QSKIP("Needs QT_HAVE_EMSCRIPTEN_ASYNCIFY")
+#endif
+
+using namespace emscripten;
+
+class tst_QStdWeb : public QObject
+{
+ Q_OBJECT
+public:
+ tst_QStdWeb() : m_window(val::global("window")), m_testSupport(val::object()) {
+ instance = this;
+
+ m_window.set("testSupport", m_testSupport);
+ }
+ ~tst_QStdWeb() noexcept {}
+
+private:
+ static tst_QStdWeb* instance;
+
+ void init() {
+ EM_ASM({
+ testSupport.resolve = {};
+ testSupport.reject = {};
+ testSupport.promises = {};
+ testSupport.waitConditionPromise = new Promise((resolve, reject) => {
+ testSupport.finishWaiting = resolve;
+ });
+
+ testSupport.makeTestPromise = (param) => {
+ testSupport.promises[param] = new Promise((resolve, reject) => {
+ testSupport.resolve[param] = resolve;
+ testSupport.reject[param] = reject;
+ });
+
+ return testSupport.promises[param];
+ };
+ });
+ }
+
+ val m_window;
+ val m_testSupport;
+
+private Q_SLOTS:
+ void simpleResolve();
+ void multipleResolve();
+ void simpleReject();
+ void multipleReject();
+ void throwInThen();
+ void bareFinally();
+ void finallyWithThen();
+ void finallyWithThrow();
+ void finallyWithThrowInThen();
+ void nested();
+ void all();
+ void allWithThrow();
+ void allWithFinally();
+ void allWithFinallyAndThrow();
+};
+
+tst_QStdWeb* tst_QStdWeb::instance = nullptr;
+
+EM_ASYNC_JS(void, awaitCondition, (), {
+ await testSupport.waitConditionPromise;
+});
+
+void tst_QStdWeb::simpleResolve()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ QVERIFY(result.isString());
+ QCOMPARE("Some lovely data", result.as<std::string>());
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ },
+ .catchFunc = [](val error) {
+ Q_UNUSED(error);
+ QFAIL("Unexpected catch");
+ }
+ }, std::string("simpleResolve"));
+
+ EM_ASM({
+ testSupport.resolve["simpleResolve"]("Some lovely data");
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::multipleResolve()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ QVERIFY(result.isString());
+ QCOMPARE("Data 1", result.as<std::string>());
+
+ EM_ASM({
+ if (!--testSupport.promisesLeft) {
+ testSupport.finishWaiting();
+ }
+ });
+ },
+ .catchFunc = [](val error) {
+ Q_UNUSED(error);
+ QFAIL("Unexpected catch");
+ }
+ }, std::string("1"));
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ QVERIFY(result.isString());
+ QCOMPARE("Data 2", result.as<std::string>());
+
+ EM_ASM({
+ if (!--testSupport.promisesLeft) {
+ testSupport.finishWaiting();
+ }
+ });
+ },
+ .catchFunc = [](val error) {
+ Q_UNUSED(error);
+ QFAIL("Unexpected catch");
+ }
+ }, std::string("2"));
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ QVERIFY(result.isString());
+ QCOMPARE("Data 3", result.as<std::string>());
+
+ EM_ASM({
+ if (!--testSupport.promisesLeft) {
+ testSupport.finishWaiting();
+ }
+ });
+ },
+ .catchFunc = [](val error) {
+ Q_UNUSED(error);
+ QFAIL("Unexpected catch");
+ }
+ }, std::string("3"));
+
+ EM_ASM({
+ testSupport.resolve["3"]("Data 3");
+ testSupport.resolve["1"]("Data 1");
+ testSupport.resolve["2"]("Data 2");
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::simpleReject()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ Q_UNUSED(result);
+ QFAIL("Unexpected then");
+ },
+ .catchFunc = [](val result) {
+ QVERIFY(result.isString());
+ QCOMPARE("Evil error", result.as<std::string>());
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ }
+ }, std::string("simpleReject"));
+
+ EM_ASM({
+ testSupport.reject["simpleReject"]("Evil error");
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::multipleReject()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ Q_UNUSED(result);
+ QFAIL("Unexpected then");
+ },
+ .catchFunc = [](val error) {
+ QVERIFY(error.isString());
+ QCOMPARE("Error 1", error.as<std::string>());
+
+ EM_ASM({
+ if (!--testSupport.promisesLeft) {
+ testSupport.finishWaiting();
+ }
+ });
+ }
+ }, std::string("1"));
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ Q_UNUSED(result);
+ QFAIL("Unexpected then");
+ },
+ .catchFunc = [](val error) {
+ QVERIFY(error.isString());
+ QCOMPARE("Error 2", error.as<std::string>());
+
+ EM_ASM({
+ if (!--testSupport.promisesLeft) {
+ testSupport.finishWaiting();
+ }
+ });
+ }
+ }, std::string("2"));
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ Q_UNUSED(result);
+ QFAIL("Unexpected then");
+ },
+ .catchFunc = [](val error) {
+ QVERIFY(error.isString());
+ QCOMPARE("Error 3", error.as<std::string>());
+
+ EM_ASM({
+ if (!--testSupport.promisesLeft) {
+ testSupport.finishWaiting();
+ }
+ });
+ }
+ }, std::string("3"));
+
+ EM_ASM({
+ testSupport.reject["3"]("Error 3");
+ testSupport.reject["1"]("Error 1");
+ testSupport.reject["2"]("Error 2");
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::throwInThen()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ Q_UNUSED(result);
+ EM_ASM({
+ throw "Expected error";
+ });
+ },
+ .catchFunc = [](val error) {
+ QCOMPARE("Expected error", error.as<std::string>());
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ }
+ }, std::string("throwInThen"));
+
+ EM_ASM({
+ testSupport.resolve["throwInThen"]();
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::bareFinally()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .finallyFunc = []() {
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ }
+ }, std::string("bareFinally"));
+
+ EM_ASM({
+ testSupport.resolve["bareFinally"]();
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::finallyWithThen()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [] (val result) {
+ Q_UNUSED(result);
+ },
+ .finallyFunc = []() {
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ }
+ }, std::string("finallyWithThen"));
+
+ EM_ASM({
+ testSupport.resolve["finallyWithThen"]();
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::finallyWithThrow()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .catchFunc = [](val error) {
+ Q_UNUSED(error);
+ },
+ .finallyFunc = []() {
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ }
+ }, std::string("finallyWithThrow"));
+
+ EM_ASM({
+ testSupport.reject["finallyWithThrow"]();
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::finallyWithThrowInThen()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val result) {
+ Q_UNUSED(result);
+ EM_ASM({
+ throw "Expected error";
+ });
+ },
+ .catchFunc = [](val result) {
+ QVERIFY(result.isString());
+ QCOMPARE("Expected error", result.as<std::string>());
+ },
+ .finallyFunc = []() {
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ }
+ }, std::string("bareFinallyWithThen"));
+
+ EM_ASM({
+ testSupport.resolve["bareFinallyWithThen"]();
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::nested()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [this](val result) {
+ QVERIFY(result.isString());
+ QCOMPARE("Outer data", result.as<std::string>());
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [this](val innerResult) {
+ QVERIFY(innerResult.isString());
+ QCOMPARE("Inner data", innerResult.as<std::string>());
+
+ qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
+ .thenFunc = [](val innerResult) {
+ QVERIFY(innerResult.isString());
+ QCOMPARE("Innermost data", innerResult.as<std::string>());
+
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ },
+ .catchFunc = [](val error) {
+ Q_UNUSED(error);
+ QFAIL("Unexpected catch");
+ }
+ }, std::string("innermost"));
+
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ },
+ .catchFunc = [](val error) {
+ Q_UNUSED(error);
+ QFAIL("Unexpected catch");
+ }
+ }, std::string("inner"));
+
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ },
+ .catchFunc = [](val error) {
+ Q_UNUSED(error);
+ QFAIL("Unexpected catch");
+ }
+ }, std::string("outer"));
+
+ EM_ASM({
+ testSupport.resolve["outer"]("Outer data");
+ });
+
+ awaitCondition();
+
+ EM_ASM({
+ testSupport.waitConditionPromise = new Promise((resolve, reject) => {
+ testSupport.finishWaiting = resolve;
+ });
+ });
+
+ EM_ASM({
+ testSupport.resolve["inner"]("Inner data");
+ });
+
+ awaitCondition();
+
+ EM_ASM({
+ testSupport.waitConditionPromise = new Promise((resolve, reject) => {
+ testSupport.finishWaiting = resolve;
+ });
+ });
+
+ EM_ASM({
+ testSupport.resolve["innermost"]("Innermost data");
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::all()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ val promise1 = m_testSupport.call<val>("makeTestPromise", val("promise1"));
+ val promise2 = m_testSupport.call<val>("makeTestPromise", val("promise2"));
+ val promise3 = m_testSupport.call<val>("makeTestPromise", val("promise3"));
+
+ auto thenCalledOnce = std::shared_ptr<bool>();
+ *thenCalledOnce = true;
+
+ qstdweb::Promise::all({promise1, promise2, promise3}, {
+ .thenFunc = [thenCalledOnce](val result) {
+ QVERIFY(*thenCalledOnce);
+ *thenCalledOnce = false;
+
+ QVERIFY(result.isArray());
+ QCOMPARE(3, result["length"].as<int>());
+ QCOMPARE("Data 1", result[0].as<std::string>());
+ QCOMPARE("Data 2", result[1].as<std::string>());
+ QCOMPARE("Data 3", result[2].as<std::string>());
+
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ },
+ .catchFunc = [](val result) {
+ Q_UNUSED(result);
+ EM_ASM({
+ throw new Error("Unexpected error");
+ });
+ }
+ });
+
+ EM_ASM({
+ testSupport.resolve["promise3"]("Data 3");
+ testSupport.resolve["promise1"]("Data 1");
+ testSupport.resolve["promise2"]("Data 2");
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::allWithThrow()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ val promise1 = m_testSupport.call<val>("makeTestPromise", val("promise1"));
+ val promise2 = m_testSupport.call<val>("makeTestPromise", val("promise2"));
+ val promise3 = m_testSupport.call<val>("makeTestPromise", val("promise3"));
+
+ auto catchCalledOnce = std::shared_ptr<bool>();
+ *catchCalledOnce = true;
+
+ qstdweb::Promise::all({promise1, promise2, promise3}, {
+ .thenFunc = [](val result) {
+ Q_UNUSED(result);
+ QFAIL("Unexpected then");
+ },
+ .catchFunc = [catchCalledOnce](val result) {
+ QVERIFY(*catchCalledOnce);
+ *catchCalledOnce = false;
+ QVERIFY(result.isString());
+ QCOMPARE("Error 2", result.as<std::string>());
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ }
+ });
+
+ EM_ASM({
+ testSupport.resolve["promise3"]("Data 3");
+ testSupport.resolve["promise1"]("Data 1");
+ testSupport.reject["promise2"]("Error 2");
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::allWithFinally()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ val promise1 = m_testSupport.call<val>("makeTestPromise", val("promise1"));
+ val promise2 = m_testSupport.call<val>("makeTestPromise", val("promise2"));
+ val promise3 = m_testSupport.call<val>("makeTestPromise", val("promise3"));
+
+ auto finallyCalledOnce = std::shared_ptr<bool>();
+ *finallyCalledOnce = true;
+
+ qstdweb::Promise::all({promise1, promise2, promise3}, {
+ .thenFunc = [](val result) {
+ Q_UNUSED(result);
+ },
+ .finallyFunc = [finallyCalledOnce]() {
+ QVERIFY(*finallyCalledOnce);
+ *finallyCalledOnce = false;
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ }
+ });
+
+ EM_ASM({
+ testSupport.resolve["promise3"]("Data 3");
+ testSupport.resolve["promise1"]("Data 1");
+ testSupport.resolve["promise2"]("Data 2");
+ });
+
+ awaitCondition();
+}
+
+void tst_QStdWeb::allWithFinallyAndThrow()
+{
+ SKIP_IF_NO_ASYNCIFY();
+
+ init();
+
+ val promise1 = m_testSupport.call<val>("makeTestPromise", val("promise1"));
+ val promise2 = m_testSupport.call<val>("makeTestPromise", val("promise2"));
+ val promise3 = m_testSupport.call<val>("makeTestPromise", val("promise3"));
+
+ auto finallyCalledOnce = std::shared_ptr<bool>();
+ *finallyCalledOnce = true;
+
+ qstdweb::Promise::all({promise1, promise2, promise3}, {
+ .thenFunc = [](val result) {
+ Q_UNUSED(result);
+ EM_ASM({
+ throw "This breaks it all!!!";
+ });
+ },
+ .finallyFunc = [finallyCalledOnce]() {
+ QVERIFY(*finallyCalledOnce);
+ *finallyCalledOnce = false;
+ EM_ASM({
+ testSupport.finishWaiting();
+ });
+ }
+ });
+
+ EM_ASM({
+ testSupport.resolve["promise3"]("Data 3");
+ testSupport.resolve["promise1"]("Data 1");
+ testSupport.resolve["promise2"]("Data 2");
+ });
+
+ awaitCondition();
+}
+
+QTEST_APPLESS_MAIN(tst_QStdWeb)
+#include "tst_qstdweb.moc"