summaryrefslogtreecommitdiffstats
path: root/util/wasm/batchedtestrunner/emrunadapter.js
diff options
context:
space:
mode:
Diffstat (limited to 'util/wasm/batchedtestrunner/emrunadapter.js')
-rw-r--r--util/wasm/batchedtestrunner/emrunadapter.js137
1 files changed, 137 insertions, 0 deletions
diff --git a/util/wasm/batchedtestrunner/emrunadapter.js b/util/wasm/batchedtestrunner/emrunadapter.js
new file mode 100644
index 0000000000..5b4284e18f
--- /dev/null
+++ b/util/wasm/batchedtestrunner/emrunadapter.js
@@ -0,0 +1,137 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import { RunnerStatus, TestStatus } from './batchedtestrunner.js';
+
+// Sends messages to the running emrun instance via POST requests.
+export class EmrunCommunication {
+ static #BATCHING_DELAY = 300;
+
+ #indexOfMessage = 0;
+ #postOutputPromise;
+ // Accumulate output in a batch that gets sent with a delay so that the emrun http server
+ // does not get pounded with requests.
+ #nextOutputBatch = null;
+
+ #post(body) {
+ return fetch('stdio.html', {
+ method: 'POST',
+ body
+ });
+ }
+
+ // Waits for the output sending to finish, if any output transfer is still in progress.
+ async waitUntilAllSent()
+ {
+ if (this.#postOutputPromise)
+ await this.#postOutputPromise;
+ }
+
+ // Posts the exit status to the running emrun instance. Emrun will drop connection unless it is
+ // run with --serve_after_exit, therefore this method will throw most of the times.
+ postExit(status) {
+ return this.#post(`^exit^${status}`);
+ }
+
+ // Posts an indexed output chunk to the running emrun instance. Each consecutive call to this
+ // method increments the output index by 1.
+ postOutput(output)
+ {
+ if (this.#nextOutputBatch) {
+ this.#nextOutputBatch += output;
+ } else {
+ this.#nextOutputBatch = output;
+ this.#postOutputPromise = new Promise(resolve =>
+ {
+ window.setTimeout(() =>
+ {
+ const toSend = this.#nextOutputBatch;
+ this.#nextOutputBatch = null;
+ this.#post(`^out^${this.#indexOfMessage++}^${toSend}$`)
+ .finally(resolve);
+ }, EmrunCommunication.#BATCHING_DELAY);
+ });
+ }
+
+ return this.#postOutputPromise;
+ }
+}
+
+// Wraps a test module runner; forwards its output and resolution state to the running emrun
+// instance.
+export class EmrunAdapter {
+ #communication;
+ #batchedTestRunner;
+ #sentLines = 0;
+ #onExitSent;
+
+ constructor(communication, batchedTestRunner, onExitSent) {
+ this.#communication = communication;
+ this.#batchedTestRunner = batchedTestRunner;
+ this.#onExitSent = onExitSent;
+ }
+
+ // Starts listening to test module runner's state changes. When the test module runner finishes
+ // or reports output, sends suitable messages to the emrun instance.
+ run() {
+ this.#batchedTestRunner.onStatusChanged.addEventListener(
+ status => this.#onRunnerStatusChanged(status));
+ this.#batchedTestRunner.onTestStatusChanged.addEventListener(
+ (test, status) => this.#onTestStatusChanged(test, status));
+ this.#batchedTestRunner.onTestOutputChanged.addEventListener(
+ (test, output) => this.#onTestOutputChanged(test, output));
+
+ const currentTest = [...this.#batchedTestRunner.results.entries()].find(
+ entry => entry[1].status === TestStatus.Running)?.[0];
+
+ const output = this.#batchedTestRunner.results.get(currentTest)?.output;
+ if (output)
+ this.#onTestOutputChanged(testName, output);
+ this.#onRunnerStatusChanged(this.#batchedTestRunner.status);
+ }
+
+ #toExitCode(status) {
+ switch (status) {
+ case RunnerStatus.Error:
+ return -1;
+ case RunnerStatus.Passed:
+ return 0;
+ case RunnerStatus.Running:
+ throw new Error('No exit code when still running');
+ case RunnerStatus.TestCrashed:
+ return -2;
+ case RunnerStatus.TestsFailed:
+ return this.#batchedTestRunner.numberOfFailed;
+ }
+ }
+
+ async #onRunnerStatusChanged(status) {
+ if (RunnerStatus.Running === status)
+ return;
+
+ const exit = this.#toExitCode(status);
+ if (RunnerStatus.Error === status)
+ this.#communication.postOutput(this.#batchedTestRunner.errorDetails);
+
+ await this.#communication.waitUntilAllSent();
+ try {
+ await this.#communication.postExit(exit);
+ } catch {
+ // no-op: The remote end will drop connection on exit.
+ } finally {
+ this.#onExitSent?.();
+ }
+ }
+
+ async #onTestOutputChanged(_, output) {
+ const notSent = output.slice(this.#sentLines);
+ for (const out of notSent)
+ this.#communication.postOutput(out);
+ this.#sentLines = output.length;
+ }
+
+ async #onTestStatusChanged(_, status) {
+ if (status === TestStatus.Running)
+ this.#sentLines = 0;
+ }
+}