1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
import { RunnerStatus, TestStatus } from './batchedtestrunner.js';
// Sends messages to the running emrun instance via POST requests.
export class EmrunCommunication {
#indexOfMessage = 0;
#postOutputPromises = [];
#post(body) {
return fetch('stdio.html', {
method: 'POST',
body
});
}
// Returns a promise whose resolution signals that all outstanding traffic to the emrun instance
// has been completed.
waitUntilAllSent() {
return Promise.all(this.#postOutputPromises);
}
// 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) {
const newPromise = this.#post(`^out^${this.#indexOfMessage++}^${output}`);
this.#postOutputPromises.push(newPromise);
newPromise.finally(() => {
this.#postOutputPromises.splice(this.#postOutputPromises.indexOf(newPromise), 1);
});
return newPromise;
}
}
// 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;
}
}
|