@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,100 @@
|
|
|
1
1
|
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { memoryUsage } from "node:process";
|
|
2
3
|
|
|
3
4
|
import { startJsCoverage } from "./profiler_v8_coverage.js";
|
|
4
5
|
import { startObservingPerformances } from "./node_execution_performance.js";
|
|
6
|
+
import { createException } from "../execution/exception.js";
|
|
5
7
|
|
|
6
8
|
export const executeUsingDynamicImport = async ({
|
|
7
9
|
rootDirectoryUrl,
|
|
8
10
|
fileUrl,
|
|
11
|
+
measureMemoryUsage,
|
|
9
12
|
collectPerformance,
|
|
10
13
|
coverageEnabled,
|
|
11
|
-
|
|
14
|
+
coverageInclude,
|
|
12
15
|
coverageMethodForNodeJs,
|
|
13
16
|
coverageFileUrl,
|
|
14
17
|
}) => {
|
|
15
|
-
const result = {
|
|
16
|
-
|
|
18
|
+
const result = {
|
|
19
|
+
timings: {
|
|
20
|
+
start: null,
|
|
21
|
+
end: null,
|
|
22
|
+
},
|
|
23
|
+
errors: [],
|
|
24
|
+
namespace: null,
|
|
25
|
+
memoryUsage: null,
|
|
26
|
+
performance: null,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
let finalizePerformance;
|
|
30
|
+
if (collectPerformance) {
|
|
31
|
+
const getPerformance = startObservingPerformances();
|
|
32
|
+
finalizePerformance = async () => {
|
|
33
|
+
const performance = await getPerformance();
|
|
34
|
+
result.performance = performance;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let finalizeCoverage;
|
|
17
39
|
if (coverageEnabled && coverageMethodForNodeJs === "Profiler") {
|
|
18
|
-
const { filterV8Coverage } = await import("../coverage/v8_coverage.js");
|
|
19
40
|
const { stopJsCoverage } = await startJsCoverage();
|
|
20
|
-
|
|
21
|
-
const coverage = await
|
|
41
|
+
finalizeCoverage = async () => {
|
|
42
|
+
const [coverage, { filterV8Coverage }] = await Promise.all([
|
|
43
|
+
stopJsCoverage(),
|
|
44
|
+
import("../coverage/v8_coverage.js"),
|
|
45
|
+
]);
|
|
22
46
|
const coverageLight = await filterV8Coverage(coverage, {
|
|
23
47
|
rootDirectoryUrl,
|
|
24
|
-
|
|
48
|
+
coverageInclude,
|
|
25
49
|
});
|
|
26
50
|
writeFileSync(
|
|
27
51
|
new URL(coverageFileUrl),
|
|
28
52
|
JSON.stringify(coverageLight, null, " "),
|
|
29
53
|
);
|
|
30
|
-
}
|
|
54
|
+
};
|
|
31
55
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
56
|
+
|
|
57
|
+
let finalizeMemoryUsage;
|
|
58
|
+
if (measureMemoryUsage) {
|
|
59
|
+
global.gc();
|
|
60
|
+
const memoryHeapUsedBeforeExecution = memoryUsage().heapUsed;
|
|
61
|
+
finalizeMemoryUsage = () => {
|
|
62
|
+
global.gc();
|
|
63
|
+
const memoryHeapUsedAfterExecution = memoryUsage().heapUsed;
|
|
64
|
+
result.memoryUsage =
|
|
65
|
+
memoryHeapUsedAfterExecution - memoryHeapUsedBeforeExecution;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
result.timings.start = Date.now();
|
|
70
|
+
try {
|
|
71
|
+
const namespace = await import(fileUrl);
|
|
72
|
+
const namespaceResolved = {};
|
|
73
|
+
await Promise.all(
|
|
74
|
+
Object.keys(namespace).map(async (key) => {
|
|
75
|
+
const value = await namespace[key];
|
|
76
|
+
namespaceResolved[key] = value;
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
result.status = "completed";
|
|
80
|
+
result.namespace = namespaceResolved;
|
|
81
|
+
} catch (e) {
|
|
82
|
+
result.status = "failed";
|
|
83
|
+
result.errors.push(createException(e, { rootDirectoryUrl }));
|
|
84
|
+
} finally {
|
|
85
|
+
result.timings.end = Date.now();
|
|
86
|
+
if (finalizeCoverage) {
|
|
87
|
+
await finalizeCoverage();
|
|
88
|
+
finalizeCoverage = null;
|
|
89
|
+
}
|
|
90
|
+
if (finalizePerformance) {
|
|
91
|
+
await finalizePerformance();
|
|
92
|
+
finalizePerformance = null;
|
|
93
|
+
}
|
|
94
|
+
if (finalizeMemoryUsage) {
|
|
95
|
+
finalizeMemoryUsage();
|
|
96
|
+
finalizeMemoryUsage = null;
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
38
99
|
}
|
|
39
|
-
const namespace = await import(fileUrl);
|
|
40
|
-
const namespaceResolved = {};
|
|
41
|
-
await Promise.all(
|
|
42
|
-
Object.keys(namespace).map(async (key) => {
|
|
43
|
-
const value = await namespace[key];
|
|
44
|
-
namespaceResolved[key] = value;
|
|
45
|
-
}),
|
|
46
|
-
);
|
|
47
|
-
result.namespace = namespaceResolved;
|
|
48
|
-
await afterImportCallbacks.reduce(async (previous, afterImportCallback) => {
|
|
49
|
-
await previous;
|
|
50
|
-
await afterImportCallback();
|
|
51
|
-
}, Promise.resolve());
|
|
52
|
-
return result;
|
|
53
100
|
};
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { fork } from "node:child_process";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import {
|
|
4
|
-
Abort,
|
|
5
|
-
raceCallbacks,
|
|
6
|
-
createCallbackListNotifiedOnce,
|
|
7
|
-
} from "@jsenv/abort";
|
|
3
|
+
import { Abort, raceCallbacks } from "@jsenv/abort";
|
|
8
4
|
import { createDetailedMessage } from "@jsenv/log";
|
|
9
5
|
import { memoize } from "@jsenv/utils/src/memoize/memoize.js";
|
|
10
6
|
|
|
@@ -56,12 +52,17 @@ export const nodeChildProcess = ({
|
|
|
56
52
|
keepRunning,
|
|
57
53
|
stopSignal,
|
|
58
54
|
onConsole,
|
|
55
|
+
onRuntimeStarted,
|
|
56
|
+
onRuntimeStopped,
|
|
59
57
|
|
|
58
|
+
measureMemoryUsage,
|
|
59
|
+
onMeasureMemoryAvailable,
|
|
60
|
+
collectConsole = false,
|
|
61
|
+
collectPerformance,
|
|
60
62
|
coverageEnabled = false,
|
|
61
|
-
|
|
63
|
+
coverageInclude,
|
|
62
64
|
coverageMethodForNodeJs,
|
|
63
65
|
coverageFileUrl,
|
|
64
|
-
collectPerformance,
|
|
65
66
|
}) => {
|
|
66
67
|
if (coverageMethodForNodeJs !== "NODE_V8_COVERAGE") {
|
|
67
68
|
env.NODE_V8_COVERAGE = "";
|
|
@@ -70,7 +71,14 @@ export const nodeChildProcess = ({
|
|
|
70
71
|
"--experimental-import-meta-resolve",
|
|
71
72
|
...commandLineOptions,
|
|
72
73
|
];
|
|
73
|
-
|
|
74
|
+
if (onMeasureMemoryAvailable) {
|
|
75
|
+
env.MEASURE_MEMORY_AT_START = "1";
|
|
76
|
+
}
|
|
77
|
+
if (measureMemoryUsage || onMeasureMemoryAvailable) {
|
|
78
|
+
if (!commandLineOptions.includes("--expose-gc")) {
|
|
79
|
+
commandLineOptions.push("--expose-gc");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
74
82
|
if (importMap) {
|
|
75
83
|
env.IMPORT_MAP = JSON.stringify(importMap);
|
|
76
84
|
env.IMPORT_MAP_BASE_URL = rootDirectoryUrl;
|
|
@@ -82,9 +90,14 @@ export const nodeChildProcess = ({
|
|
|
82
90
|
);
|
|
83
91
|
}
|
|
84
92
|
|
|
85
|
-
const
|
|
93
|
+
const cleanupCallbackSet = new Set();
|
|
86
94
|
const cleanup = async (reason) => {
|
|
87
|
-
|
|
95
|
+
const promises = [];
|
|
96
|
+
for (const cleanupCallback of cleanupCallbackSet) {
|
|
97
|
+
promises.push(cleanupCallback({ reason }));
|
|
98
|
+
}
|
|
99
|
+
cleanupCallbackSet.clear();
|
|
100
|
+
await Promise.all(promises);
|
|
88
101
|
};
|
|
89
102
|
|
|
90
103
|
const childExecOptions = await createChildExecOptions({
|
|
@@ -106,9 +119,11 @@ export const nodeChildProcess = ({
|
|
|
106
119
|
CONTROLLED_CHILD_PROCESS_URL,
|
|
107
120
|
)}`,
|
|
108
121
|
);
|
|
122
|
+
const actionOperation = Abort.startOperation();
|
|
123
|
+
actionOperation.addAbortSignal(signal);
|
|
109
124
|
const childProcess = fork(fileURLToPath(CONTROLLED_CHILD_PROCESS_URL), {
|
|
110
125
|
execArgv,
|
|
111
|
-
// silent: true
|
|
126
|
+
// silent: true,
|
|
112
127
|
stdio: ["pipe", "pipe", "pipe", "ipc"],
|
|
113
128
|
env: envForChildProcess,
|
|
114
129
|
});
|
|
@@ -131,15 +146,48 @@ export const nodeChildProcess = ({
|
|
|
131
146
|
childProcess.stderr.pipe(stderr);
|
|
132
147
|
}
|
|
133
148
|
const childProcessReadyPromise = new Promise((resolve) => {
|
|
134
|
-
|
|
149
|
+
const removeReadyListener = onChildProcessMessage(
|
|
150
|
+
childProcess,
|
|
151
|
+
"ready",
|
|
152
|
+
() => {
|
|
153
|
+
removeReadyListener();
|
|
154
|
+
onRuntimeStarted();
|
|
155
|
+
resolve();
|
|
156
|
+
},
|
|
157
|
+
);
|
|
135
158
|
});
|
|
159
|
+
cleanupCallbackSet.add(
|
|
160
|
+
onceChildProcessEvent(childProcess, "exit", () => {
|
|
161
|
+
onRuntimeStopped();
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
|
|
136
165
|
const removeOutputListener = installChildProcessOutputListener(
|
|
137
166
|
childProcess,
|
|
138
167
|
({ type, text }) => {
|
|
168
|
+
if (type === "error" && text.startsWith("Debugger attached.")) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (
|
|
172
|
+
type === "error" &&
|
|
173
|
+
text.startsWith("Waiting for the debugger to disconnect...")
|
|
174
|
+
) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
139
178
|
onConsole({ type, text });
|
|
140
179
|
},
|
|
141
180
|
);
|
|
142
181
|
const stop = memoize(async ({ gracefulStopAllocatedMs } = {}) => {
|
|
182
|
+
// read all stdout before terminating
|
|
183
|
+
// (no need for stderr because it's sync)
|
|
184
|
+
if (collectConsole || onConsole) {
|
|
185
|
+
while (childProcess.stdout.read() !== null) {}
|
|
186
|
+
await new Promise((resolve) => {
|
|
187
|
+
setTimeout(resolve, 50);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
143
191
|
// all libraries are facing problem on windows when trying
|
|
144
192
|
// to kill a process spawning other processes.
|
|
145
193
|
// "killProcessTree" is theorically correct but sometimes keep process handing forever.
|
|
@@ -174,159 +222,214 @@ export const nodeChildProcess = ({
|
|
|
174
222
|
return;
|
|
175
223
|
});
|
|
176
224
|
|
|
177
|
-
const actionOperation = Abort.startOperation();
|
|
178
|
-
actionOperation.addAbortSignal(signal);
|
|
179
|
-
const winnerPromise = new Promise((resolve) => {
|
|
180
|
-
raceCallbacks(
|
|
181
|
-
{
|
|
182
|
-
aborted: (cb) => {
|
|
183
|
-
return actionOperation.addAbortCallback(cb);
|
|
184
|
-
},
|
|
185
|
-
// https://nodejs.org/api/child_process.html#child_process_event_disconnect
|
|
186
|
-
// disconnect: (cb) => {
|
|
187
|
-
// return onceProcessEvent(childProcess, "disconnect", cb)
|
|
188
|
-
// },
|
|
189
|
-
// https://nodejs.org/api/child_process.html#child_process_event_error
|
|
190
|
-
error: (cb) => {
|
|
191
|
-
return onceChildProcessEvent(childProcess, "error", cb);
|
|
192
|
-
},
|
|
193
|
-
exit: (cb) => {
|
|
194
|
-
return onceChildProcessEvent(
|
|
195
|
-
childProcess,
|
|
196
|
-
"exit",
|
|
197
|
-
(code, signal) => {
|
|
198
|
-
cb({ code, signal });
|
|
199
|
-
},
|
|
200
|
-
);
|
|
201
|
-
},
|
|
202
|
-
response: (cb) => {
|
|
203
|
-
return onceChildProcessMessage(childProcess, "action-result", cb);
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
resolve,
|
|
207
|
-
);
|
|
208
|
-
});
|
|
209
225
|
const result = {
|
|
210
226
|
status: "executing",
|
|
211
227
|
errors: [],
|
|
212
228
|
namespace: null,
|
|
229
|
+
timings: {},
|
|
230
|
+
memoryUsage: null,
|
|
231
|
+
performance: null,
|
|
213
232
|
};
|
|
214
233
|
|
|
215
|
-
|
|
234
|
+
try {
|
|
235
|
+
let executionInternalErrorCallback;
|
|
236
|
+
let executionCompletedCallback;
|
|
237
|
+
const winnerPromise = new Promise((resolve) => {
|
|
238
|
+
raceCallbacks(
|
|
239
|
+
{
|
|
240
|
+
aborted: (cb) => {
|
|
241
|
+
return actionOperation.addAbortCallback(cb);
|
|
242
|
+
},
|
|
243
|
+
// https://nodejs.org/api/child_process.html#child_process_event_disconnect
|
|
244
|
+
// disconnect: (cb) => {
|
|
245
|
+
// return onceProcessEvent(childProcess, "disconnect", cb)
|
|
246
|
+
// },
|
|
247
|
+
// https://nodejs.org/api/child_process.html#child_process_event_error
|
|
248
|
+
error: (cb) => {
|
|
249
|
+
return onceChildProcessEvent(childProcess, "error", cb);
|
|
250
|
+
},
|
|
251
|
+
exit: (cb) => {
|
|
252
|
+
return onceChildProcessEvent(
|
|
253
|
+
childProcess,
|
|
254
|
+
"exit",
|
|
255
|
+
(code, signal) => {
|
|
256
|
+
cb({ code, signal });
|
|
257
|
+
},
|
|
258
|
+
);
|
|
259
|
+
},
|
|
260
|
+
execution_internal_error: (cb) => {
|
|
261
|
+
executionInternalErrorCallback = cb;
|
|
262
|
+
},
|
|
263
|
+
execution_completed: (cb) => {
|
|
264
|
+
executionCompletedCallback = cb;
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
resolve,
|
|
268
|
+
);
|
|
269
|
+
});
|
|
270
|
+
const raceHandlers = {
|
|
271
|
+
aborted: () => {
|
|
272
|
+
result.status = "aborted";
|
|
273
|
+
},
|
|
274
|
+
error: (error) => {
|
|
275
|
+
removeOutputListener();
|
|
276
|
+
result.status = "failed";
|
|
277
|
+
result.errors.push(error);
|
|
278
|
+
},
|
|
279
|
+
exit: ({ code }) => {
|
|
280
|
+
onRuntimeStopped();
|
|
281
|
+
if (code === 12) {
|
|
282
|
+
result.status = "failed";
|
|
283
|
+
result.errors.push(
|
|
284
|
+
new Error(
|
|
285
|
+
`node process exited with 12 (the forked child process wanted to use a non-available port for debug)`,
|
|
286
|
+
),
|
|
287
|
+
);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (code === null || code === 0) {
|
|
291
|
+
result.status = "completed";
|
|
292
|
+
result.namespace = {};
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (
|
|
296
|
+
code === EXIT_CODES.SIGINT ||
|
|
297
|
+
code === EXIT_CODES.SIGTERM ||
|
|
298
|
+
code === EXIT_CODES.SIGABORT
|
|
299
|
+
) {
|
|
300
|
+
result.status = "failed";
|
|
301
|
+
result.errors.push(
|
|
302
|
+
new Error(`node process exited during execution`),
|
|
303
|
+
);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
// process.exit(1) in child process or process.exitCode = 1 + process.exit()
|
|
307
|
+
// means there was an error even if we don't know exactly what.
|
|
308
|
+
result.status = "failed";
|
|
309
|
+
result.errors.push(
|
|
310
|
+
new Error(
|
|
311
|
+
`node process exited with code ${code} during execution`,
|
|
312
|
+
),
|
|
313
|
+
);
|
|
314
|
+
},
|
|
315
|
+
execution_internal_error: (error) => {
|
|
316
|
+
result.status = "failed";
|
|
317
|
+
result.errors.push(error);
|
|
318
|
+
},
|
|
319
|
+
execution_completed: ({
|
|
320
|
+
status,
|
|
321
|
+
errors,
|
|
322
|
+
namespace,
|
|
323
|
+
timings,
|
|
324
|
+
memoryUsage,
|
|
325
|
+
performance,
|
|
326
|
+
coverage,
|
|
327
|
+
}) => {
|
|
328
|
+
result.status = status;
|
|
329
|
+
result.errors = errors;
|
|
330
|
+
result.namespace = namespace;
|
|
331
|
+
result.timings = timings;
|
|
332
|
+
result.memoryUsage = memoryUsage;
|
|
333
|
+
result.performance = performance;
|
|
334
|
+
result.coverage = coverage;
|
|
335
|
+
},
|
|
336
|
+
};
|
|
216
337
|
actionOperation.throwIfAborted();
|
|
217
338
|
await childProcessReadyPromise;
|
|
218
339
|
actionOperation.throwIfAborted();
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
340
|
+
if (onMeasureMemoryAvailable) {
|
|
341
|
+
onMeasureMemoryAvailable(async () => {
|
|
342
|
+
let _resolve;
|
|
343
|
+
const memoryUsagePromise = new Promise((resolve) => {
|
|
344
|
+
_resolve = resolve;
|
|
345
|
+
});
|
|
346
|
+
await requestActionOnChildProcess(
|
|
347
|
+
childProcess,
|
|
348
|
+
{
|
|
349
|
+
type: "measure-memory-usage",
|
|
350
|
+
},
|
|
351
|
+
({ value }) => {
|
|
352
|
+
_resolve(value);
|
|
353
|
+
},
|
|
354
|
+
);
|
|
355
|
+
return memoryUsagePromise;
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
await requestActionOnChildProcess(
|
|
359
|
+
childProcess,
|
|
360
|
+
{
|
|
361
|
+
type: "execute-using-dynamic-import",
|
|
362
|
+
params: {
|
|
224
363
|
rootDirectoryUrl,
|
|
225
364
|
fileUrl: new URL(fileRelativeUrl, rootDirectoryUrl).href,
|
|
365
|
+
measureMemoryUsage,
|
|
226
366
|
collectPerformance,
|
|
227
367
|
coverageEnabled,
|
|
228
|
-
|
|
368
|
+
coverageInclude,
|
|
229
369
|
coverageMethodForNodeJs,
|
|
230
370
|
coverageFileUrl,
|
|
231
371
|
exitAfterAction: true,
|
|
232
372
|
},
|
|
233
373
|
},
|
|
234
|
-
|
|
374
|
+
({ status, value }) => {
|
|
375
|
+
if (status === "error") {
|
|
376
|
+
executionInternalErrorCallback(value);
|
|
377
|
+
} else {
|
|
378
|
+
executionCompletedCallback(value);
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
);
|
|
235
382
|
const winner = await winnerPromise;
|
|
236
|
-
|
|
237
|
-
result.status = "aborted";
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
if (winner.name === "error") {
|
|
241
|
-
const error = winner.data;
|
|
242
|
-
removeOutputListener();
|
|
243
|
-
result.status = "failed";
|
|
244
|
-
result.errors.push(error);
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
if (winner.name === "exit") {
|
|
248
|
-
const { code } = winner.data;
|
|
249
|
-
await cleanup("process exit");
|
|
250
|
-
if (code === 12) {
|
|
251
|
-
result.status = "failed";
|
|
252
|
-
result.errors.push(
|
|
253
|
-
new Error(
|
|
254
|
-
`node process exited with 12 (the forked child process wanted to use a non-available port for debug)`,
|
|
255
|
-
),
|
|
256
|
-
);
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
if (code === null || code === 0) {
|
|
260
|
-
result.status = "completed";
|
|
261
|
-
result.namespace = {};
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
if (
|
|
265
|
-
code === EXIT_CODES.SIGINT ||
|
|
266
|
-
code === EXIT_CODES.SIGTERM ||
|
|
267
|
-
code === EXIT_CODES.SIGABORT
|
|
268
|
-
) {
|
|
269
|
-
result.status = "failed";
|
|
270
|
-
result.errors.push(
|
|
271
|
-
new Error(`node process exited during execution`),
|
|
272
|
-
);
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
// process.exit(1) in child process or process.exitCode = 1 + process.exit()
|
|
276
|
-
// means there was an error even if we don't know exactly what.
|
|
277
|
-
result.status = "failed";
|
|
278
|
-
result.errors.push(
|
|
279
|
-
new Error(`node process exited with code ${code} during execution`),
|
|
280
|
-
);
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
const { status, value } = winner.data;
|
|
284
|
-
if (status === "action-failed") {
|
|
285
|
-
result.status = "failed";
|
|
286
|
-
result.errors.push(value);
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
const { namespace, performance, coverage } = value;
|
|
290
|
-
result.status = "completed";
|
|
291
|
-
result.namespace = namespace;
|
|
292
|
-
result.performance = performance;
|
|
293
|
-
result.coverage = coverage;
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
try {
|
|
297
|
-
await writeResult();
|
|
383
|
+
raceHandlers[winner.name](winner.data);
|
|
298
384
|
} catch (e) {
|
|
299
385
|
result.status = "failed";
|
|
300
386
|
result.errors.push(e);
|
|
387
|
+
} finally {
|
|
388
|
+
if (keepRunning) {
|
|
389
|
+
stopSignal.notify = stop;
|
|
390
|
+
} else {
|
|
391
|
+
await stop({
|
|
392
|
+
gracefulStopAllocatedMs,
|
|
393
|
+
});
|
|
394
|
+
onRuntimeStopped();
|
|
395
|
+
}
|
|
396
|
+
await actionOperation.end();
|
|
397
|
+
await cleanup();
|
|
398
|
+
return result;
|
|
301
399
|
}
|
|
302
|
-
if (keepRunning) {
|
|
303
|
-
stopSignal.notify = stop;
|
|
304
|
-
} else {
|
|
305
|
-
await stop({
|
|
306
|
-
gracefulStopAllocatedMs,
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
await actionOperation.end();
|
|
310
|
-
return result;
|
|
311
400
|
},
|
|
312
401
|
};
|
|
313
402
|
};
|
|
314
403
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const
|
|
404
|
+
let previousId = 0;
|
|
405
|
+
const requestActionOnChildProcess = async (
|
|
406
|
+
childProcess,
|
|
407
|
+
{ type, params },
|
|
408
|
+
onResponse,
|
|
409
|
+
) => {
|
|
410
|
+
const actionId = previousId + 1;
|
|
411
|
+
previousId = actionId;
|
|
322
412
|
|
|
323
|
-
const
|
|
324
|
-
|
|
413
|
+
const removeMessageListener = onChildProcessMessage(
|
|
414
|
+
childProcess,
|
|
415
|
+
"action-result",
|
|
416
|
+
({ id, ...payload }) => {
|
|
417
|
+
if (id === actionId) {
|
|
418
|
+
removeMessageListener();
|
|
419
|
+
onResponse(payload);
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
const sendPromise = new Promise((resolve, reject) => {
|
|
325
425
|
childProcess.send(
|
|
326
426
|
{
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
427
|
+
__jsenv__: "action",
|
|
428
|
+
data: {
|
|
429
|
+
id: actionId,
|
|
430
|
+
type,
|
|
431
|
+
params,
|
|
432
|
+
},
|
|
330
433
|
},
|
|
331
434
|
(error) => {
|
|
332
435
|
if (error) {
|
|
@@ -337,8 +440,17 @@ const sendToChildProcess = async (childProcess, { type, data }) => {
|
|
|
337
440
|
},
|
|
338
441
|
);
|
|
339
442
|
});
|
|
443
|
+
await sendPromise;
|
|
340
444
|
};
|
|
341
445
|
|
|
446
|
+
// http://man7.org/linux/man-pages/man7/signal.7.html
|
|
447
|
+
// https:// github.com/nodejs/node/blob/1d9511127c419ec116b3ddf5fc7a59e8f0f1c1e4/lib/internal/child_process.js#L472
|
|
448
|
+
const GRACEFUL_STOP_SIGNAL = "SIGTERM";
|
|
449
|
+
const STOP_SIGNAL = "SIGKILL";
|
|
450
|
+
// it would be more correct if GRACEFUL_STOP_FAILED_SIGNAL was SIGHUP instead of SIGKILL.
|
|
451
|
+
// but I'm not sure and it changes nothing so just use SIGKILL
|
|
452
|
+
const GRACEFUL_STOP_FAILED_SIGNAL = "SIGKILL";
|
|
453
|
+
|
|
342
454
|
const installChildProcessOutputListener = (childProcess, callback) => {
|
|
343
455
|
// beware that we may receive ansi output here, should not be a problem but keep that in mind
|
|
344
456
|
const stdoutDataCallback = (chunk) => {
|
|
@@ -351,14 +463,13 @@ const installChildProcessOutputListener = (childProcess, callback) => {
|
|
|
351
463
|
childProcess.stderr.on("data", stdErrorDataCallback);
|
|
352
464
|
return () => {
|
|
353
465
|
childProcess.stdout.removeListener("data", stdoutDataCallback);
|
|
354
|
-
childProcess.stderr.removeListener("data",
|
|
466
|
+
childProcess.stderr.removeListener("data", stdErrorDataCallback);
|
|
355
467
|
};
|
|
356
468
|
};
|
|
357
469
|
|
|
358
|
-
const
|
|
470
|
+
const onChildProcessMessage = (childProcess, type, callback) => {
|
|
359
471
|
const onmessage = (message) => {
|
|
360
|
-
if (message && message.
|
|
361
|
-
childProcess.removeListener("message", onmessage);
|
|
472
|
+
if (message && message.__jsenv__ === type) {
|
|
362
473
|
callback(message.data ? JSON.parse(message.data) : "");
|
|
363
474
|
}
|
|
364
475
|
};
|