@jsenv/test 2.1.3 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/js/exception.js +908 -0
- package/dist/js/execute_using_dynamic_import.js +76 -167
- package/dist/js/v8_coverage.js +4 -482
- package/dist/jsenv_test.js +4608 -2917
- package/dist/node_child_process_controlled.mjs +95 -76
- package/dist/node_worker_thread_controlled.mjs +72 -54
- package/package.json +23 -14
- package/src/coverage/generate_coverage.js +186 -0
- package/src/coverage/list_files_not_covered.js +2 -2
- package/src/coverage/missing_coverage.js +2 -2
- package/src/coverage/report_coverage_as_html.js +45 -0
- package/src/coverage/report_coverage_as_json.js +20 -0
- package/src/coverage/{coverage_reporter_text_log.js → report_coverage_in_console.js} +13 -7
- package/src/coverage/v8_and_istanbul.js +4 -4
- package/src/coverage/v8_coverage.js +2 -2
- package/src/execution/exception.js +151 -68
- package/src/execution/execute.js +24 -11
- package/src/execution/execute_test_plan.js +834 -750
- package/src/execution/format_error_for_terminal.js +187 -0
- package/src/execution/github_annotation_from_error.js +3 -3
- package/src/execution/junit_xml_file/report_as_junit_xml.js +149 -0
- package/src/execution/junit_xml_file/xml_generator.js +141 -0
- package/src/execution/reporters/reporter_list.js +687 -0
- package/src/execution/run.js +92 -30
- package/src/execution/web_server/start_using_command.js +2 -2
- package/src/execution/web_server_param.js +5 -5
- package/src/helpers/cpu_usage.js +142 -0
- package/src/helpers/cpu_usage.test_manual.js +7 -0
- package/src/main.js +8 -0
- package/src/runtime_browsers/chromium.js +1 -3
- package/src/runtime_browsers/firefox.js +0 -3
- package/src/runtime_browsers/middleware_istanbul.js +2 -2
- package/src/runtime_browsers/using_playwright.js +296 -220
- package/src/runtime_browsers/webkit.js +0 -3
- package/src/runtime_node/execute_using_dynamic_import.js +75 -28
- package/src/runtime_node/node_child_process.js +250 -139
- package/src/runtime_node/node_child_process_controlled.mjs +94 -75
- package/src/runtime_node/node_worker_thread.js +206 -120
- package/src/runtime_node/node_worker_thread_controlled.mjs +70 -53
- package/src/coverage/coverage_reporter_html_directory.js +0 -32
- package/src/coverage/coverage_reporter_json_file.js +0 -17
- package/src/coverage/report_to_coverage.js +0 -198
- package/src/execution/execution_colors.js +0 -10
- package/src/execution/execution_steps.js +0 -65
- package/src/execution/logs_file_execution.js +0 -495
|
@@ -1,53 +1,52 @@
|
|
|
1
|
+
import { memoryUsage } from "node:process";
|
|
2
|
+
|
|
1
3
|
import { createException } from "../execution/exception.js";
|
|
2
4
|
import { executeUsingDynamicImport } from "./execute_using_dynamic_import.js";
|
|
3
5
|
|
|
6
|
+
let memoryHeapUsedAtStart;
|
|
7
|
+
if (process.env.MEASURE_MEMORY_AT_START) {
|
|
8
|
+
global.gc();
|
|
9
|
+
memoryHeapUsedAtStart = memoryUsage().heapUsed;
|
|
10
|
+
}
|
|
11
|
+
|
|
4
12
|
const ACTIONS_AVAILABLE = {
|
|
5
|
-
"execute-using-dynamic-import":
|
|
13
|
+
"execute-using-dynamic-import": (params) => {
|
|
14
|
+
return executeUsingDynamicImport(params);
|
|
15
|
+
},
|
|
6
16
|
"execute-using-require": async ({ fileUrl }) => {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const result = {
|
|
18
|
+
timings: {
|
|
19
|
+
start: null,
|
|
20
|
+
end: null,
|
|
21
|
+
},
|
|
22
|
+
namespace: null,
|
|
23
|
+
};
|
|
24
|
+
try {
|
|
25
|
+
const { createRequire } = await import("node:module");
|
|
26
|
+
const { fileURLToPath } = await import("node:url");
|
|
27
|
+
const filePath = fileURLToPath(fileUrl);
|
|
28
|
+
const require = createRequire(fileUrl);
|
|
29
|
+
result.timings.start = Date.now();
|
|
30
|
+
// eslint-disable-next-line import/no-dynamic-require
|
|
31
|
+
const namespace = require(filePath);
|
|
32
|
+
const namespaceResolved = {};
|
|
33
|
+
await Promise.all(
|
|
34
|
+
Object.keys(namespace).map(async (key) => {
|
|
35
|
+
const value = await namespace[key];
|
|
36
|
+
namespaceResolved[key] = value;
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
result.namespace = namespaceResolved;
|
|
40
|
+
} finally {
|
|
41
|
+
result.timings.end = Date.now();
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"measure-memory-usage": () => {
|
|
46
|
+
// we compare usage - usageAtstart to prevent
|
|
47
|
+
// node or os specificities to have too much influences on the measures
|
|
48
|
+
return memoryUsage().heapUsed - memoryHeapUsedAtStart;
|
|
21
49
|
},
|
|
22
|
-
};
|
|
23
|
-
const ACTION_REQUEST_EVENT_NAME = "action";
|
|
24
|
-
const ACTION_RESPONSE_EVENT_NAME = "action-result";
|
|
25
|
-
const ACTION_RESPONSE_STATUS_FAILED = "action-failed";
|
|
26
|
-
const ACTION_RESPONSE_STATUS_COMPLETED = "action-completed";
|
|
27
|
-
|
|
28
|
-
const sendActionFailed = (error) => {
|
|
29
|
-
const exception = createException(error);
|
|
30
|
-
sendToParent(
|
|
31
|
-
ACTION_RESPONSE_EVENT_NAME,
|
|
32
|
-
JSON.stringify({
|
|
33
|
-
status: ACTION_RESPONSE_STATUS_FAILED,
|
|
34
|
-
value: exception,
|
|
35
|
-
}),
|
|
36
|
-
);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const sendActionCompleted = (value) => {
|
|
40
|
-
sendToParent(
|
|
41
|
-
ACTION_RESPONSE_EVENT_NAME,
|
|
42
|
-
// here we use JSON.stringify because we should not
|
|
43
|
-
// have non enumerable value (unlike there is on Error objects)
|
|
44
|
-
// otherwise uneval is quite slow to turn a giant object
|
|
45
|
-
// into a string (and value can be giant when using coverage)
|
|
46
|
-
JSON.stringify({
|
|
47
|
-
status: ACTION_RESPONSE_STATUS_COMPLETED,
|
|
48
|
-
value,
|
|
49
|
-
}),
|
|
50
|
-
);
|
|
51
50
|
};
|
|
52
51
|
|
|
53
52
|
const sendToParent = (type, data) => {
|
|
@@ -61,16 +60,13 @@ const sendToParent = (type, data) => {
|
|
|
61
60
|
// It means node process may stay alive longer than expected
|
|
62
61
|
// the time to send the data to the parent.
|
|
63
62
|
process.send({
|
|
64
|
-
|
|
65
|
-
type,
|
|
63
|
+
__jsenv__: type,
|
|
66
64
|
data,
|
|
67
65
|
});
|
|
68
66
|
};
|
|
69
|
-
|
|
70
|
-
const onceParentMessage = (type, callback) => {
|
|
67
|
+
const onActionRequestedByParent = (callback) => {
|
|
71
68
|
const listener = (message) => {
|
|
72
|
-
if (message && message.
|
|
73
|
-
removeListener(); // commenting this line keep this process alive
|
|
69
|
+
if (message && message.__jsenv__ === "action") {
|
|
74
70
|
callback(message.data);
|
|
75
71
|
}
|
|
76
72
|
};
|
|
@@ -81,37 +77,60 @@ const onceParentMessage = (type, callback) => {
|
|
|
81
77
|
return removeListener;
|
|
82
78
|
};
|
|
83
79
|
|
|
84
|
-
const removeActionRequestListener =
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
80
|
+
const removeActionRequestListener = onActionRequestedByParent(
|
|
81
|
+
async ({ id, type, params = {} }) => {
|
|
82
|
+
const sendActionInternalError = (id, error) => {
|
|
83
|
+
const exception = createException(error);
|
|
84
|
+
sendToParent(
|
|
85
|
+
"action-result",
|
|
86
|
+
JSON.stringify({
|
|
87
|
+
id,
|
|
88
|
+
status: "error",
|
|
89
|
+
value: exception,
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
const sendActionCompleted = (id, value) => {
|
|
94
|
+
sendToParent(
|
|
95
|
+
"action-result",
|
|
96
|
+
// here we use JSON.stringify because we should not
|
|
97
|
+
// have non enumerable value (unlike there is on Error objects)
|
|
98
|
+
// otherwise uneval is quite slow to turn a giant object
|
|
99
|
+
// into a string (and value can be giant when using coverage)
|
|
100
|
+
JSON.stringify({
|
|
101
|
+
id,
|
|
102
|
+
status: "completed",
|
|
103
|
+
value,
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const action = ACTIONS_AVAILABLE[type];
|
|
88
109
|
if (!action) {
|
|
89
|
-
|
|
110
|
+
sendActionInternalError(id, new Error(`unknown action ${type}`));
|
|
90
111
|
return;
|
|
91
112
|
}
|
|
92
113
|
|
|
93
|
-
let
|
|
94
|
-
|
|
114
|
+
let gotInternalError = false;
|
|
115
|
+
const onUncaughtException = (err) => {
|
|
116
|
+
gotInternalError = true;
|
|
117
|
+
sendActionInternalError(id, err);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
};
|
|
120
|
+
process.on("uncaughtException", onUncaughtException);
|
|
121
|
+
|
|
95
122
|
try {
|
|
96
|
-
value = await action(
|
|
123
|
+
const value = await action(params);
|
|
124
|
+
if (!gotInternalError) {
|
|
125
|
+
sendActionCompleted(id, value);
|
|
126
|
+
}
|
|
97
127
|
} catch (e) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (failed) {
|
|
105
|
-
sendActionFailed(value);
|
|
106
|
-
} else {
|
|
107
|
-
sendActionCompleted(value);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// removeActionRequestListener()
|
|
111
|
-
if (actionParams.exitAfterAction) {
|
|
112
|
-
removeActionRequestListener();
|
|
113
|
-
// for some reason this fixes v8 coverage directory sometimes empty on Ubuntu
|
|
114
|
-
// process.exit()
|
|
128
|
+
sendActionInternalError(id, e);
|
|
129
|
+
} finally {
|
|
130
|
+
process.removeListener("uncaughtException", onUncaughtException);
|
|
131
|
+
if (params.exitAfterAction) {
|
|
132
|
+
removeActionRequestListener();
|
|
133
|
+
}
|
|
115
134
|
}
|
|
116
135
|
},
|
|
117
136
|
);
|
|
@@ -3,11 +3,7 @@
|
|
|
3
3
|
// https://github.com/avajs/ava/blob/576f534b345259055c95fa0c2b33bef10847a2af/lib/worker/base.js
|
|
4
4
|
import { Worker } from "node:worker_threads";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import {
|
|
7
|
-
Abort,
|
|
8
|
-
createCallbackListNotifiedOnce,
|
|
9
|
-
raceCallbacks,
|
|
10
|
-
} from "@jsenv/abort";
|
|
6
|
+
import { Abort, raceCallbacks } from "@jsenv/abort";
|
|
11
7
|
import { memoize } from "@jsenv/utils/src/memoize/memoize.js";
|
|
12
8
|
|
|
13
9
|
import { createChildExecOptions } from "./child_exec_options.js";
|
|
@@ -51,17 +47,24 @@ export const nodeWorkerThread = ({
|
|
|
51
47
|
keepRunning,
|
|
52
48
|
stopSignal,
|
|
53
49
|
onConsole,
|
|
50
|
+
onRuntimeStarted,
|
|
51
|
+
onRuntimeStopped,
|
|
54
52
|
|
|
53
|
+
measureMemoryUsage,
|
|
54
|
+
onMeasureMemoryAvailable,
|
|
55
55
|
collectConsole = false,
|
|
56
56
|
collectPerformance,
|
|
57
57
|
coverageEnabled = false,
|
|
58
|
-
|
|
58
|
+
coverageInclude,
|
|
59
59
|
coverageMethodForNodeJs,
|
|
60
60
|
coverageFileUrl,
|
|
61
61
|
}) => {
|
|
62
62
|
if (coverageMethodForNodeJs !== "NODE_V8_COVERAGE") {
|
|
63
63
|
env.NODE_V8_COVERAGE = "";
|
|
64
64
|
}
|
|
65
|
+
if (onMeasureMemoryAvailable) {
|
|
66
|
+
env.MEASURE_MEMORY_AT_START = "1";
|
|
67
|
+
}
|
|
65
68
|
if (importMap) {
|
|
66
69
|
env.IMPORT_MAP = JSON.stringify(importMap);
|
|
67
70
|
env.IMPORT_MAP_BASE_URL = rootDirectoryUrl;
|
|
@@ -86,10 +89,16 @@ export const nodeWorkerThread = ({
|
|
|
86
89
|
...env,
|
|
87
90
|
};
|
|
88
91
|
|
|
89
|
-
const
|
|
92
|
+
const cleanupCallbackSet = new Set();
|
|
90
93
|
const cleanup = async (reason) => {
|
|
91
|
-
|
|
94
|
+
const promises = [];
|
|
95
|
+
for (const cleanupCallback of cleanupCallbackSet) {
|
|
96
|
+
promises.push(cleanupCallback({ reason }));
|
|
97
|
+
}
|
|
98
|
+
cleanupCallbackSet.clear();
|
|
99
|
+
await Promise.all(promises);
|
|
92
100
|
};
|
|
101
|
+
|
|
93
102
|
const actionOperation = Abort.startOperation();
|
|
94
103
|
actionOperation.addAbortSignal(signal);
|
|
95
104
|
// https://nodejs.org/api/worker_threads.html#new-workerfilename-options
|
|
@@ -111,13 +120,26 @@ export const nodeWorkerThread = ({
|
|
|
111
120
|
},
|
|
112
121
|
);
|
|
113
122
|
const workerThreadReadyPromise = new Promise((resolve) => {
|
|
114
|
-
|
|
123
|
+
const removeReadyListener = onWorkerThreadMessage(
|
|
124
|
+
workerThread,
|
|
125
|
+
"ready",
|
|
126
|
+
() => {
|
|
127
|
+
removeReadyListener();
|
|
128
|
+
onRuntimeStarted();
|
|
129
|
+
resolve();
|
|
130
|
+
},
|
|
131
|
+
);
|
|
115
132
|
});
|
|
133
|
+
cleanupCallbackSet.add(
|
|
134
|
+
onceWorkerThreadEvent(workerThread, "exit", () => {
|
|
135
|
+
onRuntimeStopped();
|
|
136
|
+
}),
|
|
137
|
+
);
|
|
116
138
|
|
|
117
139
|
const stop = memoize(async () => {
|
|
118
140
|
// read all stdout before terminating
|
|
119
141
|
// (no need for stderr because it's sync)
|
|
120
|
-
if (collectConsole) {
|
|
142
|
+
if (collectConsole || onConsole) {
|
|
121
143
|
while (workerThread.stdout.read() !== null) {}
|
|
122
144
|
await new Promise((resolve) => {
|
|
123
145
|
setTimeout(resolve, 50);
|
|
@@ -126,134 +148,175 @@ export const nodeWorkerThread = ({
|
|
|
126
148
|
await workerThread.terminate();
|
|
127
149
|
});
|
|
128
150
|
|
|
129
|
-
const winnerPromise = new Promise((resolve) => {
|
|
130
|
-
raceCallbacks(
|
|
131
|
-
{
|
|
132
|
-
aborted: (cb) => {
|
|
133
|
-
return actionOperation.addAbortCallback(cb);
|
|
134
|
-
},
|
|
135
|
-
error: (cb) => {
|
|
136
|
-
return onceWorkerThreadEvent(workerThread, "error", cb);
|
|
137
|
-
},
|
|
138
|
-
exit: (cb) => {
|
|
139
|
-
return onceWorkerThreadEvent(
|
|
140
|
-
workerThread,
|
|
141
|
-
"exit",
|
|
142
|
-
(code, signal) => {
|
|
143
|
-
cb({ code, signal });
|
|
144
|
-
},
|
|
145
|
-
);
|
|
146
|
-
},
|
|
147
|
-
response: (cb) => {
|
|
148
|
-
return onceWorkerThreadMessage(workerThread, "action-result", cb);
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
resolve,
|
|
152
|
-
);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
151
|
const result = {
|
|
156
152
|
status: "executing",
|
|
157
153
|
errors: [],
|
|
158
154
|
namespace: null,
|
|
155
|
+
timings: {},
|
|
156
|
+
memoryUsage: null,
|
|
157
|
+
performance: null,
|
|
159
158
|
};
|
|
160
159
|
|
|
161
|
-
|
|
160
|
+
try {
|
|
161
|
+
let executionInternalErrorCallback;
|
|
162
|
+
let executionCompletedCallback;
|
|
163
|
+
const winnerPromise = new Promise((resolve) => {
|
|
164
|
+
raceCallbacks(
|
|
165
|
+
{
|
|
166
|
+
aborted: (cb) => {
|
|
167
|
+
return actionOperation.addAbortCallback(cb);
|
|
168
|
+
},
|
|
169
|
+
error: (cb) => {
|
|
170
|
+
return onceWorkerThreadEvent(workerThread, "error", cb);
|
|
171
|
+
},
|
|
172
|
+
exit: (cb) => {
|
|
173
|
+
return onceWorkerThreadEvent(
|
|
174
|
+
workerThread,
|
|
175
|
+
"exit",
|
|
176
|
+
(code, signal) => {
|
|
177
|
+
cb({ code, signal });
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
},
|
|
181
|
+
execution_internal_error: (cb) => {
|
|
182
|
+
executionInternalErrorCallback = cb;
|
|
183
|
+
},
|
|
184
|
+
execution_completed: (cb) => {
|
|
185
|
+
executionCompletedCallback = cb;
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
resolve,
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
const raceHandlers = {
|
|
192
|
+
aborted: () => {
|
|
193
|
+
result.status = "aborted";
|
|
194
|
+
},
|
|
195
|
+
error: (error) => {
|
|
196
|
+
removeOutputListener();
|
|
197
|
+
result.status = "failed";
|
|
198
|
+
result.errors.push(error);
|
|
199
|
+
},
|
|
200
|
+
exit: ({ code }) => {
|
|
201
|
+
onRuntimeStopped();
|
|
202
|
+
if (code === 12) {
|
|
203
|
+
result.status = "failed";
|
|
204
|
+
result.errors.push(
|
|
205
|
+
new Error(
|
|
206
|
+
`node process exited with 12 (the forked child process wanted to use a non-available port for debug)`,
|
|
207
|
+
),
|
|
208
|
+
);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (code === null || code === 0) {
|
|
212
|
+
result.status = "completed";
|
|
213
|
+
result.namespace = {};
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (
|
|
217
|
+
code === EXIT_CODES.SIGINT ||
|
|
218
|
+
code === EXIT_CODES.SIGTERM ||
|
|
219
|
+
code === EXIT_CODES.SIGABORT
|
|
220
|
+
) {
|
|
221
|
+
result.status = "failed";
|
|
222
|
+
result.errors.push(
|
|
223
|
+
new Error(`node worker thread exited during execution`),
|
|
224
|
+
);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
// process.exit(1) in child process or process.exitCode = 1 + process.exit()
|
|
228
|
+
// means there was an error even if we don't know exactly what.
|
|
229
|
+
result.status = "failed";
|
|
230
|
+
result.errors.push(
|
|
231
|
+
new Error(
|
|
232
|
+
`node worker thread exited with code ${code} during execution`,
|
|
233
|
+
),
|
|
234
|
+
);
|
|
235
|
+
},
|
|
236
|
+
execution_internal_error: (error) => {
|
|
237
|
+
result.status = "failed";
|
|
238
|
+
result.errors.push(error);
|
|
239
|
+
},
|
|
240
|
+
execution_completed: ({
|
|
241
|
+
status,
|
|
242
|
+
errors,
|
|
243
|
+
namespace,
|
|
244
|
+
timings,
|
|
245
|
+
memoryUsage,
|
|
246
|
+
performance,
|
|
247
|
+
coverage,
|
|
248
|
+
}) => {
|
|
249
|
+
result.status = status;
|
|
250
|
+
result.errors = errors;
|
|
251
|
+
result.namespace = namespace;
|
|
252
|
+
result.timings = timings;
|
|
253
|
+
result.memoryUsage = memoryUsage;
|
|
254
|
+
result.performance = performance;
|
|
255
|
+
result.coverage = coverage;
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
|
|
162
259
|
actionOperation.throwIfAborted();
|
|
163
260
|
await workerThreadReadyPromise;
|
|
164
261
|
actionOperation.throwIfAborted();
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
262
|
+
if (onMeasureMemoryAvailable) {
|
|
263
|
+
onMeasureMemoryAvailable(async () => {
|
|
264
|
+
let _resolve;
|
|
265
|
+
const memoryUsagePromise = new Promise((resolve) => {
|
|
266
|
+
_resolve = resolve;
|
|
267
|
+
});
|
|
268
|
+
await requestActionOnWorkerThread(
|
|
269
|
+
workerThread,
|
|
270
|
+
{
|
|
271
|
+
type: "measure-memory-usage",
|
|
272
|
+
},
|
|
273
|
+
({ value }) => {
|
|
274
|
+
_resolve(value);
|
|
275
|
+
},
|
|
276
|
+
);
|
|
277
|
+
return memoryUsagePromise;
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
await requestActionOnWorkerThread(
|
|
281
|
+
workerThread,
|
|
282
|
+
{
|
|
283
|
+
type: "execute-using-dynamic-import",
|
|
284
|
+
params: {
|
|
170
285
|
rootDirectoryUrl,
|
|
171
286
|
fileUrl: new URL(fileRelativeUrl, rootDirectoryUrl).href,
|
|
287
|
+
measureMemoryUsage,
|
|
172
288
|
collectPerformance,
|
|
173
289
|
coverageEnabled,
|
|
174
|
-
|
|
290
|
+
coverageInclude,
|
|
175
291
|
coverageMethodForNodeJs,
|
|
176
292
|
coverageFileUrl,
|
|
177
293
|
exitAfterAction: true,
|
|
178
294
|
},
|
|
179
295
|
},
|
|
180
|
-
|
|
296
|
+
({ status, value }) => {
|
|
297
|
+
if (status === "error") {
|
|
298
|
+
executionInternalErrorCallback(value);
|
|
299
|
+
} else {
|
|
300
|
+
executionCompletedCallback(value);
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
);
|
|
181
304
|
const winner = await winnerPromise;
|
|
182
|
-
|
|
183
|
-
result.status = "aborted";
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
if (winner.name === "error") {
|
|
187
|
-
const error = winner.data;
|
|
188
|
-
removeOutputListener();
|
|
189
|
-
result.status = "failed";
|
|
190
|
-
result.errors.push(error);
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
if (winner.name === "exit") {
|
|
194
|
-
const { code } = winner.data;
|
|
195
|
-
await cleanup("process exit");
|
|
196
|
-
if (code === 12) {
|
|
197
|
-
result.status = "failed";
|
|
198
|
-
result.errors.push(
|
|
199
|
-
new Error(
|
|
200
|
-
`node process exited with 12 (the forked child process wanted to use a non-available port for debug)`,
|
|
201
|
-
),
|
|
202
|
-
);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (code === null || code === 0) {
|
|
206
|
-
result.status = "completed";
|
|
207
|
-
result.namespace = {};
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
if (
|
|
211
|
-
code === EXIT_CODES.SIGINT ||
|
|
212
|
-
code === EXIT_CODES.SIGTERM ||
|
|
213
|
-
code === EXIT_CODES.SIGABORT
|
|
214
|
-
) {
|
|
215
|
-
result.status = "failed";
|
|
216
|
-
result.errors.push(
|
|
217
|
-
new Error(`node worker thread exited during execution`),
|
|
218
|
-
);
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
// process.exit(1) in child process or process.exitCode = 1 + process.exit()
|
|
222
|
-
// means there was an error even if we don't know exactly what.
|
|
223
|
-
result.status = "failed";
|
|
224
|
-
result.errors.push(
|
|
225
|
-
new Error(
|
|
226
|
-
`node worker thread exited with code ${code} during execution`,
|
|
227
|
-
),
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
const { status, value } = winner.data;
|
|
231
|
-
if (status === "action-failed") {
|
|
232
|
-
result.status = "failed";
|
|
233
|
-
result.errors.push(value);
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
const { namespace, performance, coverage } = value;
|
|
237
|
-
result.status = "completed";
|
|
238
|
-
result.namespace = namespace;
|
|
239
|
-
result.performance = performance;
|
|
240
|
-
result.coverage = coverage;
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
try {
|
|
244
|
-
await writeResult();
|
|
305
|
+
raceHandlers[winner.name](winner.data);
|
|
245
306
|
} catch (e) {
|
|
246
307
|
result.status = "failed";
|
|
247
308
|
result.errors.push(e);
|
|
309
|
+
} finally {
|
|
310
|
+
if (keepRunning) {
|
|
311
|
+
stopSignal.notify = stop;
|
|
312
|
+
} else {
|
|
313
|
+
await stop();
|
|
314
|
+
onRuntimeStopped();
|
|
315
|
+
}
|
|
316
|
+
await actionOperation.end();
|
|
317
|
+
await cleanup();
|
|
318
|
+
return result;
|
|
248
319
|
}
|
|
249
|
-
|
|
250
|
-
if (keepRunning) {
|
|
251
|
-
stopSignal.notify = stop;
|
|
252
|
-
} else {
|
|
253
|
-
await stop();
|
|
254
|
-
}
|
|
255
|
-
await actionOperation.end();
|
|
256
|
-
return result;
|
|
257
320
|
},
|
|
258
321
|
};
|
|
259
322
|
};
|
|
@@ -276,14 +339,37 @@ const installWorkerThreadOutputListener = (workerThread, callback) => {
|
|
|
276
339
|
};
|
|
277
340
|
};
|
|
278
341
|
|
|
279
|
-
|
|
280
|
-
|
|
342
|
+
let previousId = 0;
|
|
343
|
+
const requestActionOnWorkerThread = (
|
|
344
|
+
workerThread,
|
|
345
|
+
{ type, params },
|
|
346
|
+
onResponse,
|
|
347
|
+
) => {
|
|
348
|
+
const actionId = previousId + 1;
|
|
349
|
+
previousId = actionId;
|
|
350
|
+
const removeResultListener = onWorkerThreadMessage(
|
|
351
|
+
workerThread,
|
|
352
|
+
"action-result",
|
|
353
|
+
({ id, ...payload }) => {
|
|
354
|
+
if (id === actionId) {
|
|
355
|
+
removeResultListener();
|
|
356
|
+
onResponse(payload);
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
);
|
|
360
|
+
workerThread.postMessage({
|
|
361
|
+
__jsenv__: "action",
|
|
362
|
+
data: {
|
|
363
|
+
id: actionId,
|
|
364
|
+
type,
|
|
365
|
+
params,
|
|
366
|
+
},
|
|
367
|
+
});
|
|
281
368
|
};
|
|
282
369
|
|
|
283
|
-
const
|
|
370
|
+
const onWorkerThreadMessage = (workerThread, type, callback) => {
|
|
284
371
|
const onmessage = (message) => {
|
|
285
|
-
if (message && message.
|
|
286
|
-
workerThread.removeListener("message", onmessage);
|
|
372
|
+
if (message && message.__jsenv__ === type) {
|
|
287
373
|
callback(message.data ? JSON.parse(message.data) : undefined);
|
|
288
374
|
}
|
|
289
375
|
};
|