@jsenv/test 3.3.12 → 3.3.14
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/dist/js/execute_using_dynamic_import.js +58 -58
- package/dist/jsenv_test.js +1476 -1464
- package/dist/node_child_process_controlled.mjs +1 -1
- package/dist/node_worker_thread_controlled.mjs +2 -2
- package/package.json +8 -8
- package/src/coverage/empty_coverage_factory.js +2 -2
- package/src/coverage/file_by_file_coverage.js +1 -1
- package/src/coverage/generate_coverage.js +6 -6
- package/src/coverage/missing_coverage.js +1 -1
- package/src/coverage/report_coverage_as_html.js +3 -3
- package/src/coverage/report_coverage_as_json.js +1 -1
- package/src/coverage/v8_coverage_node_directory.js +2 -2
- package/src/coverage/v8_coverage_to_istanbul.js +1 -1
- package/src/execution/exception.js +1 -1
- package/src/execution/execute.js +1 -1
- package/src/execution/execute_test_plan.js +20 -20
- package/src/execution/format_error_for_terminal.js +2 -2
- package/src/execution/reporters/reporter_list.js +21 -9
- package/src/execution/run.js +1 -1
- package/src/execution/web_server/start_using_command.js +1 -1
- package/src/execution/web_server_param.js +3 -3
- package/src/helpers/cpu_usage.test_manual.js +1 -1
- package/src/helpers/web_url_converter.js +1 -1
- package/src/main.js +4 -2
- package/src/runtime_browsers/middleware_istanbul.js +2 -2
- package/src/runtime_browsers/middleware_js_supervisor.js +2 -2
- package/src/runtime_browsers/using_playwright.js +5 -5
- package/src/runtime_node/execute_using_dynamic_import.js +2 -2
- package/src/runtime_node/importmap_hooks.mjs +1 -1
- package/src/runtime_node/node_child_process.js +4 -4
- package/src/runtime_node/node_worker_thread.js +3 -3
- package/src/runtime_node/node_worker_thread_controlled.mjs +1 -1
package/dist/jsenv_test.js
CHANGED
|
@@ -1,70 +1,26 @@
|
|
|
1
|
-
import os, { cpus, release, totalmem, availableParallelism, freemem } from "node:os";
|
|
2
1
|
import process$1, { cpuUsage, memoryUsage } from "node:process";
|
|
3
|
-
import
|
|
4
|
-
import { takeCoverage } from "node:v8";
|
|
5
|
-
import stripAnsi from "strip-ansi";
|
|
6
|
-
import { URL_META, createException } from "./js/exception.js";
|
|
2
|
+
import os, { cpus, release, totalmem, availableParallelism, freemem } from "node:os";
|
|
7
3
|
import tty from "node:tty";
|
|
8
4
|
import stringWidth from "string-width";
|
|
9
5
|
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
6
|
+
import { URL_META, createException } from "./js/exception.js";
|
|
7
|
+
import { readdir, chmod, stat, lstat, chmodSync, statSync, lstatSync, promises, readFile as readFile$1, readFileSync, writeFileSync as writeFileSync$1, mkdirSync, unlink, openSync, closeSync, rmdir, unlinkSync, readdirSync, rmdirSync, existsSync } from "node:fs";
|
|
10
8
|
import { dirname } from "node:path";
|
|
11
9
|
import crypto from "node:crypto";
|
|
12
10
|
import { readGitHubWorkflowEnv, startGithubCheckRun } from "@jsenv/github-check-run";
|
|
13
|
-
import {
|
|
11
|
+
import { takeCoverage } from "node:v8";
|
|
12
|
+
import stripAnsi from "strip-ansi";
|
|
14
13
|
import { createRequire } from "node:module";
|
|
15
14
|
import { applyBabelPlugins } from "@jsenv/ast";
|
|
16
|
-
import {
|
|
15
|
+
import { filterV8Coverage } from "./js/v8_coverage.js";
|
|
17
16
|
import { createServer } from "node:net";
|
|
18
|
-
import {
|
|
17
|
+
import { spawn, spawnSync, fork } from "node:child_process";
|
|
19
18
|
import { SOURCEMAP, generateSourcemapDataUrl } from "@jsenv/sourcemap";
|
|
19
|
+
import { injectSupervisorIntoHTML, supervisorFileUrl } from "@jsenv/plugin-supervisor";
|
|
20
20
|
import { findFreePort } from "@jsenv/server";
|
|
21
21
|
import { Worker } from "node:worker_threads";
|
|
22
22
|
import he from "he";
|
|
23
23
|
|
|
24
|
-
/*
|
|
25
|
-
* See callback_race.md
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
const raceCallbacks = (raceDescription, winnerCallback) => {
|
|
29
|
-
let cleanCallbacks = [];
|
|
30
|
-
let status = "racing";
|
|
31
|
-
|
|
32
|
-
const clean = () => {
|
|
33
|
-
cleanCallbacks.forEach((clean) => {
|
|
34
|
-
clean();
|
|
35
|
-
});
|
|
36
|
-
cleanCallbacks = null;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const cancel = () => {
|
|
40
|
-
if (status !== "racing") {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
status = "cancelled";
|
|
44
|
-
clean();
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
Object.keys(raceDescription).forEach((candidateName) => {
|
|
48
|
-
const register = raceDescription[candidateName];
|
|
49
|
-
const returnValue = register((data) => {
|
|
50
|
-
if (status !== "racing") {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
status = "done";
|
|
54
|
-
clean();
|
|
55
|
-
winnerCallback({
|
|
56
|
-
name: candidateName,
|
|
57
|
-
data,
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
if (typeof returnValue === "function") {
|
|
61
|
-
cleanCallbacks.push(returnValue);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
return cancel;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
24
|
const createCallbackListNotifiedOnce = () => {
|
|
69
25
|
let callbacks = [];
|
|
70
26
|
let status = "waiting";
|
|
@@ -177,6 +133,50 @@ const emitCallbackDuplicationWarning = () => {
|
|
|
177
133
|
|
|
178
134
|
const removeNoop = () => {};
|
|
179
135
|
|
|
136
|
+
/*
|
|
137
|
+
* See callback_race.md
|
|
138
|
+
*/
|
|
139
|
+
|
|
140
|
+
const raceCallbacks = (raceDescription, winnerCallback) => {
|
|
141
|
+
let cleanCallbacks = [];
|
|
142
|
+
let status = "racing";
|
|
143
|
+
|
|
144
|
+
const clean = () => {
|
|
145
|
+
cleanCallbacks.forEach((clean) => {
|
|
146
|
+
clean();
|
|
147
|
+
});
|
|
148
|
+
cleanCallbacks = null;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const cancel = () => {
|
|
152
|
+
if (status !== "racing") {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
status = "cancelled";
|
|
156
|
+
clean();
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
Object.keys(raceDescription).forEach((candidateName) => {
|
|
160
|
+
const register = raceDescription[candidateName];
|
|
161
|
+
const returnValue = register((data) => {
|
|
162
|
+
if (status !== "racing") {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
status = "done";
|
|
166
|
+
clean();
|
|
167
|
+
winnerCallback({
|
|
168
|
+
name: candidateName,
|
|
169
|
+
data,
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
if (typeof returnValue === "function") {
|
|
173
|
+
cleanCallbacks.push(returnValue);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return cancel;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
180
|
/*
|
|
181
181
|
* https://github.com/whatwg/dom/issues/920
|
|
182
182
|
*/
|
|
@@ -3097,262 +3097,305 @@ process.platform === "win32";
|
|
|
3097
3097
|
|
|
3098
3098
|
process.platform === "linux";
|
|
3099
3099
|
|
|
3100
|
-
|
|
3101
|
-
|
|
3100
|
+
const normalizeFileByFileCoveragePaths = (
|
|
3101
|
+
fileByFileCoverage,
|
|
3102
|
+
rootDirectoryUrl,
|
|
3103
|
+
) => {
|
|
3104
|
+
const fileByFileNormalized = {};
|
|
3105
|
+
Object.keys(fileByFileCoverage).forEach((key) => {
|
|
3106
|
+
const fileCoverage = fileByFileCoverage[key];
|
|
3107
|
+
const { path } = fileCoverage;
|
|
3108
|
+
const url = isFileSystemPath(path)
|
|
3109
|
+
? fileSystemPathToUrl(path)
|
|
3110
|
+
: new URL(path, rootDirectoryUrl).href;
|
|
3111
|
+
const relativeUrl = urlToRelativeUrl(url, rootDirectoryUrl);
|
|
3112
|
+
fileByFileNormalized[`./${relativeUrl}`] = {
|
|
3113
|
+
...fileCoverage,
|
|
3114
|
+
path: `./${relativeUrl}`,
|
|
3115
|
+
};
|
|
3116
|
+
});
|
|
3117
|
+
return fileByFileNormalized;
|
|
3118
|
+
};
|
|
3102
3119
|
|
|
3103
|
-
const
|
|
3104
|
-
let previousCpuArray = cpus();
|
|
3105
|
-
let previousMs = Date.now();
|
|
3106
|
-
let previousCpuUsage = cpuUsage();
|
|
3120
|
+
const importWithRequire = createRequire(import.meta.url);
|
|
3107
3121
|
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
active: 0,
|
|
3123
|
-
system: 0,
|
|
3124
|
-
user: 0,
|
|
3125
|
-
};
|
|
3122
|
+
const composeTwoFileByFileIstanbulCoverages = (
|
|
3123
|
+
firstFileByFileIstanbulCoverage,
|
|
3124
|
+
secondFileByFileIstanbulCoverage,
|
|
3125
|
+
) => {
|
|
3126
|
+
const fileByFileIstanbulCoverage = {};
|
|
3127
|
+
Object.keys(firstFileByFileIstanbulCoverage).forEach((key) => {
|
|
3128
|
+
fileByFileIstanbulCoverage[key] = firstFileByFileIstanbulCoverage[key];
|
|
3129
|
+
});
|
|
3130
|
+
Object.keys(secondFileByFileIstanbulCoverage).forEach((key) => {
|
|
3131
|
+
const firstCoverage = firstFileByFileIstanbulCoverage[key];
|
|
3132
|
+
const secondCoverage = secondFileByFileIstanbulCoverage[key];
|
|
3133
|
+
fileByFileIstanbulCoverage[key] = firstCoverage
|
|
3134
|
+
? merge(firstCoverage, secondCoverage)
|
|
3135
|
+
: secondCoverage;
|
|
3126
3136
|
});
|
|
3127
3137
|
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
let cpuArray = cpus();
|
|
3131
|
-
const ms = Date.now();
|
|
3132
|
-
const ellapsedMs = ms - previousMs;
|
|
3133
|
-
const cpuUsageSampleArray = [];
|
|
3134
|
-
let overallSystemMs = 0;
|
|
3135
|
-
let overallUserMs = 0;
|
|
3136
|
-
let overallInactiveMs = 0;
|
|
3137
|
-
let overallActiveMs = 0;
|
|
3138
|
-
let overallMsEllapsed = 0;
|
|
3139
|
-
let index = 0;
|
|
3140
|
-
for (const cpu of cpuArray) {
|
|
3141
|
-
const previousCpuTimes = previousCpuArray[index].times;
|
|
3142
|
-
const cpuTimes = cpu.times;
|
|
3143
|
-
const systemMs = cpuTimes.sys - previousCpuTimes.sys;
|
|
3144
|
-
const userMs = cpuTimes.user - previousCpuTimes.user;
|
|
3145
|
-
const activeMs = systemMs + userMs;
|
|
3146
|
-
const inactiveMs = ellapsedMs - activeMs;
|
|
3147
|
-
const cpuUsageSample = {
|
|
3148
|
-
inactive: inactiveMs / ellapsedMs,
|
|
3149
|
-
active: activeMs / ellapsedMs,
|
|
3150
|
-
system: systemMs / ellapsedMs,
|
|
3151
|
-
user: userMs / ellapsedMs,
|
|
3152
|
-
};
|
|
3153
|
-
cpuUsageSampleArray.push(cpuUsageSample);
|
|
3138
|
+
return fileByFileIstanbulCoverage;
|
|
3139
|
+
};
|
|
3154
3140
|
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
const overallUsageSample = {
|
|
3163
|
-
inactive: overallInactiveMs / overallMsEllapsed,
|
|
3164
|
-
active: overallActiveMs / overallMsEllapsed,
|
|
3165
|
-
system: overallSystemMs / overallMsEllapsed,
|
|
3166
|
-
user: overallUserMs / overallMsEllapsed,
|
|
3167
|
-
};
|
|
3168
|
-
previousCpuArray = cpuArray;
|
|
3169
|
-
previousMs = ms;
|
|
3141
|
+
const merge = (firstIstanbulCoverage, secondIstanbulCoverage) => {
|
|
3142
|
+
const { createFileCoverage } = importWithRequire("istanbul-lib-coverage");
|
|
3143
|
+
const istanbulFileCoverageObject = createFileCoverage(firstIstanbulCoverage);
|
|
3144
|
+
istanbulFileCoverageObject.merge(secondIstanbulCoverage);
|
|
3145
|
+
const istanbulCoverage = istanbulFileCoverageObject.toJSON();
|
|
3146
|
+
return istanbulCoverage;
|
|
3147
|
+
};
|
|
3170
3148
|
|
|
3171
|
-
|
|
3172
|
-
const thisProcessSystemMs = Math.round(
|
|
3173
|
-
(processCpuUsage.system - previousCpuUsage.system) / 1000,
|
|
3174
|
-
);
|
|
3175
|
-
const thisProcessUserMs = Math.round(
|
|
3176
|
-
(processCpuUsage.user - previousCpuUsage.user) / 1000,
|
|
3177
|
-
);
|
|
3178
|
-
previousCpuUsage = processCpuUsage;
|
|
3149
|
+
// https://github.com/istanbuljs/babel-plugin-istanbul/blob/321740f7b25d803f881466ea819d870f7ed6a254/src/index.js
|
|
3179
3150
|
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
inactive: thisProcessInactiveMs / overallMsEllapsed,
|
|
3184
|
-
active: thisProcessActiveMs / overallMsEllapsed,
|
|
3185
|
-
system: thisProcessSystemMs / overallMsEllapsed,
|
|
3186
|
-
user: thisProcessUserMs / overallMsEllapsed,
|
|
3187
|
-
};
|
|
3188
|
-
samples.push({
|
|
3189
|
-
cpuUsageSampleArray,
|
|
3190
|
-
overallUsageSample,
|
|
3191
|
-
thisProcessSample,
|
|
3192
|
-
});
|
|
3193
|
-
if (samples.length === 10) {
|
|
3194
|
-
{
|
|
3195
|
-
let index = 0;
|
|
3196
|
-
for (const detail of details) {
|
|
3197
|
-
let systemSum = 0;
|
|
3198
|
-
let userSum = 0;
|
|
3199
|
-
let inactiveSum = 0;
|
|
3200
|
-
let activeSum = 0;
|
|
3201
|
-
for (const sample of samples) {
|
|
3202
|
-
const { cpuUsageSampleArray } = sample;
|
|
3203
|
-
const cpuUsageSample = cpuUsageSampleArray[index];
|
|
3204
|
-
inactiveSum += cpuUsageSample.inactive;
|
|
3205
|
-
activeSum += cpuUsageSample.active;
|
|
3206
|
-
systemSum += cpuUsageSample.system;
|
|
3207
|
-
userSum += cpuUsageSample.user;
|
|
3208
|
-
}
|
|
3209
|
-
Object.assign(detail, {
|
|
3210
|
-
inactive: inactiveSum / samples.length,
|
|
3211
|
-
active: activeSum / samples.length,
|
|
3212
|
-
system: systemSum / samples.length,
|
|
3213
|
-
user: userSum / samples.length,
|
|
3214
|
-
});
|
|
3215
|
-
index++;
|
|
3216
|
-
}
|
|
3217
|
-
}
|
|
3218
|
-
{
|
|
3219
|
-
let overallSystemSum = 0;
|
|
3220
|
-
let overallUserSum = 0;
|
|
3221
|
-
let overallInactiveSum = 0;
|
|
3222
|
-
let overallActiveSum = 0;
|
|
3223
|
-
for (const sample of samples) {
|
|
3224
|
-
const { overallUsageSample } = sample;
|
|
3225
|
-
overallSystemSum += overallUsageSample.system;
|
|
3226
|
-
overallUserSum += overallUsageSample.user;
|
|
3227
|
-
overallInactiveSum += overallUsageSample.inactive;
|
|
3228
|
-
overallActiveSum += overallUsageSample.active;
|
|
3229
|
-
}
|
|
3230
|
-
Object.assign(overall, {
|
|
3231
|
-
inactive: overallInactiveSum / samples.length,
|
|
3232
|
-
active: overallActiveSum / samples.length,
|
|
3233
|
-
system: overallSystemSum / samples.length,
|
|
3234
|
-
user: overallUserSum / samples.length,
|
|
3235
|
-
});
|
|
3236
|
-
}
|
|
3237
|
-
{
|
|
3238
|
-
let thisProcessSystemSum = 0;
|
|
3239
|
-
let thisProcessUserSum = 0;
|
|
3240
|
-
let thisProcessInactiveSum = 0;
|
|
3241
|
-
let thisProcessActiveSum = 0;
|
|
3242
|
-
for (const sample of samples) {
|
|
3243
|
-
const { thisProcessSample } = sample;
|
|
3244
|
-
thisProcessSystemSum += thisProcessSample.system;
|
|
3245
|
-
thisProcessUserSum += thisProcessSample.user;
|
|
3246
|
-
thisProcessInactiveSum += thisProcessSample.inactive;
|
|
3247
|
-
thisProcessActiveSum += thisProcessSample.active;
|
|
3248
|
-
}
|
|
3249
|
-
Object.assign(thisProcess, {
|
|
3250
|
-
inactive: thisProcessInactiveSum / samples.length,
|
|
3251
|
-
active: thisProcessActiveSum / samples.length,
|
|
3252
|
-
system: thisProcessSystemSum / samples.length,
|
|
3253
|
-
user: thisProcessUserSum / samples.length,
|
|
3254
|
-
});
|
|
3255
|
-
}
|
|
3256
|
-
samples.length = 0;
|
|
3257
|
-
}
|
|
3258
|
-
}, 15);
|
|
3259
|
-
interval.unref();
|
|
3151
|
+
const babelPluginInstrument = (api, { useInlineSourceMaps = false }) => {
|
|
3152
|
+
const { programVisitor } = importWithRequire("istanbul-lib-instrument");
|
|
3153
|
+
const { types } = api;
|
|
3260
3154
|
|
|
3261
3155
|
return {
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3156
|
+
name: "transform-instrument",
|
|
3157
|
+
visitor: {
|
|
3158
|
+
Program: {
|
|
3159
|
+
enter(path, state) {
|
|
3160
|
+
const { file } = state;
|
|
3161
|
+
const { opts } = file;
|
|
3162
|
+
let inputSourceMap;
|
|
3163
|
+
if (useInlineSourceMaps) {
|
|
3164
|
+
// https://github.com/istanbuljs/babel-plugin-istanbul/commit/a9e15643d249a2985e4387e4308022053b2cd0ad#diff-1fdf421c05c1140f6d71444ea2b27638R65
|
|
3165
|
+
inputSourceMap =
|
|
3166
|
+
opts.inputSourceMap || file.inputMap
|
|
3167
|
+
? file.inputMap.sourcemap
|
|
3168
|
+
: null;
|
|
3169
|
+
} else {
|
|
3170
|
+
inputSourceMap = opts.inputSourceMap;
|
|
3171
|
+
}
|
|
3172
|
+
const __dv__ = programVisitor(
|
|
3173
|
+
types,
|
|
3174
|
+
opts.filenameRelative || opts.filename,
|
|
3175
|
+
{
|
|
3176
|
+
coverageVariable: "__coverage__",
|
|
3177
|
+
inputSourceMap,
|
|
3178
|
+
},
|
|
3179
|
+
);
|
|
3180
|
+
__dv__.enter(path);
|
|
3181
|
+
file.metadata.__dv__ = __dv__;
|
|
3182
|
+
},
|
|
3183
|
+
|
|
3184
|
+
exit(path, state) {
|
|
3185
|
+
const { __dv__ } = state.file.metadata;
|
|
3186
|
+
if (!__dv__) {
|
|
3187
|
+
return;
|
|
3188
|
+
}
|
|
3189
|
+
const object = __dv__.exit(path);
|
|
3190
|
+
// object got two properties: fileCoverage and sourceMappingURL
|
|
3191
|
+
state.file.metadata.coverage = object.fileCoverage;
|
|
3192
|
+
},
|
|
3193
|
+
},
|
|
3267
3194
|
},
|
|
3268
3195
|
};
|
|
3269
3196
|
};
|
|
3270
3197
|
|
|
3271
|
-
const
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3198
|
+
const relativeUrlToEmptyCoverage = async (
|
|
3199
|
+
relativeUrl,
|
|
3200
|
+
{ signal, rootDirectoryUrl },
|
|
3201
|
+
) => {
|
|
3202
|
+
const operation = Abort.startOperation();
|
|
3203
|
+
operation.addAbortSignal(signal);
|
|
3277
3204
|
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
}
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3205
|
+
try {
|
|
3206
|
+
const fileUrl = resolveUrl(relativeUrl, rootDirectoryUrl);
|
|
3207
|
+
const content = await readFile(fileUrl, { as: "string" });
|
|
3208
|
+
|
|
3209
|
+
operation.throwIfAborted();
|
|
3210
|
+
const { metadata } = await applyBabelPlugins({
|
|
3211
|
+
babelPlugins: [babelPluginInstrument],
|
|
3212
|
+
input: content,
|
|
3213
|
+
inputIsJsModule: true,
|
|
3214
|
+
inputUrl: fileUrl,
|
|
3215
|
+
});
|
|
3216
|
+
const { coverage } = metadata;
|
|
3217
|
+
if (!coverage) {
|
|
3218
|
+
throw new Error(`missing coverage for file`);
|
|
3292
3219
|
}
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
value();
|
|
3304
|
-
}
|
|
3305
|
-
afterIndex++;
|
|
3220
|
+
// https://github.com/gotwarlost/istanbul/blob/bc84c315271a5dd4d39bcefc5925cfb61a3d174a/lib/command/common/run-with-cover.js#L229
|
|
3221
|
+
Object.keys(coverage.s).forEach(function (key) {
|
|
3222
|
+
coverage.s[key] = 0;
|
|
3223
|
+
});
|
|
3224
|
+
return coverage;
|
|
3225
|
+
} catch (e) {
|
|
3226
|
+
if (e && e.code === "PARSE_ERROR") {
|
|
3227
|
+
// return an empty coverage for that file when
|
|
3228
|
+
// it contains a syntax error
|
|
3229
|
+
return createEmptyCoverage(relativeUrl);
|
|
3306
3230
|
}
|
|
3307
|
-
|
|
3308
|
-
|
|
3231
|
+
throw e;
|
|
3232
|
+
} finally {
|
|
3233
|
+
await operation.end();
|
|
3234
|
+
}
|
|
3309
3235
|
};
|
|
3310
3236
|
|
|
3311
|
-
const
|
|
3312
|
-
|
|
3313
|
-
|
|
3237
|
+
const createEmptyCoverage = (relativeUrl) => {
|
|
3238
|
+
const { createFileCoverage } = importWithRequire("istanbul-lib-coverage");
|
|
3239
|
+
return createFileCoverage(relativeUrl).toJSON();
|
|
3240
|
+
};
|
|
3241
|
+
|
|
3242
|
+
const listRelativeFileUrlToCover = async ({
|
|
3314
3243
|
signal,
|
|
3315
|
-
|
|
3316
|
-
|
|
3244
|
+
rootDirectoryUrl,
|
|
3245
|
+
coverageInclude,
|
|
3317
3246
|
}) => {
|
|
3318
|
-
const
|
|
3319
|
-
|
|
3320
|
-
|
|
3247
|
+
const matchingFileResultArray = await collectFiles({
|
|
3248
|
+
signal,
|
|
3249
|
+
directoryUrl: rootDirectoryUrl,
|
|
3250
|
+
associations: { cover: coverageInclude },
|
|
3251
|
+
predicate: ({ cover }) => cover,
|
|
3252
|
+
});
|
|
3253
|
+
return matchingFileResultArray.map(({ relativeUrl }) => relativeUrl);
|
|
3254
|
+
};
|
|
3321
3255
|
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
return dirContent;
|
|
3339
|
-
};
|
|
3256
|
+
const getMissingFileByFileCoverage = async ({
|
|
3257
|
+
signal,
|
|
3258
|
+
rootDirectoryUrl,
|
|
3259
|
+
coverageInclude,
|
|
3260
|
+
fileByFileCoverage,
|
|
3261
|
+
}) => {
|
|
3262
|
+
const relativeUrlsToCover = await listRelativeFileUrlToCover({
|
|
3263
|
+
signal,
|
|
3264
|
+
rootDirectoryUrl,
|
|
3265
|
+
coverageInclude,
|
|
3266
|
+
});
|
|
3267
|
+
const relativeUrlsMissing = relativeUrlsToCover.filter((relativeUrlToCover) =>
|
|
3268
|
+
Object.keys(fileByFileCoverage).every((key) => {
|
|
3269
|
+
return key !== `./${relativeUrlToCover}`;
|
|
3270
|
+
}),
|
|
3271
|
+
);
|
|
3340
3272
|
|
|
3341
|
-
|
|
3273
|
+
const operation = Abort.startOperation();
|
|
3274
|
+
operation.addAbortSignal(signal);
|
|
3275
|
+
const missingFileByFileCoverage = {};
|
|
3276
|
+
await relativeUrlsMissing.reduce(async (previous, relativeUrlMissing) => {
|
|
3342
3277
|
operation.throwIfAborted();
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3278
|
+
await previous;
|
|
3279
|
+
await operation.withSignal(async (signal) => {
|
|
3280
|
+
const emptyCoverage = await relativeUrlToEmptyCoverage(
|
|
3281
|
+
relativeUrlMissing,
|
|
3282
|
+
{
|
|
3283
|
+
signal,
|
|
3284
|
+
rootDirectoryUrl,
|
|
3285
|
+
},
|
|
3286
|
+
);
|
|
3287
|
+
missingFileByFileCoverage[`./${relativeUrlMissing}`] = emptyCoverage;
|
|
3288
|
+
});
|
|
3289
|
+
}, Promise.resolve());
|
|
3290
|
+
return missingFileByFileCoverage;
|
|
3291
|
+
};
|
|
3349
3292
|
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3293
|
+
const composeV8AndIstanbul = (
|
|
3294
|
+
v8FileByFileCoverage,
|
|
3295
|
+
istanbulFileByFileCoverage,
|
|
3296
|
+
{ warn, v8ConflictWarning },
|
|
3297
|
+
) => {
|
|
3298
|
+
const fileByFileCoverage = {};
|
|
3299
|
+
const v8Files = Object.keys(v8FileByFileCoverage);
|
|
3300
|
+
const istanbulFiles = Object.keys(istanbulFileByFileCoverage);
|
|
3353
3301
|
|
|
3354
|
-
|
|
3355
|
-
|
|
3302
|
+
v8Files.forEach((key) => {
|
|
3303
|
+
fileByFileCoverage[key] = v8FileByFileCoverage[key];
|
|
3304
|
+
});
|
|
3305
|
+
istanbulFiles.forEach((key) => {
|
|
3306
|
+
const v8Coverage = v8FileByFileCoverage[key];
|
|
3307
|
+
if (v8Coverage) {
|
|
3308
|
+
if (v8ConflictWarning) {
|
|
3309
|
+
warn({
|
|
3310
|
+
code: "V8_COVERAGE_CONFLICT",
|
|
3311
|
+
message: createDetailedMessage(
|
|
3312
|
+
`Coverage conflict on "${key}", found two coverage that cannot be merged together: v8 and istanbul. The istanbul coverage will be ignored.`,
|
|
3313
|
+
{
|
|
3314
|
+
"details": `This happens when a file is executed on a runtime using v8 coverage (node or chromium) and on runtime using istanbul coverage (firefox or webkit)`,
|
|
3315
|
+
"suggestion":
|
|
3316
|
+
"disable this warning with coverage.v8ConflictWarning: false",
|
|
3317
|
+
"suggestion 2": `force coverage using istanbul with coverage.methodForBrowsers: "istanbul"`,
|
|
3318
|
+
},
|
|
3319
|
+
),
|
|
3320
|
+
});
|
|
3321
|
+
}
|
|
3322
|
+
fileByFileCoverage[key] = v8Coverage;
|
|
3323
|
+
} else {
|
|
3324
|
+
fileByFileCoverage[key] = istanbulFileByFileCoverage[key];
|
|
3325
|
+
}
|
|
3326
|
+
});
|
|
3327
|
+
|
|
3328
|
+
return fileByFileCoverage;
|
|
3329
|
+
};
|
|
3330
|
+
|
|
3331
|
+
const composeTwoV8Coverages = (firstV8Coverage, secondV8Coverage) => {
|
|
3332
|
+
if (secondV8Coverage.result.length === 0) {
|
|
3333
|
+
return firstV8Coverage;
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
// eslint-disable-next-line import/no-unresolved
|
|
3337
|
+
const { mergeProcessCovs } = importWithRequire("@c88/v8-coverage");
|
|
3338
|
+
// "mergeProcessCovs" do not preserves source-map-cache during the merge
|
|
3339
|
+
// so we store sourcemap cache now
|
|
3340
|
+
const sourceMapCache = {};
|
|
3341
|
+
const visit = (coverageReport) => {
|
|
3342
|
+
if (coverageReport["source-map-cache"]) {
|
|
3343
|
+
Object.assign(sourceMapCache, coverageReport["source-map-cache"]);
|
|
3344
|
+
}
|
|
3345
|
+
};
|
|
3346
|
+
visit(firstV8Coverage);
|
|
3347
|
+
visit(secondV8Coverage);
|
|
3348
|
+
const v8Coverage = mergeProcessCovs([firstV8Coverage, secondV8Coverage]);
|
|
3349
|
+
v8Coverage["source-map-cache"] = sourceMapCache;
|
|
3350
|
+
|
|
3351
|
+
return v8Coverage;
|
|
3352
|
+
};
|
|
3353
|
+
|
|
3354
|
+
const readNodeV8CoverageDirectory = async ({
|
|
3355
|
+
logger,
|
|
3356
|
+
warn,
|
|
3357
|
+
signal,
|
|
3358
|
+
onV8Coverage,
|
|
3359
|
+
maxMsWaitingForNodeToWriteCoverageFile = 2000,
|
|
3360
|
+
}) => {
|
|
3361
|
+
const NODE_V8_COVERAGE = process.env.NODE_V8_COVERAGE;
|
|
3362
|
+
const operation = Abort.startOperation();
|
|
3363
|
+
operation.addAbortSignal(signal);
|
|
3364
|
+
|
|
3365
|
+
let timeSpentTrying = 0;
|
|
3366
|
+
const tryReadDirectory = async () => {
|
|
3367
|
+
const dirContent = readdirSync(NODE_V8_COVERAGE);
|
|
3368
|
+
if (dirContent.length > 0) {
|
|
3369
|
+
return dirContent;
|
|
3370
|
+
}
|
|
3371
|
+
if (timeSpentTrying < maxMsWaitingForNodeToWriteCoverageFile) {
|
|
3372
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
3373
|
+
timeSpentTrying += 200;
|
|
3374
|
+
logger.debug("retry to read coverage directory");
|
|
3375
|
+
return tryReadDirectory();
|
|
3376
|
+
}
|
|
3377
|
+
warn({
|
|
3378
|
+
code: "V8_COVERAGE_EMPTY",
|
|
3379
|
+
message: `v8 coverage directory is empty at ${NODE_V8_COVERAGE}`,
|
|
3380
|
+
});
|
|
3381
|
+
return dirContent;
|
|
3382
|
+
};
|
|
3383
|
+
|
|
3384
|
+
try {
|
|
3385
|
+
operation.throwIfAborted();
|
|
3386
|
+
const dirContent = await tryReadDirectory();
|
|
3387
|
+
|
|
3388
|
+
const coverageDirectoryUrl = assertAndNormalizeDirectoryUrl(
|
|
3389
|
+
NODE_V8_COVERAGE,
|
|
3390
|
+
"NODE_V8_COVERAGE",
|
|
3391
|
+
);
|
|
3392
|
+
|
|
3393
|
+
await dirContent.reduce(async (previous, dirEntry) => {
|
|
3394
|
+
operation.throwIfAborted();
|
|
3395
|
+
await previous;
|
|
3396
|
+
|
|
3397
|
+
const dirEntryUrl = new URL(dirEntry, coverageDirectoryUrl);
|
|
3398
|
+
const tryReadJsonFile = async () => {
|
|
3356
3399
|
const fileContent = String(readFileSync(dirEntryUrl));
|
|
3357
3400
|
if (fileContent === "") {
|
|
3358
3401
|
if (timeSpentTrying < maxMsWaitingForNodeToWriteCoverageFile) {
|
|
@@ -3393,58 +3436,6 @@ const readNodeV8CoverageDirectory = async ({
|
|
|
3393
3436
|
}
|
|
3394
3437
|
};
|
|
3395
3438
|
|
|
3396
|
-
const importWithRequire = createRequire(import.meta.url);
|
|
3397
|
-
|
|
3398
|
-
const composeTwoV8Coverages = (firstV8Coverage, secondV8Coverage) => {
|
|
3399
|
-
if (secondV8Coverage.result.length === 0) {
|
|
3400
|
-
return firstV8Coverage;
|
|
3401
|
-
}
|
|
3402
|
-
|
|
3403
|
-
// eslint-disable-next-line import/no-unresolved
|
|
3404
|
-
const { mergeProcessCovs } = importWithRequire("@c88/v8-coverage");
|
|
3405
|
-
// "mergeProcessCovs" do not preserves source-map-cache during the merge
|
|
3406
|
-
// so we store sourcemap cache now
|
|
3407
|
-
const sourceMapCache = {};
|
|
3408
|
-
const visit = (coverageReport) => {
|
|
3409
|
-
if (coverageReport["source-map-cache"]) {
|
|
3410
|
-
Object.assign(sourceMapCache, coverageReport["source-map-cache"]);
|
|
3411
|
-
}
|
|
3412
|
-
};
|
|
3413
|
-
visit(firstV8Coverage);
|
|
3414
|
-
visit(secondV8Coverage);
|
|
3415
|
-
const v8Coverage = mergeProcessCovs([firstV8Coverage, secondV8Coverage]);
|
|
3416
|
-
v8Coverage["source-map-cache"] = sourceMapCache;
|
|
3417
|
-
|
|
3418
|
-
return v8Coverage;
|
|
3419
|
-
};
|
|
3420
|
-
|
|
3421
|
-
const composeTwoFileByFileIstanbulCoverages = (
|
|
3422
|
-
firstFileByFileIstanbulCoverage,
|
|
3423
|
-
secondFileByFileIstanbulCoverage,
|
|
3424
|
-
) => {
|
|
3425
|
-
const fileByFileIstanbulCoverage = {};
|
|
3426
|
-
Object.keys(firstFileByFileIstanbulCoverage).forEach((key) => {
|
|
3427
|
-
fileByFileIstanbulCoverage[key] = firstFileByFileIstanbulCoverage[key];
|
|
3428
|
-
});
|
|
3429
|
-
Object.keys(secondFileByFileIstanbulCoverage).forEach((key) => {
|
|
3430
|
-
const firstCoverage = firstFileByFileIstanbulCoverage[key];
|
|
3431
|
-
const secondCoverage = secondFileByFileIstanbulCoverage[key];
|
|
3432
|
-
fileByFileIstanbulCoverage[key] = firstCoverage
|
|
3433
|
-
? merge(firstCoverage, secondCoverage)
|
|
3434
|
-
: secondCoverage;
|
|
3435
|
-
});
|
|
3436
|
-
|
|
3437
|
-
return fileByFileIstanbulCoverage;
|
|
3438
|
-
};
|
|
3439
|
-
|
|
3440
|
-
const merge = (firstIstanbulCoverage, secondIstanbulCoverage) => {
|
|
3441
|
-
const { createFileCoverage } = importWithRequire("istanbul-lib-coverage");
|
|
3442
|
-
const istanbulFileCoverageObject = createFileCoverage(firstIstanbulCoverage);
|
|
3443
|
-
istanbulFileCoverageObject.merge(secondIstanbulCoverage);
|
|
3444
|
-
const istanbulCoverage = istanbulFileCoverageObject.toJSON();
|
|
3445
|
-
return istanbulCoverage;
|
|
3446
|
-
};
|
|
3447
|
-
|
|
3448
3439
|
const v8CoverageToIstanbul = async (v8Coverage, { signal }) => {
|
|
3449
3440
|
const operation = Abort.startOperation();
|
|
3450
3441
|
operation.addAbortSignal(signal);
|
|
@@ -3539,246 +3530,44 @@ const sourcesFromLineLengths = (lineLengths) => {
|
|
|
3539
3530
|
return source;
|
|
3540
3531
|
};
|
|
3541
3532
|
|
|
3542
|
-
const
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
{ warn, v8ConflictWarning },
|
|
3546
|
-
) => {
|
|
3547
|
-
const fileByFileCoverage = {};
|
|
3548
|
-
const v8Files = Object.keys(v8FileByFileCoverage);
|
|
3549
|
-
const istanbulFiles = Object.keys(istanbulFileByFileCoverage);
|
|
3550
|
-
|
|
3551
|
-
v8Files.forEach((key) => {
|
|
3552
|
-
fileByFileCoverage[key] = v8FileByFileCoverage[key];
|
|
3553
|
-
});
|
|
3554
|
-
istanbulFiles.forEach((key) => {
|
|
3555
|
-
const v8Coverage = v8FileByFileCoverage[key];
|
|
3556
|
-
if (v8Coverage) {
|
|
3557
|
-
if (v8ConflictWarning) {
|
|
3558
|
-
warn({
|
|
3559
|
-
code: "V8_COVERAGE_CONFLICT",
|
|
3560
|
-
message: createDetailedMessage(
|
|
3561
|
-
`Coverage conflict on "${key}", found two coverage that cannot be merged together: v8 and istanbul. The istanbul coverage will be ignored.`,
|
|
3562
|
-
{
|
|
3563
|
-
"details": `This happens when a file is executed on a runtime using v8 coverage (node or chromium) and on runtime using istanbul coverage (firefox or webkit)`,
|
|
3564
|
-
"suggestion":
|
|
3565
|
-
"disable this warning with coverage.v8ConflictWarning: false",
|
|
3566
|
-
"suggestion 2": `force coverage using istanbul with coverage.methodForBrowsers: "istanbul"`,
|
|
3567
|
-
},
|
|
3568
|
-
),
|
|
3569
|
-
});
|
|
3570
|
-
}
|
|
3571
|
-
fileByFileCoverage[key] = v8Coverage;
|
|
3572
|
-
} else {
|
|
3573
|
-
fileByFileCoverage[key] = istanbulFileByFileCoverage[key];
|
|
3574
|
-
}
|
|
3575
|
-
});
|
|
3576
|
-
|
|
3577
|
-
return fileByFileCoverage;
|
|
3578
|
-
};
|
|
3579
|
-
|
|
3580
|
-
const normalizeFileByFileCoveragePaths = (
|
|
3581
|
-
fileByFileCoverage,
|
|
3582
|
-
rootDirectoryUrl,
|
|
3533
|
+
const generateCoverage = async (
|
|
3534
|
+
testPlanResult,
|
|
3535
|
+
{ signal, logger, warn, rootDirectoryUrl, coverage },
|
|
3583
3536
|
) => {
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
};
|
|
3596
|
-
});
|
|
3597
|
-
return fileByFileNormalized;
|
|
3598
|
-
};
|
|
3537
|
+
// collect v8 and istanbul coverage from executions
|
|
3538
|
+
let { v8Coverage, fileByFileIstanbulCoverage } =
|
|
3539
|
+
await getCoverageFromTestPlanResults(testPlanResult.results, {
|
|
3540
|
+
signal,
|
|
3541
|
+
onMissing: ({ file, executionResult, executionName }) => {
|
|
3542
|
+
// several reasons not to have coverage here:
|
|
3543
|
+
// 1. the file we executed did not import an instrumented file.
|
|
3544
|
+
// - a test file without import
|
|
3545
|
+
// - a test file importing only file excluded from coverage
|
|
3546
|
+
// - a coverDescription badly configured so that we don't realize
|
|
3547
|
+
// a file should be covered
|
|
3599
3548
|
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
}) => {
|
|
3605
|
-
const matchingFileResultArray = await collectFiles({
|
|
3606
|
-
signal,
|
|
3607
|
-
directoryUrl: rootDirectoryUrl,
|
|
3608
|
-
associations: { cover: coverageInclude },
|
|
3609
|
-
predicate: ({ cover }) => cover,
|
|
3610
|
-
});
|
|
3611
|
-
return matchingFileResultArray.map(({ relativeUrl }) => relativeUrl);
|
|
3612
|
-
};
|
|
3549
|
+
// 2. the file we wanted to executed timedout
|
|
3550
|
+
// - infinite loop
|
|
3551
|
+
// - too extensive operation
|
|
3552
|
+
// - a badly configured or too low allocatedMs for that execution.
|
|
3613
3553
|
|
|
3614
|
-
//
|
|
3554
|
+
// 3. the file we wanted to execute contains syntax-error
|
|
3615
3555
|
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
opts.inputSourceMap || file.inputMap
|
|
3632
|
-
? file.inputMap.sourcemap
|
|
3633
|
-
: null;
|
|
3634
|
-
} else {
|
|
3635
|
-
inputSourceMap = opts.inputSourceMap;
|
|
3636
|
-
}
|
|
3637
|
-
const __dv__ = programVisitor(
|
|
3638
|
-
types,
|
|
3639
|
-
opts.filenameRelative || opts.filename,
|
|
3640
|
-
{
|
|
3641
|
-
coverageVariable: "__coverage__",
|
|
3642
|
-
inputSourceMap,
|
|
3643
|
-
},
|
|
3644
|
-
);
|
|
3645
|
-
__dv__.enter(path);
|
|
3646
|
-
file.metadata.__dv__ = __dv__;
|
|
3647
|
-
},
|
|
3648
|
-
|
|
3649
|
-
exit(path, state) {
|
|
3650
|
-
const { __dv__ } = state.file.metadata;
|
|
3651
|
-
if (!__dv__) {
|
|
3652
|
-
return;
|
|
3653
|
-
}
|
|
3654
|
-
const object = __dv__.exit(path);
|
|
3655
|
-
// object got two properties: fileCoverage and sourceMappingURL
|
|
3656
|
-
state.file.metadata.coverage = object.fileCoverage;
|
|
3657
|
-
},
|
|
3658
|
-
},
|
|
3659
|
-
},
|
|
3660
|
-
};
|
|
3661
|
-
};
|
|
3662
|
-
|
|
3663
|
-
const relativeUrlToEmptyCoverage = async (
|
|
3664
|
-
relativeUrl,
|
|
3665
|
-
{ signal, rootDirectoryUrl },
|
|
3666
|
-
) => {
|
|
3667
|
-
const operation = Abort.startOperation();
|
|
3668
|
-
operation.addAbortSignal(signal);
|
|
3669
|
-
|
|
3670
|
-
try {
|
|
3671
|
-
const fileUrl = resolveUrl(relativeUrl, rootDirectoryUrl);
|
|
3672
|
-
const content = await readFile(fileUrl, { as: "string" });
|
|
3673
|
-
|
|
3674
|
-
operation.throwIfAborted();
|
|
3675
|
-
const { metadata } = await applyBabelPlugins({
|
|
3676
|
-
babelPlugins: [babelPluginInstrument],
|
|
3677
|
-
input: content,
|
|
3678
|
-
inputIsJsModule: true,
|
|
3679
|
-
inputUrl: fileUrl,
|
|
3680
|
-
});
|
|
3681
|
-
const { coverage } = metadata;
|
|
3682
|
-
if (!coverage) {
|
|
3683
|
-
throw new Error(`missing coverage for file`);
|
|
3684
|
-
}
|
|
3685
|
-
// https://github.com/gotwarlost/istanbul/blob/bc84c315271a5dd4d39bcefc5925cfb61a3d174a/lib/command/common/run-with-cover.js#L229
|
|
3686
|
-
Object.keys(coverage.s).forEach(function (key) {
|
|
3687
|
-
coverage.s[key] = 0;
|
|
3688
|
-
});
|
|
3689
|
-
return coverage;
|
|
3690
|
-
} catch (e) {
|
|
3691
|
-
if (e && e.code === "PARSE_ERROR") {
|
|
3692
|
-
// return an empty coverage for that file when
|
|
3693
|
-
// it contains a syntax error
|
|
3694
|
-
return createEmptyCoverage(relativeUrl);
|
|
3695
|
-
}
|
|
3696
|
-
throw e;
|
|
3697
|
-
} finally {
|
|
3698
|
-
await operation.end();
|
|
3699
|
-
}
|
|
3700
|
-
};
|
|
3701
|
-
|
|
3702
|
-
const createEmptyCoverage = (relativeUrl) => {
|
|
3703
|
-
const { createFileCoverage } = importWithRequire("istanbul-lib-coverage");
|
|
3704
|
-
return createFileCoverage(relativeUrl).toJSON();
|
|
3705
|
-
};
|
|
3706
|
-
|
|
3707
|
-
const getMissingFileByFileCoverage = async ({
|
|
3708
|
-
signal,
|
|
3709
|
-
rootDirectoryUrl,
|
|
3710
|
-
coverageInclude,
|
|
3711
|
-
fileByFileCoverage,
|
|
3712
|
-
}) => {
|
|
3713
|
-
const relativeUrlsToCover = await listRelativeFileUrlToCover({
|
|
3714
|
-
signal,
|
|
3715
|
-
rootDirectoryUrl,
|
|
3716
|
-
coverageInclude,
|
|
3717
|
-
});
|
|
3718
|
-
const relativeUrlsMissing = relativeUrlsToCover.filter((relativeUrlToCover) =>
|
|
3719
|
-
Object.keys(fileByFileCoverage).every((key) => {
|
|
3720
|
-
return key !== `./${relativeUrlToCover}`;
|
|
3721
|
-
}),
|
|
3722
|
-
);
|
|
3723
|
-
|
|
3724
|
-
const operation = Abort.startOperation();
|
|
3725
|
-
operation.addAbortSignal(signal);
|
|
3726
|
-
const missingFileByFileCoverage = {};
|
|
3727
|
-
await relativeUrlsMissing.reduce(async (previous, relativeUrlMissing) => {
|
|
3728
|
-
operation.throwIfAborted();
|
|
3729
|
-
await previous;
|
|
3730
|
-
await operation.withSignal(async (signal) => {
|
|
3731
|
-
const emptyCoverage = await relativeUrlToEmptyCoverage(
|
|
3732
|
-
relativeUrlMissing,
|
|
3733
|
-
{
|
|
3734
|
-
signal,
|
|
3735
|
-
rootDirectoryUrl,
|
|
3736
|
-
},
|
|
3737
|
-
);
|
|
3738
|
-
missingFileByFileCoverage[`./${relativeUrlMissing}`] = emptyCoverage;
|
|
3739
|
-
});
|
|
3740
|
-
}, Promise.resolve());
|
|
3741
|
-
return missingFileByFileCoverage;
|
|
3742
|
-
};
|
|
3743
|
-
|
|
3744
|
-
const generateCoverage = async (
|
|
3745
|
-
testPlanResult,
|
|
3746
|
-
{ signal, logger, warn, rootDirectoryUrl, coverage },
|
|
3747
|
-
) => {
|
|
3748
|
-
// collect v8 and istanbul coverage from executions
|
|
3749
|
-
let { v8Coverage, fileByFileIstanbulCoverage } =
|
|
3750
|
-
await getCoverageFromTestPlanResults(testPlanResult.results, {
|
|
3751
|
-
signal,
|
|
3752
|
-
onMissing: ({ file, executionResult, executionName }) => {
|
|
3753
|
-
// several reasons not to have coverage here:
|
|
3754
|
-
// 1. the file we executed did not import an instrumented file.
|
|
3755
|
-
// - a test file without import
|
|
3756
|
-
// - a test file importing only file excluded from coverage
|
|
3757
|
-
// - a coverDescription badly configured so that we don't realize
|
|
3758
|
-
// a file should be covered
|
|
3759
|
-
|
|
3760
|
-
// 2. the file we wanted to executed timedout
|
|
3761
|
-
// - infinite loop
|
|
3762
|
-
// - too extensive operation
|
|
3763
|
-
// - a badly configured or too low allocatedMs for that execution.
|
|
3764
|
-
|
|
3765
|
-
// 3. the file we wanted to execute contains syntax-error
|
|
3766
|
-
|
|
3767
|
-
// in any scenario we are fine because
|
|
3768
|
-
// coverDescription will generate empty coverage for files
|
|
3769
|
-
// that were suppose to be coverage but were not.
|
|
3770
|
-
if (
|
|
3771
|
-
executionResult.status === "completed" &&
|
|
3772
|
-
executionResult.type === "node" &&
|
|
3773
|
-
coverage.methodForNodeJs !== "NODE_V8_COVERAGE"
|
|
3774
|
-
) {
|
|
3775
|
-
warn({
|
|
3776
|
-
code: "EXECUTION_COVERAGE_FILE_NOT_FOUND",
|
|
3777
|
-
message: `"${executionName}" execution of ${file} did not properly write coverage into ${executionResult.coverageFileUrl}`,
|
|
3778
|
-
});
|
|
3779
|
-
}
|
|
3780
|
-
},
|
|
3781
|
-
});
|
|
3556
|
+
// in any scenario we are fine because
|
|
3557
|
+
// coverDescription will generate empty coverage for files
|
|
3558
|
+
// that were suppose to be coverage but were not.
|
|
3559
|
+
if (
|
|
3560
|
+
executionResult.status === "completed" &&
|
|
3561
|
+
executionResult.type === "node" &&
|
|
3562
|
+
coverage.methodForNodeJs !== "NODE_V8_COVERAGE"
|
|
3563
|
+
) {
|
|
3564
|
+
warn({
|
|
3565
|
+
code: "EXECUTION_COVERAGE_FILE_NOT_FOUND",
|
|
3566
|
+
message: `"${executionName}" execution of ${file} did not properly write coverage into ${executionResult.coverageFileUrl}`,
|
|
3567
|
+
});
|
|
3568
|
+
}
|
|
3569
|
+
},
|
|
3570
|
+
});
|
|
3782
3571
|
|
|
3783
3572
|
if (coverage.methodForNodeJs === "NODE_V8_COVERAGE") {
|
|
3784
3573
|
await readNodeV8CoverageDirectory({
|
|
@@ -3921,323 +3710,215 @@ const getCoverageFromTestPlanResults = async (
|
|
|
3921
3710
|
|
|
3922
3711
|
const isV8Coverage = (coverage) => Boolean(coverage.result);
|
|
3923
3712
|
|
|
3924
|
-
const
|
|
3925
|
-
const
|
|
3926
|
-
const
|
|
3713
|
+
const createCallOrderer = () => {
|
|
3714
|
+
const queue = [];
|
|
3715
|
+
const callWhenPreviousExecutionAreDone = (executionIndex, callback) => {
|
|
3716
|
+
if (queue[executionIndex]) {
|
|
3717
|
+
throw new Error(`${executionIndex} already used`);
|
|
3718
|
+
}
|
|
3927
3719
|
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
return true;
|
|
3720
|
+
let allBeforeAreDone = true;
|
|
3721
|
+
if (executionIndex > 0) {
|
|
3722
|
+
let beforeIndex = executionIndex - 1;
|
|
3723
|
+
do {
|
|
3724
|
+
const value = queue[beforeIndex];
|
|
3725
|
+
if (!value) {
|
|
3726
|
+
allBeforeAreDone = false;
|
|
3727
|
+
break;
|
|
3728
|
+
}
|
|
3729
|
+
} while (beforeIndex--);
|
|
3939
3730
|
}
|
|
3940
|
-
if (
|
|
3941
|
-
|
|
3731
|
+
if (!allBeforeAreDone) {
|
|
3732
|
+
queue[executionIndex] = callback;
|
|
3733
|
+
return;
|
|
3942
3734
|
}
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3735
|
+
queue[executionIndex] = true;
|
|
3736
|
+
callback();
|
|
3737
|
+
let afterIndex = executionIndex + 1;
|
|
3738
|
+
while (afterIndex < queue.length) {
|
|
3739
|
+
const value = queue[afterIndex];
|
|
3740
|
+
if (value === undefined) {
|
|
3741
|
+
break;
|
|
3742
|
+
}
|
|
3743
|
+
if (typeof value === "function") {
|
|
3744
|
+
queue[afterIndex] = true;
|
|
3745
|
+
value();
|
|
3746
|
+
}
|
|
3747
|
+
afterIndex++;
|
|
3748
|
+
}
|
|
3749
|
+
};
|
|
3750
|
+
return callWhenPreviousExecutionAreDone;
|
|
3951
3751
|
};
|
|
3952
3752
|
|
|
3953
|
-
|
|
3954
|
-
webServer,
|
|
3955
|
-
{ signal, allocatedMs, logger, teardownCallbackSet },
|
|
3956
|
-
) => {
|
|
3957
|
-
const spawnedProcess = spawn(webServer.command, [], {
|
|
3958
|
-
// On non-windows platforms, `detached: true` makes child process a leader of a new
|
|
3959
|
-
// process group, making it possible to kill child process tree with `.kill(-pid)` command.
|
|
3960
|
-
// @see https://nodejs.org/api/child_process.html#child_process_options_detached
|
|
3961
|
-
detached: process.platform !== "win32",
|
|
3962
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
3963
|
-
shell: true,
|
|
3964
|
-
cwd: webServer.cwd,
|
|
3965
|
-
});
|
|
3966
|
-
if (!spawnedProcess.pid) {
|
|
3967
|
-
await new Promise((resolve, reject) => {
|
|
3968
|
-
spawnedProcess.once("error", (error) => {
|
|
3969
|
-
reject(new Error(`Failed to launch: ${error}`));
|
|
3970
|
-
});
|
|
3971
|
-
});
|
|
3972
|
-
}
|
|
3753
|
+
// https://gist.github.com/GaetanoPiazzolla/c40e1ebb9f709d091208e89baf9f4e00
|
|
3973
3754
|
|
|
3974
|
-
let errorReceived = false;
|
|
3975
|
-
const errorPromise = new Promise((resolve, reject) => {
|
|
3976
|
-
spawnedProcess.on("error", (e) => {
|
|
3977
|
-
errorReceived = true;
|
|
3978
|
-
reject(e);
|
|
3979
|
-
});
|
|
3980
|
-
});
|
|
3981
3755
|
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
resolve();
|
|
3998
|
-
});
|
|
3999
|
-
});
|
|
4000
|
-
const killProcess = async () => {
|
|
4001
|
-
logger.debug(`[pid=${spawnedProcess.pid}] <kill>`);
|
|
4002
|
-
if (!spawnedProcess.pid || spawnedProcess.killed || processClosed) {
|
|
4003
|
-
logger.debug(
|
|
4004
|
-
`[pid=${spawnedProcess.pid}] <skipped force kill spawnedProcess.killed=${spawnedProcess.killed} processClosed=${processClosed}>`,
|
|
4005
|
-
);
|
|
4006
|
-
return;
|
|
4007
|
-
}
|
|
4008
|
-
logger.debug(`[pid=${spawnedProcess.pid}] <will force kill>`);
|
|
4009
|
-
// Force kill the browser.
|
|
4010
|
-
try {
|
|
4011
|
-
if (process.platform === "win32") {
|
|
4012
|
-
const taskkillProcess = spawnSync(
|
|
4013
|
-
`taskkill /pid ${spawnedProcess.pid} /T /F`,
|
|
4014
|
-
{ shell: true },
|
|
4015
|
-
);
|
|
4016
|
-
const [stdout, stderr] = [
|
|
4017
|
-
taskkillProcess.stdout.toString(),
|
|
4018
|
-
taskkillProcess.stderr.toString(),
|
|
4019
|
-
];
|
|
4020
|
-
if (stdout)
|
|
4021
|
-
logger.debug(
|
|
4022
|
-
`[pid=${spawnedProcess.pid}] taskkill stdout: ${stdout}`,
|
|
4023
|
-
);
|
|
4024
|
-
if (stderr)
|
|
4025
|
-
logger.info(`[pid=${spawnedProcess.pid}] taskkill stderr: ${stderr}`);
|
|
4026
|
-
} else {
|
|
4027
|
-
process.kill(-spawnedProcess.pid, "SIGKILL");
|
|
4028
|
-
}
|
|
4029
|
-
} catch (e) {
|
|
4030
|
-
logger.info(
|
|
4031
|
-
`[pid=${spawnedProcess.pid}] exception while trying to kill process: ${e}`,
|
|
4032
|
-
);
|
|
4033
|
-
// the process might have already stopped
|
|
4034
|
-
}
|
|
4035
|
-
await closedPromise;
|
|
3756
|
+
const startMeasuringTotalCpuUsage = () => {
|
|
3757
|
+
let previousCpuArray = cpus();
|
|
3758
|
+
let previousMs = Date.now();
|
|
3759
|
+
let previousCpuUsage = cpuUsage();
|
|
3760
|
+
|
|
3761
|
+
const overall = {
|
|
3762
|
+
inactive: 100,
|
|
3763
|
+
active: 0,
|
|
3764
|
+
system: 0,
|
|
3765
|
+
user: 0,
|
|
3766
|
+
};
|
|
3767
|
+
const thisProcess = {
|
|
3768
|
+
active: 0,
|
|
3769
|
+
system: 0,
|
|
3770
|
+
user: 0,
|
|
4036
3771
|
};
|
|
3772
|
+
const details = previousCpuArray.map(() => {
|
|
3773
|
+
return {
|
|
3774
|
+
inactive: 100,
|
|
3775
|
+
active: 0,
|
|
3776
|
+
system: 0,
|
|
3777
|
+
user: 0,
|
|
3778
|
+
};
|
|
3779
|
+
});
|
|
4037
3780
|
|
|
4038
|
-
const
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
3781
|
+
const samples = [];
|
|
3782
|
+
const interval = setInterval(() => {
|
|
3783
|
+
let cpuArray = cpus();
|
|
3784
|
+
const ms = Date.now();
|
|
3785
|
+
const ellapsedMs = ms - previousMs;
|
|
3786
|
+
const cpuUsageSampleArray = [];
|
|
3787
|
+
let overallSystemMs = 0;
|
|
3788
|
+
let overallUserMs = 0;
|
|
3789
|
+
let overallInactiveMs = 0;
|
|
3790
|
+
let overallActiveMs = 0;
|
|
3791
|
+
let overallMsEllapsed = 0;
|
|
3792
|
+
let index = 0;
|
|
3793
|
+
for (const cpu of cpuArray) {
|
|
3794
|
+
const previousCpuTimes = previousCpuArray[index].times;
|
|
3795
|
+
const cpuTimes = cpu.times;
|
|
3796
|
+
const systemMs = cpuTimes.sys - previousCpuTimes.sys;
|
|
3797
|
+
const userMs = cpuTimes.user - previousCpuTimes.user;
|
|
3798
|
+
const activeMs = systemMs + userMs;
|
|
3799
|
+
const inactiveMs = ellapsedMs - activeMs;
|
|
3800
|
+
const cpuUsageSample = {
|
|
3801
|
+
inactive: inactiveMs / ellapsedMs,
|
|
3802
|
+
active: activeMs / ellapsedMs,
|
|
3803
|
+
system: systemMs / ellapsedMs,
|
|
3804
|
+
user: userMs / ellapsedMs,
|
|
3805
|
+
};
|
|
3806
|
+
cpuUsageSampleArray.push(cpuUsageSample);
|
|
4043
3807
|
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
}
|
|
4051
|
-
const connected = await pingServer(webServer.origin);
|
|
4052
|
-
if (connected) {
|
|
4053
|
-
break;
|
|
4054
|
-
}
|
|
4055
|
-
startOperation.throwIfAborted();
|
|
4056
|
-
const delay = logScale.shift() || 1000;
|
|
4057
|
-
logger.debug(`Waiting ${delay}ms`);
|
|
4058
|
-
await new Promise((x) => setTimeout(x, delay));
|
|
3808
|
+
overallSystemMs += systemMs;
|
|
3809
|
+
overallUserMs += userMs;
|
|
3810
|
+
overallInactiveMs += inactiveMs;
|
|
3811
|
+
overallActiveMs += activeMs;
|
|
3812
|
+
overallMsEllapsed += ellapsedMs;
|
|
3813
|
+
index++;
|
|
4059
3814
|
}
|
|
4060
|
-
|
|
3815
|
+
const overallUsageSample = {
|
|
3816
|
+
inactive: overallInactiveMs / overallMsEllapsed,
|
|
3817
|
+
active: overallActiveMs / overallMsEllapsed,
|
|
3818
|
+
system: overallSystemMs / overallMsEllapsed,
|
|
3819
|
+
user: overallUserMs / overallMsEllapsed,
|
|
3820
|
+
};
|
|
3821
|
+
previousCpuArray = cpuArray;
|
|
3822
|
+
previousMs = ms;
|
|
4061
3823
|
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
3824
|
+
const processCpuUsage = cpuUsage();
|
|
3825
|
+
const thisProcessSystemMs = Math.round(
|
|
3826
|
+
(processCpuUsage.system - previousCpuUsage.system) / 1000,
|
|
3827
|
+
);
|
|
3828
|
+
const thisProcessUserMs = Math.round(
|
|
3829
|
+
(processCpuUsage.user - previousCpuUsage.user) / 1000,
|
|
3830
|
+
);
|
|
3831
|
+
previousCpuUsage = processCpuUsage;
|
|
3832
|
+
|
|
3833
|
+
const thisProcessActiveMs = thisProcessSystemMs + thisProcessUserMs;
|
|
3834
|
+
const thisProcessInactiveMs = overallMsEllapsed - thisProcessActiveMs;
|
|
3835
|
+
const thisProcessSample = {
|
|
3836
|
+
inactive: thisProcessInactiveMs / overallMsEllapsed,
|
|
3837
|
+
active: thisProcessActiveMs / overallMsEllapsed,
|
|
3838
|
+
system: thisProcessSystemMs / overallMsEllapsed,
|
|
3839
|
+
user: thisProcessUserMs / overallMsEllapsed,
|
|
3840
|
+
};
|
|
3841
|
+
samples.push({
|
|
3842
|
+
cpuUsageSampleArray,
|
|
3843
|
+
overallUsageSample,
|
|
3844
|
+
thisProcessSample,
|
|
3845
|
+
});
|
|
3846
|
+
if (samples.length === 10) {
|
|
3847
|
+
{
|
|
3848
|
+
let index = 0;
|
|
3849
|
+
for (const detail of details) {
|
|
3850
|
+
let systemSum = 0;
|
|
3851
|
+
let userSum = 0;
|
|
3852
|
+
let inactiveSum = 0;
|
|
3853
|
+
let activeSum = 0;
|
|
3854
|
+
for (const sample of samples) {
|
|
3855
|
+
const { cpuUsageSampleArray } = sample;
|
|
3856
|
+
const cpuUsageSample = cpuUsageSampleArray[index];
|
|
3857
|
+
inactiveSum += cpuUsageSample.inactive;
|
|
3858
|
+
activeSum += cpuUsageSample.active;
|
|
3859
|
+
systemSum += cpuUsageSample.system;
|
|
3860
|
+
userSum += cpuUsageSample.user;
|
|
3861
|
+
}
|
|
3862
|
+
Object.assign(detail, {
|
|
3863
|
+
inactive: inactiveSum / samples.length,
|
|
3864
|
+
active: activeSum / samples.length,
|
|
3865
|
+
system: systemSum / samples.length,
|
|
3866
|
+
user: userSum / samples.length,
|
|
3867
|
+
});
|
|
3868
|
+
index++;
|
|
3869
|
+
}
|
|
4071
3870
|
}
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
3871
|
+
{
|
|
3872
|
+
let overallSystemSum = 0;
|
|
3873
|
+
let overallUserSum = 0;
|
|
3874
|
+
let overallInactiveSum = 0;
|
|
3875
|
+
let overallActiveSum = 0;
|
|
3876
|
+
for (const sample of samples) {
|
|
3877
|
+
const { overallUsageSample } = sample;
|
|
3878
|
+
overallSystemSum += overallUsageSample.system;
|
|
3879
|
+
overallUserSum += overallUsageSample.user;
|
|
3880
|
+
overallInactiveSum += overallUsageSample.inactive;
|
|
3881
|
+
overallActiveSum += overallUsageSample.active;
|
|
3882
|
+
}
|
|
3883
|
+
Object.assign(overall, {
|
|
3884
|
+
inactive: overallInactiveSum / samples.length,
|
|
3885
|
+
active: overallActiveSum / samples.length,
|
|
3886
|
+
system: overallSystemSum / samples.length,
|
|
3887
|
+
user: overallUserSum / samples.length,
|
|
3888
|
+
});
|
|
3889
|
+
}
|
|
3890
|
+
{
|
|
3891
|
+
let thisProcessSystemSum = 0;
|
|
3892
|
+
let thisProcessUserSum = 0;
|
|
3893
|
+
let thisProcessInactiveSum = 0;
|
|
3894
|
+
let thisProcessActiveSum = 0;
|
|
3895
|
+
for (const sample of samples) {
|
|
3896
|
+
const { thisProcessSample } = sample;
|
|
3897
|
+
thisProcessSystemSum += thisProcessSample.system;
|
|
3898
|
+
thisProcessUserSum += thisProcessSample.user;
|
|
3899
|
+
thisProcessInactiveSum += thisProcessSample.inactive;
|
|
3900
|
+
thisProcessActiveSum += thisProcessSample.active;
|
|
3901
|
+
}
|
|
3902
|
+
Object.assign(thisProcess, {
|
|
3903
|
+
inactive: thisProcessInactiveSum / samples.length,
|
|
3904
|
+
active: thisProcessActiveSum / samples.length,
|
|
3905
|
+
system: thisProcessSystemSum / samples.length,
|
|
3906
|
+
user: thisProcessUserSum / samples.length,
|
|
3907
|
+
});
|
|
4075
3908
|
}
|
|
3909
|
+
samples.length = 0;
|
|
4076
3910
|
}
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
await startOperation.end();
|
|
4080
|
-
}
|
|
4081
|
-
};
|
|
3911
|
+
}, 15);
|
|
3912
|
+
interval.unref();
|
|
4082
3913
|
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
...webServer,
|
|
4090
|
-
command: `node ${fileURLToPath(webServer.moduleUrl)}`,
|
|
3914
|
+
return {
|
|
3915
|
+
overall,
|
|
3916
|
+
thisProcess,
|
|
3917
|
+
details,
|
|
3918
|
+
stop: () => {
|
|
3919
|
+
clearInterval(interval);
|
|
4091
3920
|
},
|
|
4092
|
-
|
|
4093
|
-
);
|
|
4094
|
-
};
|
|
4095
|
-
|
|
4096
|
-
const basicFetch = async (
|
|
4097
|
-
url,
|
|
4098
|
-
{ rejectUnauthorized = true, method = "GET", headers = {} } = {},
|
|
4099
|
-
) => {
|
|
4100
|
-
let requestModule;
|
|
4101
|
-
if (url.startsWith("http:")) {
|
|
4102
|
-
requestModule = await import("node:http");
|
|
4103
|
-
} else {
|
|
4104
|
-
requestModule = await import("node:https");
|
|
4105
|
-
}
|
|
4106
|
-
const { request } = requestModule;
|
|
4107
|
-
|
|
4108
|
-
const urlObject = new URL(url);
|
|
4109
|
-
|
|
4110
|
-
return new Promise((resolve, reject) => {
|
|
4111
|
-
const req = request({
|
|
4112
|
-
rejectUnauthorized,
|
|
4113
|
-
hostname: urlObject.hostname,
|
|
4114
|
-
port: urlObject.port,
|
|
4115
|
-
path: urlObject.pathname,
|
|
4116
|
-
method,
|
|
4117
|
-
headers,
|
|
4118
|
-
});
|
|
4119
|
-
req.on("response", (response) => {
|
|
4120
|
-
resolve({
|
|
4121
|
-
status: response.statusCode,
|
|
4122
|
-
headers: response.headers,
|
|
4123
|
-
json: () => {
|
|
4124
|
-
req.setTimeout(0);
|
|
4125
|
-
req.destroy();
|
|
4126
|
-
return new Promise((resolve) => {
|
|
4127
|
-
if (response.headers["content-type"] !== "application/json") {
|
|
4128
|
-
console.warn("not json");
|
|
4129
|
-
}
|
|
4130
|
-
let responseBody = "";
|
|
4131
|
-
response.setEncoding("utf8");
|
|
4132
|
-
response.on("data", (chunk) => {
|
|
4133
|
-
responseBody += chunk;
|
|
4134
|
-
});
|
|
4135
|
-
response.on("end", () => {
|
|
4136
|
-
resolve(JSON.parse(responseBody));
|
|
4137
|
-
});
|
|
4138
|
-
response.on("error", (e) => {
|
|
4139
|
-
reject(e);
|
|
4140
|
-
});
|
|
4141
|
-
});
|
|
4142
|
-
},
|
|
4143
|
-
});
|
|
4144
|
-
});
|
|
4145
|
-
req.on("error", reject);
|
|
4146
|
-
req.end();
|
|
4147
|
-
});
|
|
4148
|
-
};
|
|
4149
|
-
|
|
4150
|
-
const assertAndNormalizeWebServer = async (
|
|
4151
|
-
webServer,
|
|
4152
|
-
{ signal, logger, teardownCallbackSet },
|
|
4153
|
-
) => {
|
|
4154
|
-
if (!webServer) {
|
|
4155
|
-
throw new TypeError(
|
|
4156
|
-
`webServer is required when running tests on browser(s)`,
|
|
4157
|
-
);
|
|
4158
|
-
}
|
|
4159
|
-
const unexpectedParamNames = Object.keys(webServer).filter((key) => {
|
|
4160
|
-
return ![
|
|
4161
|
-
"origin",
|
|
4162
|
-
"moduleUrl",
|
|
4163
|
-
"command",
|
|
4164
|
-
"cwd",
|
|
4165
|
-
"rootDirectoryUrl",
|
|
4166
|
-
].includes(key);
|
|
4167
|
-
});
|
|
4168
|
-
if (unexpectedParamNames.length > 0) {
|
|
4169
|
-
throw new TypeError(
|
|
4170
|
-
`${unexpectedParamNames.join(",")}: there is no such param to webServer`,
|
|
4171
|
-
);
|
|
4172
|
-
}
|
|
4173
|
-
if (typeof webServer.origin !== "string") {
|
|
4174
|
-
throw new TypeError(
|
|
4175
|
-
`webServer.origin must be a string, got ${webServer.origin}`,
|
|
4176
|
-
);
|
|
4177
|
-
}
|
|
4178
|
-
await ensureWebServerIsStarted(webServer, {
|
|
4179
|
-
signal,
|
|
4180
|
-
teardownCallbackSet,
|
|
4181
|
-
logger,
|
|
4182
|
-
});
|
|
4183
|
-
const { headers } = await basicFetch(webServer.origin, {
|
|
4184
|
-
method: "GET",
|
|
4185
|
-
rejectUnauthorized: false,
|
|
4186
|
-
headers: {
|
|
4187
|
-
"x-server-inspect": "1",
|
|
4188
|
-
},
|
|
4189
|
-
});
|
|
4190
|
-
if (String(headers["server"]).includes("jsenv_dev_server")) {
|
|
4191
|
-
webServer.isJsenvDevServer = true;
|
|
4192
|
-
const { json } = await basicFetch(`${webServer.origin}/__params__.json`, {
|
|
4193
|
-
rejectUnauthorized: false,
|
|
4194
|
-
});
|
|
4195
|
-
if (webServer.rootDirectoryUrl === undefined) {
|
|
4196
|
-
const jsenvDevServerParams = await json();
|
|
4197
|
-
webServer.rootDirectoryUrl = jsenvDevServerParams.sourceDirectoryUrl;
|
|
4198
|
-
} else {
|
|
4199
|
-
webServer.rootDirectoryUrl = assertAndNormalizeDirectoryUrl(
|
|
4200
|
-
webServer.rootDirectoryUrl,
|
|
4201
|
-
"webServer.rootDirectoryUrl",
|
|
4202
|
-
);
|
|
4203
|
-
}
|
|
4204
|
-
} else {
|
|
4205
|
-
webServer.rootDirectoryUrl = assertAndNormalizeDirectoryUrl(
|
|
4206
|
-
webServer.rootDirectoryUrl,
|
|
4207
|
-
"webServer.rootDirectoryUrl",
|
|
4208
|
-
);
|
|
4209
|
-
}
|
|
4210
|
-
};
|
|
4211
|
-
|
|
4212
|
-
const ensureWebServerIsStarted = async (
|
|
4213
|
-
webServer,
|
|
4214
|
-
{ signal, teardownCallbackSet, logger, allocatedMs = 5_000 },
|
|
4215
|
-
) => {
|
|
4216
|
-
const aServerIsListening = await pingServer(webServer.origin);
|
|
4217
|
-
if (aServerIsListening) {
|
|
4218
|
-
return;
|
|
4219
|
-
}
|
|
4220
|
-
if (webServer.moduleUrl) {
|
|
4221
|
-
await startServerUsingModuleUrl(webServer, {
|
|
4222
|
-
signal,
|
|
4223
|
-
allocatedMs,
|
|
4224
|
-
teardownCallbackSet,
|
|
4225
|
-
logger,
|
|
4226
|
-
});
|
|
4227
|
-
return;
|
|
4228
|
-
}
|
|
4229
|
-
if (webServer.command) {
|
|
4230
|
-
await startServerUsingCommand(webServer, {
|
|
4231
|
-
signal,
|
|
4232
|
-
allocatedMs,
|
|
4233
|
-
teardownCallbackSet,
|
|
4234
|
-
logger,
|
|
4235
|
-
});
|
|
4236
|
-
return;
|
|
4237
|
-
}
|
|
4238
|
-
throw new TypeError(
|
|
4239
|
-
`webServer.moduleUrl or webServer.command is required as there is no server listening "${webServer.origin}"`,
|
|
4240
|
-
);
|
|
3921
|
+
};
|
|
4241
3922
|
};
|
|
4242
3923
|
|
|
4243
3924
|
const githubAnnotationFromError = (
|
|
@@ -4371,226 +4052,38 @@ const replaceUrls$1 = (source, replace) => {
|
|
|
4371
4052
|
});
|
|
4372
4053
|
};
|
|
4373
4054
|
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
signal = new AbortController().signal,
|
|
4389
|
-
logger,
|
|
4390
|
-
allocatedMs,
|
|
4391
|
-
keepRunning = false,
|
|
4392
|
-
mirrorConsole = false,
|
|
4393
|
-
collectConsole = false,
|
|
4394
|
-
measureMemoryUsage = false,
|
|
4395
|
-
onMeasureMemoryAvailable,
|
|
4396
|
-
collectPerformance = false,
|
|
4397
|
-
coverageEnabled = false,
|
|
4398
|
-
coverageTempDirectoryUrl,
|
|
4399
|
-
runtime,
|
|
4400
|
-
runtimeParams,
|
|
4401
|
-
}) => {
|
|
4402
|
-
if (keepRunning) {
|
|
4403
|
-
allocatedMs = Infinity;
|
|
4404
|
-
}
|
|
4405
|
-
if (allocatedMs === Infinity) {
|
|
4406
|
-
allocatedMs = 0;
|
|
4407
|
-
}
|
|
4408
|
-
|
|
4409
|
-
const timingsOrigin = Date.now();
|
|
4410
|
-
const takeTiming = () => {
|
|
4411
|
-
return Date.now() - timingsOrigin;
|
|
4412
|
-
};
|
|
4413
|
-
const result = {
|
|
4414
|
-
status: "pending",
|
|
4415
|
-
errors: [],
|
|
4416
|
-
namespace: null,
|
|
4417
|
-
consoleCalls: null,
|
|
4418
|
-
timings: {
|
|
4419
|
-
origin: timingsOrigin,
|
|
4420
|
-
start: 0,
|
|
4421
|
-
runtimeStart: null,
|
|
4422
|
-
executionStart: null,
|
|
4423
|
-
executionEnd: null,
|
|
4424
|
-
runtimeEnd: null,
|
|
4425
|
-
end: null,
|
|
4426
|
-
},
|
|
4427
|
-
memoryUsage: null,
|
|
4428
|
-
performance: null,
|
|
4429
|
-
coverageFileUrl: null,
|
|
4430
|
-
};
|
|
4431
|
-
const onConsoleRef = { current: () => {} };
|
|
4432
|
-
const stopSignal = { notify: () => {} };
|
|
4433
|
-
const runtimeLabel = `${runtime.name}/${runtime.version}`;
|
|
4434
|
-
|
|
4435
|
-
const runOperation = Abort.startOperation();
|
|
4436
|
-
runOperation.addAbortSignal(signal);
|
|
4437
|
-
let timeoutAbortSource;
|
|
4438
|
-
if (allocatedMs) {
|
|
4439
|
-
timeoutAbortSource = runOperation.timeout(allocatedMs);
|
|
4440
|
-
}
|
|
4441
|
-
const consoleCalls = [];
|
|
4442
|
-
onConsoleRef.current = ({ type, text }) => {
|
|
4443
|
-
if (mirrorConsole) {
|
|
4444
|
-
if (type === "error") {
|
|
4445
|
-
process.stderr.write(text);
|
|
4446
|
-
} else {
|
|
4447
|
-
process.stdout.write(text);
|
|
4448
|
-
}
|
|
4055
|
+
const createIsInsideFragment = (fragment, total) => {
|
|
4056
|
+
let [dividend, divisor] = fragment.split("/");
|
|
4057
|
+
dividend = parseInt(dividend);
|
|
4058
|
+
divisor = parseInt(divisor);
|
|
4059
|
+
const groupSize = Math.ceil(total / divisor);
|
|
4060
|
+
let from;
|
|
4061
|
+
let to;
|
|
4062
|
+
if (groupSize === 0) {
|
|
4063
|
+
if (dividend === 1) {
|
|
4064
|
+
from = 0;
|
|
4065
|
+
to = 0;
|
|
4066
|
+
} else {
|
|
4067
|
+
from = Infinity;
|
|
4068
|
+
to = -1;
|
|
4449
4069
|
}
|
|
4450
|
-
|
|
4451
|
-
|
|
4070
|
+
} else if (dividend === 1) {
|
|
4071
|
+
from = 0;
|
|
4072
|
+
to = groupSize;
|
|
4073
|
+
} else {
|
|
4074
|
+
from = (dividend - 1) * groupSize;
|
|
4075
|
+
if (dividend === divisor) {
|
|
4076
|
+
to = total;
|
|
4077
|
+
} else {
|
|
4078
|
+
to = from + groupSize;
|
|
4452
4079
|
}
|
|
4453
|
-
};
|
|
4454
|
-
if (collectConsole) {
|
|
4455
|
-
result.consoleCalls = consoleCalls;
|
|
4456
4080
|
}
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
coverageFileUrl = new URL(
|
|
4464
|
-
`./${runtime.name}/${crypto.randomUUID()}.json`,
|
|
4465
|
-
coverageTempDirectoryUrl,
|
|
4466
|
-
).href;
|
|
4467
|
-
await ensureParentDirectories(coverageFileUrl);
|
|
4468
|
-
result.coverageFileUrl = coverageFileUrl;
|
|
4469
|
-
// written within the child_process/worker_thread or during runtime.run()
|
|
4470
|
-
// for browsers
|
|
4471
|
-
// (because it takes time to serialize and transfer the coverage object)
|
|
4472
|
-
}
|
|
4473
|
-
|
|
4474
|
-
try {
|
|
4475
|
-
logger.debug(`run() ${runtimeLabel}`);
|
|
4476
|
-
runOperation.throwIfAborted();
|
|
4477
|
-
const winnerPromise = new Promise((resolve) => {
|
|
4478
|
-
raceCallbacks(
|
|
4479
|
-
{
|
|
4480
|
-
aborted: (cb) => {
|
|
4481
|
-
runOperation.signal.addEventListener("abort", cb);
|
|
4482
|
-
return () => {
|
|
4483
|
-
runOperation.signal.removeEventListener("abort", cb);
|
|
4484
|
-
};
|
|
4485
|
-
},
|
|
4486
|
-
runned: async (cb) => {
|
|
4487
|
-
let runtimeStatus = "starting";
|
|
4488
|
-
try {
|
|
4489
|
-
const runResult = await runtime.run({
|
|
4490
|
-
signal: runOperation.signal,
|
|
4491
|
-
logger,
|
|
4492
|
-
...runtimeParams,
|
|
4493
|
-
collectConsole,
|
|
4494
|
-
measureMemoryUsage,
|
|
4495
|
-
onMeasureMemoryAvailable,
|
|
4496
|
-
collectPerformance,
|
|
4497
|
-
coverageFileUrl,
|
|
4498
|
-
keepRunning,
|
|
4499
|
-
stopSignal,
|
|
4500
|
-
onConsole: (log) => onConsoleRef.current(log),
|
|
4501
|
-
onRuntimeStarted: () => {
|
|
4502
|
-
runtimeStatus = "started";
|
|
4503
|
-
result.timings.runtimeStart = takeTiming();
|
|
4504
|
-
},
|
|
4505
|
-
onRuntimeStopped: () => {
|
|
4506
|
-
if (runtimeStatus === "stopped") return; // ignore double calls
|
|
4507
|
-
runtimeStatus = "stopped";
|
|
4508
|
-
result.timings.runtimeEnd = takeTiming();
|
|
4509
|
-
},
|
|
4510
|
-
});
|
|
4511
|
-
cb(runResult);
|
|
4512
|
-
} catch (e) {
|
|
4513
|
-
cb({
|
|
4514
|
-
status: "failed",
|
|
4515
|
-
errors: [e],
|
|
4516
|
-
});
|
|
4517
|
-
}
|
|
4518
|
-
},
|
|
4519
|
-
},
|
|
4520
|
-
resolve,
|
|
4521
|
-
);
|
|
4522
|
-
});
|
|
4523
|
-
const winner = await winnerPromise;
|
|
4524
|
-
if (winner.name === "aborted") {
|
|
4525
|
-
runOperation.throwIfAborted();
|
|
4526
|
-
}
|
|
4527
|
-
const {
|
|
4528
|
-
status,
|
|
4529
|
-
errors,
|
|
4530
|
-
namespace,
|
|
4531
|
-
timings = {},
|
|
4532
|
-
memoryUsage,
|
|
4533
|
-
performance,
|
|
4534
|
-
} = winner.data;
|
|
4535
|
-
result.status = status;
|
|
4536
|
-
for (let error of errors) {
|
|
4537
|
-
const errorProxy = normalizeRuntimeError(error);
|
|
4538
|
-
result.errors.push(errorProxy);
|
|
4539
|
-
}
|
|
4540
|
-
result.namespace = namespace;
|
|
4541
|
-
if (timings && typeof timings.start === "number") {
|
|
4542
|
-
const diff = timings.origin - result.timings.origin;
|
|
4543
|
-
result.timings.executionStart = timings.start + diff;
|
|
4544
|
-
result.timings.executionEnd = timings.end + diff;
|
|
4545
|
-
|
|
4546
|
-
if (result.timings.executionStart < result.timings.runtimeStart) {
|
|
4547
|
-
// can happen for browsers where navigationStart can be earlier
|
|
4548
|
-
// than when node.js calls runtimeStarted()
|
|
4549
|
-
result.timings.runtimeStart = result.timings.executionStart;
|
|
4550
|
-
}
|
|
4551
|
-
}
|
|
4552
|
-
result.memoryUsage =
|
|
4553
|
-
typeof memoryUsage === "number"
|
|
4554
|
-
? memoryUsage < 0
|
|
4555
|
-
? 0
|
|
4556
|
-
: memoryUsage
|
|
4557
|
-
: memoryUsage;
|
|
4558
|
-
result.performance = performance;
|
|
4559
|
-
} catch (e) {
|
|
4560
|
-
if (Abort.isAbortError(e)) {
|
|
4561
|
-
if (timeoutAbortSource && timeoutAbortSource.signal.aborted) {
|
|
4562
|
-
result.status = "timedout";
|
|
4563
|
-
} else {
|
|
4564
|
-
result.status = "aborted";
|
|
4565
|
-
}
|
|
4566
|
-
} else {
|
|
4567
|
-
result.status = "failed";
|
|
4568
|
-
result.errors.push(e);
|
|
4569
|
-
}
|
|
4570
|
-
} finally {
|
|
4571
|
-
await runOperation.end();
|
|
4572
|
-
result.timings.end = takeTiming();
|
|
4573
|
-
return result;
|
|
4574
|
-
}
|
|
4575
|
-
};
|
|
4576
|
-
const normalizeRuntimeError = (runtimeError) => {
|
|
4577
|
-
// the goal here is to obtain a "regular error"
|
|
4578
|
-
// that can be thrown using "throw" keyword
|
|
4579
|
-
// so we wrap the error hapenning inside the runtime
|
|
4580
|
-
// into "errorProxy" and put .stack property on it
|
|
4581
|
-
// the other properties are set by defineProperty so they are not enumerable
|
|
4582
|
-
// otherwise they would pollute the error displayed by Node.js
|
|
4583
|
-
const errorProxy = new Error(runtimeError.message);
|
|
4584
|
-
const exception = createException(runtimeError);
|
|
4585
|
-
for (const ownPropertyName of Object.getOwnPropertyNames(exception)) {
|
|
4586
|
-
Object.defineProperty(errorProxy, ownPropertyName, {
|
|
4587
|
-
writable: true,
|
|
4588
|
-
configurable: true,
|
|
4589
|
-
value: runtimeError[ownPropertyName],
|
|
4590
|
-
});
|
|
4591
|
-
}
|
|
4592
|
-
return errorProxy;
|
|
4593
|
-
};
|
|
4081
|
+
return (index) => {
|
|
4082
|
+
if (index < from) return false;
|
|
4083
|
+
if (index >= to) return false;
|
|
4084
|
+
return true;
|
|
4085
|
+
};
|
|
4086
|
+
};
|
|
4594
4087
|
|
|
4595
4088
|
// `Error: yo
|
|
4596
4089
|
// at Object.execute (http://127.0.0.1:57300/build/src/__test__/file-throw.js:9:13)
|
|
@@ -5329,7 +4822,11 @@ const renderConsoleOutput = (consoleCalls) => {
|
|
|
5329
4822
|
// and in that case the consoleCall is not suffixed with "\n"
|
|
5330
4823
|
// we want to keep these calls together in the output
|
|
5331
4824
|
const regroupedCalls = [];
|
|
4825
|
+
let everyCallIsLog = true;
|
|
5332
4826
|
consoleCalls.forEach((consoleCall, index) => {
|
|
4827
|
+
if (everyCallIsLog && consoleCall.type !== "log") {
|
|
4828
|
+
everyCallIsLog = false;
|
|
4829
|
+
}
|
|
5333
4830
|
if (index === 0) {
|
|
5334
4831
|
regroupedCalls.push(consoleCall);
|
|
5335
4832
|
return;
|
|
@@ -5351,30 +4848,38 @@ const renderConsoleOutput = (consoleCalls) => {
|
|
|
5351
4848
|
const previousRegroupedCall = regroupedCalls[previousRegroupedCallIndex];
|
|
5352
4849
|
previousRegroupedCall.text = `${previousRegroupedCall.text}${consoleCall.text}`;
|
|
5353
4850
|
});
|
|
5354
|
-
|
|
5355
4851
|
let consoleOutput = ``;
|
|
5356
|
-
|
|
4852
|
+
let index = 0;
|
|
4853
|
+
for (const regroupedCall of regroupedCalls) {
|
|
5357
4854
|
const text = regroupedCall.text;
|
|
5358
4855
|
const textFormatted = prefixFirstAndIndentRemainingLines({
|
|
5359
|
-
prefix: CONSOLE_ICONS[regroupedCall.type],
|
|
4856
|
+
prefix: everyCallIsLog ? "" : CONSOLE_ICONS[regroupedCall.type],
|
|
5360
4857
|
text,
|
|
5361
4858
|
trimLines: false,
|
|
5362
4859
|
trimLastLine: index === regroupedCalls.length - 1,
|
|
5363
4860
|
});
|
|
5364
4861
|
consoleOutput += textFormatted;
|
|
5365
|
-
|
|
4862
|
+
index++;
|
|
4863
|
+
}
|
|
5366
4864
|
return consoleOutput;
|
|
5367
4865
|
};
|
|
5368
4866
|
const prefixFirstAndIndentRemainingLines = ({
|
|
5369
4867
|
prefix,
|
|
5370
|
-
indentation
|
|
4868
|
+
indentation,
|
|
5371
4869
|
text,
|
|
5372
4870
|
trimLines,
|
|
5373
4871
|
trimLastLine,
|
|
5374
4872
|
}) => {
|
|
5375
4873
|
const lines = text.split(/\r?\n/);
|
|
5376
4874
|
const firstLine = lines.shift();
|
|
5377
|
-
|
|
4875
|
+
if (indentation === undefined) {
|
|
4876
|
+
if (prefix) {
|
|
4877
|
+
indentation = " "; // prefix + space
|
|
4878
|
+
} else {
|
|
4879
|
+
indentation = "";
|
|
4880
|
+
}
|
|
4881
|
+
}
|
|
4882
|
+
let result = prefix ? `${prefix} ${firstLine}` : firstLine;
|
|
5378
4883
|
let i = 0;
|
|
5379
4884
|
while (i < lines.length) {
|
|
5380
4885
|
const line = trimLines ? lines[i].trim() : lines[i];
|
|
@@ -5385,316 +4890,823 @@ const prefixFirstAndIndentRemainingLines = ({
|
|
|
5385
4890
|
? ""
|
|
5386
4891
|
: `\n`;
|
|
5387
4892
|
}
|
|
5388
|
-
return result;
|
|
5389
|
-
};
|
|
5390
|
-
const CONSOLE_ICONS = {
|
|
5391
|
-
get debug() {
|
|
5392
|
-
return UNICODE.DEBUG;
|
|
5393
|
-
},
|
|
5394
|
-
get info() {
|
|
5395
|
-
return UNICODE.INFO;
|
|
5396
|
-
},
|
|
5397
|
-
get warning() {
|
|
5398
|
-
return UNICODE.WARNING;
|
|
5399
|
-
},
|
|
5400
|
-
get error() {
|
|
5401
|
-
return UNICODE.FAILURE;
|
|
5402
|
-
},
|
|
5403
|
-
log: " ",
|
|
4893
|
+
return result;
|
|
4894
|
+
};
|
|
4895
|
+
const CONSOLE_ICONS = {
|
|
4896
|
+
get debug() {
|
|
4897
|
+
return UNICODE.DEBUG;
|
|
4898
|
+
},
|
|
4899
|
+
get info() {
|
|
4900
|
+
return UNICODE.INFO;
|
|
4901
|
+
},
|
|
4902
|
+
get warning() {
|
|
4903
|
+
return UNICODE.WARNING;
|
|
4904
|
+
},
|
|
4905
|
+
get error() {
|
|
4906
|
+
return UNICODE.FAILURE;
|
|
4907
|
+
},
|
|
4908
|
+
log: " ",
|
|
4909
|
+
};
|
|
4910
|
+
|
|
4911
|
+
const renderErrors = (execution, logOptions) => {
|
|
4912
|
+
const { errors = [] } = execution.result;
|
|
4913
|
+
if (errors.length === 0) {
|
|
4914
|
+
return "";
|
|
4915
|
+
}
|
|
4916
|
+
|
|
4917
|
+
if (errors.length === 1) {
|
|
4918
|
+
return renderSection({
|
|
4919
|
+
dashColor: ANSI.GREY,
|
|
4920
|
+
title: "error",
|
|
4921
|
+
content: formatErrorForTerminal(errors[0], {
|
|
4922
|
+
rootDirectoryUrl: execution.rootDirectoryUrl,
|
|
4923
|
+
mainFileRelativeUrl: execution.fileRelativeUrl,
|
|
4924
|
+
mockFluctuatingValues: logOptions.mockFluctuatingValues,
|
|
4925
|
+
tryColors: true,
|
|
4926
|
+
}),
|
|
4927
|
+
});
|
|
4928
|
+
}
|
|
4929
|
+
|
|
4930
|
+
let output = [];
|
|
4931
|
+
errors.forEach((error) => {
|
|
4932
|
+
output.push(
|
|
4933
|
+
prefixFirstAndIndentRemainingLines({
|
|
4934
|
+
prefix: `${UNICODE.CIRCLE_CROSS} `,
|
|
4935
|
+
indentation: " ",
|
|
4936
|
+
text: formatErrorForTerminal(error, {
|
|
4937
|
+
rootDirectoryUrl: execution.rootDirectoryUrl,
|
|
4938
|
+
mainFileRelativeUrl: execution.fileRelativeUrl,
|
|
4939
|
+
mockFluctuatingValues: logOptions.mockFluctuatingValues,
|
|
4940
|
+
tryColors: true,
|
|
4941
|
+
}),
|
|
4942
|
+
}),
|
|
4943
|
+
);
|
|
4944
|
+
});
|
|
4945
|
+
return renderSection({
|
|
4946
|
+
dashColor: ANSI.GREY,
|
|
4947
|
+
title: `errors (${errors.length})`,
|
|
4948
|
+
content: output.join(`\n`),
|
|
4949
|
+
});
|
|
4950
|
+
};
|
|
4951
|
+
|
|
4952
|
+
const renderOutro = (testPlanResult, logOptions = {}) => {
|
|
4953
|
+
const { counters } = testPlanResult;
|
|
4954
|
+
const { planified } = counters;
|
|
4955
|
+
if (planified === 0) {
|
|
4956
|
+
return "";
|
|
4957
|
+
}
|
|
4958
|
+
let title;
|
|
4959
|
+
if (planified === 0) {
|
|
4960
|
+
title = `no execution`;
|
|
4961
|
+
} else if (planified === 1) {
|
|
4962
|
+
title = `1 execution done`;
|
|
4963
|
+
} else {
|
|
4964
|
+
title = `${planified} executions done`;
|
|
4965
|
+
}
|
|
4966
|
+
return `${renderBigSection({
|
|
4967
|
+
title,
|
|
4968
|
+
content: renderOutroContent(testPlanResult, logOptions),
|
|
4969
|
+
})}\n`;
|
|
4970
|
+
};
|
|
4971
|
+
|
|
4972
|
+
const renderOutroContent = (testPlanResult, logOptions = {}) => {
|
|
4973
|
+
const lines = [];
|
|
4974
|
+
const { counters } = testPlanResult;
|
|
4975
|
+
|
|
4976
|
+
let executionLine = `status: ${renderStatusRepartition(counters)}`;
|
|
4977
|
+
lines.push(executionLine);
|
|
4978
|
+
|
|
4979
|
+
let durationLine = `duration: `;
|
|
4980
|
+
const { timings } = testPlanResult;
|
|
4981
|
+
if (logOptions.mockFluctuatingValues) {
|
|
4982
|
+
durationLine += "<mock>s";
|
|
4983
|
+
} else {
|
|
4984
|
+
durationLine += humanizeDuration(timings.end, { short: true });
|
|
4985
|
+
const namedTimings = {
|
|
4986
|
+
setup: humanizeTiming(timings.executionStart),
|
|
4987
|
+
execution: humanizeTiming(timings.executionEnd),
|
|
4988
|
+
teardown: humanizeTiming(timings.teardownEnd - timings.executionEnd),
|
|
4989
|
+
...(testPlanResult.coverage
|
|
4990
|
+
? {
|
|
4991
|
+
coverage: humanizeTiming(
|
|
4992
|
+
timings.coverageTeardownEnd - timings.teardownEnd,
|
|
4993
|
+
),
|
|
4994
|
+
}
|
|
4995
|
+
: {}),
|
|
4996
|
+
};
|
|
4997
|
+
durationLine += renderDetails(namedTimings);
|
|
4998
|
+
}
|
|
4999
|
+
lines.push(durationLine);
|
|
5000
|
+
|
|
5001
|
+
if (logOptions.cpuUsage) {
|
|
5002
|
+
const processCpuUsage = testPlanResult.cpuUsage.process;
|
|
5003
|
+
let cpuUsageLine = "cpu: ";
|
|
5004
|
+
cpuUsageLine += `${humanizeProcessCpuUsage(processCpuUsage.end)}`;
|
|
5005
|
+
cpuUsageLine += renderDetails({
|
|
5006
|
+
med: humanizeProcessCpuUsage(processCpuUsage.median),
|
|
5007
|
+
min: humanizeProcessCpuUsage(processCpuUsage.min),
|
|
5008
|
+
max: humanizeProcessCpuUsage(processCpuUsage.max),
|
|
5009
|
+
});
|
|
5010
|
+
lines.push(cpuUsageLine);
|
|
5011
|
+
}
|
|
5012
|
+
if (logOptions.memoryUsage) {
|
|
5013
|
+
const processMemoryUsage = testPlanResult.memoryUsage.process;
|
|
5014
|
+
let memoryUsageLine = "memory: ";
|
|
5015
|
+
memoryUsageLine += `${humanizeProcessMemoryUsage(processMemoryUsage.end)}`;
|
|
5016
|
+
memoryUsageLine += renderDetails({
|
|
5017
|
+
med: humanizeProcessMemoryUsage(processMemoryUsage.median),
|
|
5018
|
+
min: humanizeProcessMemoryUsage(processMemoryUsage.min),
|
|
5019
|
+
max: humanizeProcessMemoryUsage(processMemoryUsage.max),
|
|
5020
|
+
});
|
|
5021
|
+
lines.push(memoryUsageLine);
|
|
5022
|
+
}
|
|
5023
|
+
return lines.join("\n");
|
|
5024
|
+
};
|
|
5025
|
+
|
|
5026
|
+
const humanizeTiming = (value) => {
|
|
5027
|
+
return humanizeDuration(value, { short: true });
|
|
5028
|
+
};
|
|
5029
|
+
|
|
5030
|
+
const humanizeProcessCpuUsage = (ratio) => {
|
|
5031
|
+
const percentageAsNumber = ratio * 100;
|
|
5032
|
+
const percentageAsNumberRounded = Math.round(percentageAsNumber);
|
|
5033
|
+
const percentage = `${percentageAsNumberRounded}%`;
|
|
5034
|
+
return percentage;
|
|
5035
|
+
};
|
|
5036
|
+
|
|
5037
|
+
const humanizeProcessMemoryUsage = (value) => {
|
|
5038
|
+
return humanizeMemory(value, { short: true, decimals: 0 });
|
|
5039
|
+
};
|
|
5040
|
+
|
|
5041
|
+
const renderStatusRepartition = (counters, { showProgression } = {}) => {
|
|
5042
|
+
if (counters.planified === 0) {
|
|
5043
|
+
return ``;
|
|
5044
|
+
}
|
|
5045
|
+
if (counters.skipped === counters.planified) {
|
|
5046
|
+
return `all ${ANSI.color(`skipped`, COLOR_SKIPPED)}`;
|
|
5047
|
+
}
|
|
5048
|
+
if (counters.aborted === counters.planified) {
|
|
5049
|
+
return `all ${ANSI.color(`aborted`, COLOR_ABORTED)}`;
|
|
5050
|
+
}
|
|
5051
|
+
if (counters.cancelled === counters.planified) {
|
|
5052
|
+
return `all ${ANSI.color(`cancelled`, COLOR_CANCELLED)}`;
|
|
5053
|
+
}
|
|
5054
|
+
if (counters.timedout === counters.planified) {
|
|
5055
|
+
return `all ${ANSI.color(`timed out`, COLOR_TIMEOUT)}`;
|
|
5056
|
+
}
|
|
5057
|
+
if (counters.failed === counters.planified) {
|
|
5058
|
+
return `all ${ANSI.color(`failed`, COLOR_FAILED)}`;
|
|
5059
|
+
}
|
|
5060
|
+
if (counters.completed === counters.planified) {
|
|
5061
|
+
return `all ${ANSI.color(`completed`, COLOR_COMPLETED)}`;
|
|
5062
|
+
}
|
|
5063
|
+
const parts = [];
|
|
5064
|
+
if (showProgression) {
|
|
5065
|
+
if (counters.executing) {
|
|
5066
|
+
parts.push(`${counters.executing} executing`);
|
|
5067
|
+
}
|
|
5068
|
+
if (counters.waiting) {
|
|
5069
|
+
parts.push(`${counters.waiting} waiting`);
|
|
5070
|
+
}
|
|
5071
|
+
}
|
|
5072
|
+
if (counters.timedout) {
|
|
5073
|
+
parts.push(
|
|
5074
|
+
`${counters.timedout} ${ANSI.color(`timed out`, COLOR_TIMEOUT)}`,
|
|
5075
|
+
);
|
|
5076
|
+
}
|
|
5077
|
+
if (counters.failed) {
|
|
5078
|
+
parts.push(`${counters.failed} ${ANSI.color(`failed`, COLOR_FAILED)}`);
|
|
5079
|
+
}
|
|
5080
|
+
if (counters.completed) {
|
|
5081
|
+
parts.push(
|
|
5082
|
+
`${counters.completed} ${ANSI.color(`completed`, COLOR_COMPLETED)}`,
|
|
5083
|
+
);
|
|
5084
|
+
}
|
|
5085
|
+
if (counters.aborted) {
|
|
5086
|
+
parts.push(`${counters.aborted} ${ANSI.color(`aborted`, COLOR_ABORTED)}`);
|
|
5087
|
+
}
|
|
5088
|
+
if (counters.cancelled) {
|
|
5089
|
+
parts.push(
|
|
5090
|
+
`${counters.cancelled} ${ANSI.color(`cancelled`, COLOR_CANCELLED)}`,
|
|
5091
|
+
);
|
|
5092
|
+
}
|
|
5093
|
+
if (counters.skipped) {
|
|
5094
|
+
parts.push(`${counters.skipped} ${ANSI.color(`skipped`, COLOR_SKIPPED)}`);
|
|
5095
|
+
}
|
|
5096
|
+
return `${parts.join(", ")}`;
|
|
5097
|
+
};
|
|
5098
|
+
|
|
5099
|
+
const renderBigSection = (params) => {
|
|
5100
|
+
return renderSection({
|
|
5101
|
+
width: 45,
|
|
5102
|
+
...params,
|
|
5103
|
+
});
|
|
5104
|
+
};
|
|
5105
|
+
|
|
5106
|
+
const renderSection = ({
|
|
5107
|
+
title,
|
|
5108
|
+
content,
|
|
5109
|
+
dashColor = ANSI.GREY,
|
|
5110
|
+
width = 38,
|
|
5111
|
+
bottomSeparator = true,
|
|
5112
|
+
}) => {
|
|
5113
|
+
let section = "";
|
|
5114
|
+
|
|
5115
|
+
if (title) {
|
|
5116
|
+
const titleWidth = stripAnsi(title).length;
|
|
5117
|
+
const minWidthRequired = `--- … ---`.length;
|
|
5118
|
+
const needsTruncate = titleWidth + minWidthRequired >= width;
|
|
5119
|
+
if (needsTruncate) {
|
|
5120
|
+
const titleTruncated = title.slice(0, width - minWidthRequired);
|
|
5121
|
+
const leftDashes = ANSI.color("---", dashColor);
|
|
5122
|
+
const rightDashes = ANSI.color("---", dashColor);
|
|
5123
|
+
section += `${leftDashes} ${titleTruncated}… ${rightDashes}`;
|
|
5124
|
+
} else {
|
|
5125
|
+
const remainingWidth = width - titleWidth - 2; // 2 for spaces around the title
|
|
5126
|
+
const dashLeftCount = Math.floor(remainingWidth / 2);
|
|
5127
|
+
const dashRightCount = remainingWidth - dashLeftCount;
|
|
5128
|
+
const leftDashes = ANSI.color("-".repeat(dashLeftCount), dashColor);
|
|
5129
|
+
const rightDashes = ANSI.color("-".repeat(dashRightCount), dashColor);
|
|
5130
|
+
section += `${leftDashes} ${title} ${rightDashes}`;
|
|
5131
|
+
}
|
|
5132
|
+
section += "\n";
|
|
5133
|
+
} else {
|
|
5134
|
+
const topDashes = ANSI.color(`-`.repeat(width), dashColor);
|
|
5135
|
+
section += topDashes;
|
|
5136
|
+
section += "\n";
|
|
5137
|
+
}
|
|
5138
|
+
section += `${content}`;
|
|
5139
|
+
if (bottomSeparator) {
|
|
5140
|
+
section += "\n";
|
|
5141
|
+
const bottomDashes = ANSI.color(`-`.repeat(width), dashColor);
|
|
5142
|
+
section += bottomDashes;
|
|
5143
|
+
}
|
|
5144
|
+
return section;
|
|
5145
|
+
};
|
|
5146
|
+
|
|
5147
|
+
const renderDetails = (data) => {
|
|
5148
|
+
const details = [];
|
|
5149
|
+
for (const key of Object.keys(data)) {
|
|
5150
|
+
const value = data[key];
|
|
5151
|
+
let valueString = "";
|
|
5152
|
+
valueString += ANSI.color(`${key}:`, ANSI.GREY);
|
|
5153
|
+
const useNonGreyAnsiColor =
|
|
5154
|
+
typeof value === "string" && value.includes("\x1b");
|
|
5155
|
+
valueString += " ";
|
|
5156
|
+
valueString += useNonGreyAnsiColor
|
|
5157
|
+
? value
|
|
5158
|
+
: ANSI.color(String(value), ANSI.GREY);
|
|
5159
|
+
details.push(valueString);
|
|
5160
|
+
}
|
|
5161
|
+
if (details.length === 0) {
|
|
5162
|
+
return "";
|
|
5163
|
+
}
|
|
5164
|
+
|
|
5165
|
+
let string = "";
|
|
5166
|
+
string += ` ${ANSI.color("(", ANSI.GREY)}`;
|
|
5167
|
+
string += details.join(ANSI.color(", ", ANSI.GREY));
|
|
5168
|
+
string += ANSI.color(")", ANSI.GREY);
|
|
5169
|
+
return string;
|
|
5170
|
+
};
|
|
5171
|
+
|
|
5172
|
+
/*
|
|
5173
|
+
* Export a function capable to run a file on a runtime.
|
|
5174
|
+
*
|
|
5175
|
+
* - Used internally by "executeTestPlan" part of the documented API
|
|
5176
|
+
* - Used internally by "execute" an advanced API not documented
|
|
5177
|
+
* - logs generated during file execution can be collected
|
|
5178
|
+
* - logs generated during file execution can be mirrored (re-logged to the console)
|
|
5179
|
+
* - File is given allocatedMs to complete
|
|
5180
|
+
* - Errors are collected
|
|
5181
|
+
* - File execution result is returned, it contains status/errors/namespace/consoleCalls
|
|
5182
|
+
*/
|
|
5183
|
+
|
|
5184
|
+
|
|
5185
|
+
const run = async ({
|
|
5186
|
+
signal = new AbortController().signal,
|
|
5187
|
+
logger,
|
|
5188
|
+
allocatedMs,
|
|
5189
|
+
keepRunning = false,
|
|
5190
|
+
mirrorConsole = false,
|
|
5191
|
+
collectConsole = false,
|
|
5192
|
+
measureMemoryUsage = false,
|
|
5193
|
+
onMeasureMemoryAvailable,
|
|
5194
|
+
collectPerformance = false,
|
|
5195
|
+
coverageEnabled = false,
|
|
5196
|
+
coverageTempDirectoryUrl,
|
|
5197
|
+
runtime,
|
|
5198
|
+
runtimeParams,
|
|
5199
|
+
}) => {
|
|
5200
|
+
if (keepRunning) {
|
|
5201
|
+
allocatedMs = Infinity;
|
|
5202
|
+
}
|
|
5203
|
+
if (allocatedMs === Infinity) {
|
|
5204
|
+
allocatedMs = 0;
|
|
5205
|
+
}
|
|
5206
|
+
|
|
5207
|
+
const timingsOrigin = Date.now();
|
|
5208
|
+
const takeTiming = () => {
|
|
5209
|
+
return Date.now() - timingsOrigin;
|
|
5210
|
+
};
|
|
5211
|
+
const result = {
|
|
5212
|
+
status: "pending",
|
|
5213
|
+
errors: [],
|
|
5214
|
+
namespace: null,
|
|
5215
|
+
consoleCalls: null,
|
|
5216
|
+
timings: {
|
|
5217
|
+
origin: timingsOrigin,
|
|
5218
|
+
start: 0,
|
|
5219
|
+
runtimeStart: null,
|
|
5220
|
+
executionStart: null,
|
|
5221
|
+
executionEnd: null,
|
|
5222
|
+
runtimeEnd: null,
|
|
5223
|
+
end: null,
|
|
5224
|
+
},
|
|
5225
|
+
memoryUsage: null,
|
|
5226
|
+
performance: null,
|
|
5227
|
+
coverageFileUrl: null,
|
|
5228
|
+
};
|
|
5229
|
+
const onConsoleRef = { current: () => {} };
|
|
5230
|
+
const stopSignal = { notify: () => {} };
|
|
5231
|
+
const runtimeLabel = `${runtime.name}/${runtime.version}`;
|
|
5232
|
+
|
|
5233
|
+
const runOperation = Abort.startOperation();
|
|
5234
|
+
runOperation.addAbortSignal(signal);
|
|
5235
|
+
let timeoutAbortSource;
|
|
5236
|
+
if (allocatedMs) {
|
|
5237
|
+
timeoutAbortSource = runOperation.timeout(allocatedMs);
|
|
5238
|
+
}
|
|
5239
|
+
const consoleCalls = [];
|
|
5240
|
+
onConsoleRef.current = ({ type, text }) => {
|
|
5241
|
+
if (mirrorConsole) {
|
|
5242
|
+
if (type === "error") {
|
|
5243
|
+
process.stderr.write(text);
|
|
5244
|
+
} else {
|
|
5245
|
+
process.stdout.write(text);
|
|
5246
|
+
}
|
|
5247
|
+
}
|
|
5248
|
+
if (collectConsole) {
|
|
5249
|
+
consoleCalls.push({ type, text });
|
|
5250
|
+
}
|
|
5251
|
+
};
|
|
5252
|
+
if (collectConsole) {
|
|
5253
|
+
result.consoleCalls = consoleCalls;
|
|
5254
|
+
}
|
|
5255
|
+
|
|
5256
|
+
// we do not keep coverage in memory, it can grow very big
|
|
5257
|
+
// instead we store it on the filesystem
|
|
5258
|
+
// and they can be read later at "coverageFileUrl"
|
|
5259
|
+
let coverageFileUrl;
|
|
5260
|
+
if (coverageEnabled) {
|
|
5261
|
+
coverageFileUrl = new URL(
|
|
5262
|
+
`./${runtime.name}/${crypto.randomUUID()}.json`,
|
|
5263
|
+
coverageTempDirectoryUrl,
|
|
5264
|
+
).href;
|
|
5265
|
+
await ensureParentDirectories(coverageFileUrl);
|
|
5266
|
+
result.coverageFileUrl = coverageFileUrl;
|
|
5267
|
+
// written within the child_process/worker_thread or during runtime.run()
|
|
5268
|
+
// for browsers
|
|
5269
|
+
// (because it takes time to serialize and transfer the coverage object)
|
|
5270
|
+
}
|
|
5271
|
+
|
|
5272
|
+
try {
|
|
5273
|
+
logger.debug(`run() ${runtimeLabel}`);
|
|
5274
|
+
runOperation.throwIfAborted();
|
|
5275
|
+
const winnerPromise = new Promise((resolve) => {
|
|
5276
|
+
raceCallbacks(
|
|
5277
|
+
{
|
|
5278
|
+
aborted: (cb) => {
|
|
5279
|
+
runOperation.signal.addEventListener("abort", cb);
|
|
5280
|
+
return () => {
|
|
5281
|
+
runOperation.signal.removeEventListener("abort", cb);
|
|
5282
|
+
};
|
|
5283
|
+
},
|
|
5284
|
+
runned: async (cb) => {
|
|
5285
|
+
let runtimeStatus = "starting";
|
|
5286
|
+
try {
|
|
5287
|
+
const runResult = await runtime.run({
|
|
5288
|
+
signal: runOperation.signal,
|
|
5289
|
+
logger,
|
|
5290
|
+
...runtimeParams,
|
|
5291
|
+
collectConsole,
|
|
5292
|
+
measureMemoryUsage,
|
|
5293
|
+
onMeasureMemoryAvailable,
|
|
5294
|
+
collectPerformance,
|
|
5295
|
+
coverageFileUrl,
|
|
5296
|
+
keepRunning,
|
|
5297
|
+
stopSignal,
|
|
5298
|
+
onConsole: (log) => onConsoleRef.current(log),
|
|
5299
|
+
onRuntimeStarted: () => {
|
|
5300
|
+
runtimeStatus = "started";
|
|
5301
|
+
result.timings.runtimeStart = takeTiming();
|
|
5302
|
+
},
|
|
5303
|
+
onRuntimeStopped: () => {
|
|
5304
|
+
if (runtimeStatus === "stopped") return; // ignore double calls
|
|
5305
|
+
runtimeStatus = "stopped";
|
|
5306
|
+
result.timings.runtimeEnd = takeTiming();
|
|
5307
|
+
},
|
|
5308
|
+
});
|
|
5309
|
+
cb(runResult);
|
|
5310
|
+
} catch (e) {
|
|
5311
|
+
cb({
|
|
5312
|
+
status: "failed",
|
|
5313
|
+
errors: [e],
|
|
5314
|
+
});
|
|
5315
|
+
}
|
|
5316
|
+
},
|
|
5317
|
+
},
|
|
5318
|
+
resolve,
|
|
5319
|
+
);
|
|
5320
|
+
});
|
|
5321
|
+
const winner = await winnerPromise;
|
|
5322
|
+
if (winner.name === "aborted") {
|
|
5323
|
+
runOperation.throwIfAborted();
|
|
5324
|
+
}
|
|
5325
|
+
const {
|
|
5326
|
+
status,
|
|
5327
|
+
errors,
|
|
5328
|
+
namespace,
|
|
5329
|
+
timings = {},
|
|
5330
|
+
memoryUsage,
|
|
5331
|
+
performance,
|
|
5332
|
+
} = winner.data;
|
|
5333
|
+
result.status = status;
|
|
5334
|
+
for (let error of errors) {
|
|
5335
|
+
const errorProxy = normalizeRuntimeError(error);
|
|
5336
|
+
result.errors.push(errorProxy);
|
|
5337
|
+
}
|
|
5338
|
+
result.namespace = namespace;
|
|
5339
|
+
if (timings && typeof timings.start === "number") {
|
|
5340
|
+
const diff = timings.origin - result.timings.origin;
|
|
5341
|
+
result.timings.executionStart = timings.start + diff;
|
|
5342
|
+
result.timings.executionEnd = timings.end + diff;
|
|
5343
|
+
|
|
5344
|
+
if (result.timings.executionStart < result.timings.runtimeStart) {
|
|
5345
|
+
// can happen for browsers where navigationStart can be earlier
|
|
5346
|
+
// than when node.js calls runtimeStarted()
|
|
5347
|
+
result.timings.runtimeStart = result.timings.executionStart;
|
|
5348
|
+
}
|
|
5349
|
+
}
|
|
5350
|
+
result.memoryUsage =
|
|
5351
|
+
typeof memoryUsage === "number"
|
|
5352
|
+
? memoryUsage < 0
|
|
5353
|
+
? 0
|
|
5354
|
+
: memoryUsage
|
|
5355
|
+
: memoryUsage;
|
|
5356
|
+
result.performance = performance;
|
|
5357
|
+
} catch (e) {
|
|
5358
|
+
if (Abort.isAbortError(e)) {
|
|
5359
|
+
if (timeoutAbortSource && timeoutAbortSource.signal.aborted) {
|
|
5360
|
+
result.status = "timedout";
|
|
5361
|
+
} else {
|
|
5362
|
+
result.status = "aborted";
|
|
5363
|
+
}
|
|
5364
|
+
} else {
|
|
5365
|
+
result.status = "failed";
|
|
5366
|
+
result.errors.push(e);
|
|
5367
|
+
}
|
|
5368
|
+
} finally {
|
|
5369
|
+
await runOperation.end();
|
|
5370
|
+
result.timings.end = takeTiming();
|
|
5371
|
+
return result;
|
|
5372
|
+
}
|
|
5404
5373
|
};
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
mockFluctuatingValues: logOptions.mockFluctuatingValues,
|
|
5420
|
-
tryColors: true,
|
|
5421
|
-
}),
|
|
5374
|
+
const normalizeRuntimeError = (runtimeError) => {
|
|
5375
|
+
// the goal here is to obtain a "regular error"
|
|
5376
|
+
// that can be thrown using "throw" keyword
|
|
5377
|
+
// so we wrap the error hapenning inside the runtime
|
|
5378
|
+
// into "errorProxy" and put .stack property on it
|
|
5379
|
+
// the other properties are set by defineProperty so they are not enumerable
|
|
5380
|
+
// otherwise they would pollute the error displayed by Node.js
|
|
5381
|
+
const errorProxy = new Error(runtimeError.message);
|
|
5382
|
+
const exception = createException(runtimeError);
|
|
5383
|
+
for (const ownPropertyName of Object.getOwnPropertyNames(exception)) {
|
|
5384
|
+
Object.defineProperty(errorProxy, ownPropertyName, {
|
|
5385
|
+
writable: true,
|
|
5386
|
+
configurable: true,
|
|
5387
|
+
value: runtimeError[ownPropertyName],
|
|
5422
5388
|
});
|
|
5423
5389
|
}
|
|
5424
|
-
|
|
5425
|
-
let output = [];
|
|
5426
|
-
errors.forEach((error) => {
|
|
5427
|
-
output.push(
|
|
5428
|
-
prefixFirstAndIndentRemainingLines({
|
|
5429
|
-
prefix: `${UNICODE.CIRCLE_CROSS} `,
|
|
5430
|
-
indentation: " ",
|
|
5431
|
-
text: formatErrorForTerminal(error, {
|
|
5432
|
-
rootDirectoryUrl: execution.rootDirectoryUrl,
|
|
5433
|
-
mainFileRelativeUrl: execution.fileRelativeUrl,
|
|
5434
|
-
mockFluctuatingValues: logOptions.mockFluctuatingValues,
|
|
5435
|
-
tryColors: true,
|
|
5436
|
-
}),
|
|
5437
|
-
}),
|
|
5438
|
-
);
|
|
5439
|
-
});
|
|
5440
|
-
return renderSection({
|
|
5441
|
-
dashColor: ANSI.GREY,
|
|
5442
|
-
title: `errors (${errors.length})`,
|
|
5443
|
-
content: output.join(`\n`),
|
|
5444
|
-
});
|
|
5390
|
+
return errorProxy;
|
|
5445
5391
|
};
|
|
5446
5392
|
|
|
5447
|
-
const
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
if (planified === 0) {
|
|
5455
|
-
title = `no execution`;
|
|
5456
|
-
} else if (planified === 1) {
|
|
5457
|
-
title = `1 execution done`;
|
|
5393
|
+
const basicFetch = async (
|
|
5394
|
+
url,
|
|
5395
|
+
{ rejectUnauthorized = true, method = "GET", headers = {} } = {},
|
|
5396
|
+
) => {
|
|
5397
|
+
let requestModule;
|
|
5398
|
+
if (url.startsWith("http:")) {
|
|
5399
|
+
requestModule = await import("node:http");
|
|
5458
5400
|
} else {
|
|
5459
|
-
|
|
5401
|
+
requestModule = await import("node:https");
|
|
5460
5402
|
}
|
|
5461
|
-
|
|
5462
|
-
title,
|
|
5463
|
-
content: renderOutroContent(testPlanResult, logOptions),
|
|
5464
|
-
})}\n`;
|
|
5465
|
-
};
|
|
5466
|
-
|
|
5467
|
-
const renderOutroContent = (testPlanResult, logOptions = {}) => {
|
|
5468
|
-
const lines = [];
|
|
5469
|
-
const { counters } = testPlanResult;
|
|
5470
|
-
|
|
5471
|
-
let executionLine = `status: ${renderStatusRepartition(counters)}`;
|
|
5472
|
-
lines.push(executionLine);
|
|
5403
|
+
const { request } = requestModule;
|
|
5473
5404
|
|
|
5474
|
-
|
|
5475
|
-
const { timings } = testPlanResult;
|
|
5476
|
-
if (logOptions.mockFluctuatingValues) {
|
|
5477
|
-
durationLine += "<mock>s";
|
|
5478
|
-
} else {
|
|
5479
|
-
durationLine += humanizeDuration(timings.end, { short: true });
|
|
5480
|
-
const namedTimings = {
|
|
5481
|
-
setup: humanizeTiming(timings.executionStart),
|
|
5482
|
-
execution: humanizeTiming(timings.executionEnd),
|
|
5483
|
-
teardown: humanizeTiming(timings.teardownEnd - timings.executionEnd),
|
|
5484
|
-
...(testPlanResult.coverage
|
|
5485
|
-
? {
|
|
5486
|
-
coverage: humanizeTiming(
|
|
5487
|
-
timings.coverageTeardownEnd - timings.teardownEnd,
|
|
5488
|
-
),
|
|
5489
|
-
}
|
|
5490
|
-
: {}),
|
|
5491
|
-
};
|
|
5492
|
-
durationLine += renderDetails(namedTimings);
|
|
5493
|
-
}
|
|
5494
|
-
lines.push(durationLine);
|
|
5405
|
+
const urlObject = new URL(url);
|
|
5495
5406
|
|
|
5496
|
-
|
|
5497
|
-
const
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5407
|
+
return new Promise((resolve, reject) => {
|
|
5408
|
+
const req = request({
|
|
5409
|
+
rejectUnauthorized,
|
|
5410
|
+
hostname: urlObject.hostname,
|
|
5411
|
+
port: urlObject.port,
|
|
5412
|
+
path: urlObject.pathname,
|
|
5413
|
+
method,
|
|
5414
|
+
headers,
|
|
5504
5415
|
});
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5416
|
+
req.on("response", (response) => {
|
|
5417
|
+
resolve({
|
|
5418
|
+
status: response.statusCode,
|
|
5419
|
+
headers: response.headers,
|
|
5420
|
+
json: () => {
|
|
5421
|
+
req.setTimeout(0);
|
|
5422
|
+
req.destroy();
|
|
5423
|
+
return new Promise((resolve) => {
|
|
5424
|
+
if (response.headers["content-type"] !== "application/json") {
|
|
5425
|
+
console.warn("not json");
|
|
5426
|
+
}
|
|
5427
|
+
let responseBody = "";
|
|
5428
|
+
response.setEncoding("utf8");
|
|
5429
|
+
response.on("data", (chunk) => {
|
|
5430
|
+
responseBody += chunk;
|
|
5431
|
+
});
|
|
5432
|
+
response.on("end", () => {
|
|
5433
|
+
resolve(JSON.parse(responseBody));
|
|
5434
|
+
});
|
|
5435
|
+
response.on("error", (e) => {
|
|
5436
|
+
reject(e);
|
|
5437
|
+
});
|
|
5438
|
+
});
|
|
5439
|
+
},
|
|
5440
|
+
});
|
|
5515
5441
|
});
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
};
|
|
5520
|
-
|
|
5521
|
-
const humanizeTiming = (value) => {
|
|
5522
|
-
return humanizeDuration(value, { short: true });
|
|
5523
|
-
};
|
|
5524
|
-
|
|
5525
|
-
const humanizeProcessCpuUsage = (ratio) => {
|
|
5526
|
-
const percentageAsNumber = ratio * 100;
|
|
5527
|
-
const percentageAsNumberRounded = Math.round(percentageAsNumber);
|
|
5528
|
-
const percentage = `${percentageAsNumberRounded}%`;
|
|
5529
|
-
return percentage;
|
|
5442
|
+
req.on("error", reject);
|
|
5443
|
+
req.end();
|
|
5444
|
+
});
|
|
5530
5445
|
};
|
|
5531
5446
|
|
|
5532
|
-
const
|
|
5533
|
-
|
|
5534
|
-
};
|
|
5447
|
+
const pingServer = async (url) => {
|
|
5448
|
+
const server = createServer();
|
|
5449
|
+
const { hostname, port } = new URL(url);
|
|
5535
5450
|
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
if (counters.failed === counters.planified) {
|
|
5553
|
-
return `all ${ANSI.color(`failed`, COLOR_FAILED)}`;
|
|
5554
|
-
}
|
|
5555
|
-
if (counters.completed === counters.planified) {
|
|
5556
|
-
return `all ${ANSI.color(`completed`, COLOR_COMPLETED)}`;
|
|
5557
|
-
}
|
|
5558
|
-
const parts = [];
|
|
5559
|
-
if (showProgression) {
|
|
5560
|
-
if (counters.executing) {
|
|
5561
|
-
parts.push(`${counters.executing} executing`);
|
|
5562
|
-
}
|
|
5563
|
-
if (counters.waiting) {
|
|
5564
|
-
parts.push(`${counters.waiting} waiting`);
|
|
5565
|
-
}
|
|
5566
|
-
}
|
|
5567
|
-
if (counters.timedout) {
|
|
5568
|
-
parts.push(
|
|
5569
|
-
`${counters.timedout} ${ANSI.color(`timed out`, COLOR_TIMEOUT)}`,
|
|
5570
|
-
);
|
|
5571
|
-
}
|
|
5572
|
-
if (counters.failed) {
|
|
5573
|
-
parts.push(`${counters.failed} ${ANSI.color(`failed`, COLOR_FAILED)}`);
|
|
5574
|
-
}
|
|
5575
|
-
if (counters.completed) {
|
|
5576
|
-
parts.push(
|
|
5577
|
-
`${counters.completed} ${ANSI.color(`completed`, COLOR_COMPLETED)}`,
|
|
5578
|
-
);
|
|
5579
|
-
}
|
|
5580
|
-
if (counters.aborted) {
|
|
5581
|
-
parts.push(`${counters.aborted} ${ANSI.color(`aborted`, COLOR_ABORTED)}`);
|
|
5582
|
-
}
|
|
5583
|
-
if (counters.cancelled) {
|
|
5584
|
-
parts.push(
|
|
5585
|
-
`${counters.cancelled} ${ANSI.color(`cancelled`, COLOR_CANCELLED)}`,
|
|
5586
|
-
);
|
|
5587
|
-
}
|
|
5588
|
-
if (counters.skipped) {
|
|
5589
|
-
parts.push(`${counters.skipped} ${ANSI.color(`skipped`, COLOR_SKIPPED)}`);
|
|
5451
|
+
try {
|
|
5452
|
+
await new Promise((resolve, reject) => {
|
|
5453
|
+
server.on("error", reject);
|
|
5454
|
+
server.on("listening", () => {
|
|
5455
|
+
resolve();
|
|
5456
|
+
});
|
|
5457
|
+
server.listen(port, hostname);
|
|
5458
|
+
});
|
|
5459
|
+
} catch (error) {
|
|
5460
|
+
if (error && error.code === "EADDRINUSE") {
|
|
5461
|
+
return true;
|
|
5462
|
+
}
|
|
5463
|
+
if (error && error.code === "EACCES") {
|
|
5464
|
+
return true;
|
|
5465
|
+
}
|
|
5466
|
+
throw error;
|
|
5590
5467
|
}
|
|
5591
|
-
|
|
5468
|
+
await new Promise((resolve, reject) => {
|
|
5469
|
+
server.on("error", reject);
|
|
5470
|
+
server.on("close", resolve);
|
|
5471
|
+
server.close();
|
|
5472
|
+
});
|
|
5473
|
+
return false;
|
|
5592
5474
|
};
|
|
5593
5475
|
|
|
5594
|
-
const
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5476
|
+
const startServerUsingCommand = async (
|
|
5477
|
+
webServer,
|
|
5478
|
+
{ signal, allocatedMs, logger, teardownCallbackSet },
|
|
5479
|
+
) => {
|
|
5480
|
+
const spawnedProcess = spawn(webServer.command, [], {
|
|
5481
|
+
// On non-windows platforms, `detached: true` makes child process a leader of a new
|
|
5482
|
+
// process group, making it possible to kill child process tree with `.kill(-pid)` command.
|
|
5483
|
+
// @see https://nodejs.org/api/child_process.html#child_process_options_detached
|
|
5484
|
+
detached: process.platform !== "win32",
|
|
5485
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
5486
|
+
shell: true,
|
|
5487
|
+
cwd: webServer.cwd,
|
|
5598
5488
|
});
|
|
5599
|
-
|
|
5489
|
+
if (!spawnedProcess.pid) {
|
|
5490
|
+
await new Promise((resolve, reject) => {
|
|
5491
|
+
spawnedProcess.once("error", (error) => {
|
|
5492
|
+
reject(new Error(`Failed to launch: ${error}`));
|
|
5493
|
+
});
|
|
5494
|
+
});
|
|
5495
|
+
}
|
|
5600
5496
|
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
})
|
|
5608
|
-
let section = "";
|
|
5497
|
+
let errorReceived = false;
|
|
5498
|
+
const errorPromise = new Promise((resolve, reject) => {
|
|
5499
|
+
spawnedProcess.on("error", (e) => {
|
|
5500
|
+
errorReceived = true;
|
|
5501
|
+
reject(e);
|
|
5502
|
+
});
|
|
5503
|
+
});
|
|
5609
5504
|
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5505
|
+
// const stdout = readline.createInterface({ input: spawnedProcess.stdout });
|
|
5506
|
+
// stdout.on("line", () => {
|
|
5507
|
+
// logger.debug(`[pid=${spawnedProcess.pid}][out] ${data}`);
|
|
5508
|
+
// });
|
|
5509
|
+
// const stderr = readline.createInterface({ input: spawnedProcess.stderr });
|
|
5510
|
+
// stderr.on("line", (data) => {
|
|
5511
|
+
// logger.debug(`[pid=${spawnedProcess.pid}][err] ${data}`);
|
|
5512
|
+
// });
|
|
5513
|
+
let processClosed = false;
|
|
5514
|
+
const closedPromise = new Promise((resolve) => {
|
|
5515
|
+
spawnedProcess.once("exit", (exitCode, signal) => {
|
|
5516
|
+
logger.debug(
|
|
5517
|
+
`[pid=${spawnedProcess.pid}] <process did exit: exitCode=${exitCode}, signal=${signal}>`,
|
|
5518
|
+
);
|
|
5519
|
+
processClosed = true;
|
|
5520
|
+
resolve();
|
|
5521
|
+
});
|
|
5522
|
+
});
|
|
5523
|
+
const killProcess = async () => {
|
|
5524
|
+
logger.debug(`[pid=${spawnedProcess.pid}] <kill>`);
|
|
5525
|
+
if (!spawnedProcess.pid || spawnedProcess.killed || processClosed) {
|
|
5526
|
+
logger.debug(
|
|
5527
|
+
`[pid=${spawnedProcess.pid}] <skipped force kill spawnedProcess.killed=${spawnedProcess.killed} processClosed=${processClosed}>`,
|
|
5528
|
+
);
|
|
5529
|
+
return;
|
|
5626
5530
|
}
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5531
|
+
logger.debug(`[pid=${spawnedProcess.pid}] <will force kill>`);
|
|
5532
|
+
// Force kill the browser.
|
|
5533
|
+
try {
|
|
5534
|
+
if (process.platform === "win32") {
|
|
5535
|
+
const taskkillProcess = spawnSync(
|
|
5536
|
+
`taskkill /pid ${spawnedProcess.pid} /T /F`,
|
|
5537
|
+
{ shell: true },
|
|
5538
|
+
);
|
|
5539
|
+
const [stdout, stderr] = [
|
|
5540
|
+
taskkillProcess.stdout.toString(),
|
|
5541
|
+
taskkillProcess.stderr.toString(),
|
|
5542
|
+
];
|
|
5543
|
+
if (stdout)
|
|
5544
|
+
logger.debug(
|
|
5545
|
+
`[pid=${spawnedProcess.pid}] taskkill stdout: ${stdout}`,
|
|
5546
|
+
);
|
|
5547
|
+
if (stderr)
|
|
5548
|
+
logger.info(`[pid=${spawnedProcess.pid}] taskkill stderr: ${stderr}`);
|
|
5549
|
+
} else {
|
|
5550
|
+
process.kill(-spawnedProcess.pid, "SIGKILL");
|
|
5551
|
+
}
|
|
5552
|
+
} catch (e) {
|
|
5553
|
+
logger.info(
|
|
5554
|
+
`[pid=${spawnedProcess.pid}] exception while trying to kill process: ${e}`,
|
|
5555
|
+
);
|
|
5556
|
+
// the process might have already stopped
|
|
5557
|
+
}
|
|
5558
|
+
await closedPromise;
|
|
5559
|
+
};
|
|
5560
|
+
|
|
5561
|
+
const startOperation = Abort.startOperation();
|
|
5562
|
+
startOperation.addAbortSignal(signal);
|
|
5563
|
+
const timeoutAbortSource = startOperation.timeout(allocatedMs);
|
|
5564
|
+
startOperation.addAbortCallback(killProcess);
|
|
5565
|
+
teardownCallbackSet.add(killProcess);
|
|
5566
|
+
|
|
5567
|
+
const startedPromise = (async () => {
|
|
5568
|
+
const logScale = [100, 250, 500];
|
|
5569
|
+
// eslint-disable-next-line no-constant-condition
|
|
5570
|
+
while (true) {
|
|
5571
|
+
if (errorReceived) {
|
|
5572
|
+
break;
|
|
5573
|
+
}
|
|
5574
|
+
const connected = await pingServer(webServer.origin);
|
|
5575
|
+
if (connected) {
|
|
5576
|
+
break;
|
|
5577
|
+
}
|
|
5578
|
+
startOperation.throwIfAborted();
|
|
5579
|
+
const delay = logScale.shift() || 1000;
|
|
5580
|
+
logger.debug(`Waiting ${delay}ms`);
|
|
5581
|
+
await new Promise((x) => setTimeout(x, delay));
|
|
5582
|
+
}
|
|
5583
|
+
})();
|
|
5584
|
+
|
|
5585
|
+
try {
|
|
5586
|
+
await Promise.race([errorPromise, startedPromise]);
|
|
5587
|
+
} catch (e) {
|
|
5588
|
+
if (Abort.isAbortError(e)) {
|
|
5589
|
+
if (timeoutAbortSource.signal.aborted) {
|
|
5590
|
+
// aborted by timeout
|
|
5591
|
+
throw new Error(
|
|
5592
|
+
`"${webServer.command}" command did not start a server in less than ${allocatedMs}ms`,
|
|
5593
|
+
);
|
|
5594
|
+
}
|
|
5595
|
+
if (signal.aborted) {
|
|
5596
|
+
// aborted from outside
|
|
5597
|
+
return;
|
|
5598
|
+
}
|
|
5599
|
+
}
|
|
5600
|
+
throw e;
|
|
5601
|
+
} finally {
|
|
5602
|
+
await startOperation.end();
|
|
5638
5603
|
}
|
|
5639
|
-
return section;
|
|
5640
5604
|
};
|
|
5641
5605
|
|
|
5642
|
-
const
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
const value = data[key];
|
|
5646
|
-
let valueString = "";
|
|
5647
|
-
valueString += ANSI.color(`${key}:`, ANSI.GREY);
|
|
5648
|
-
const useNonGreyAnsiColor =
|
|
5649
|
-
typeof value === "string" && value.includes("\x1b");
|
|
5650
|
-
valueString += " ";
|
|
5651
|
-
valueString += useNonGreyAnsiColor
|
|
5652
|
-
? value
|
|
5653
|
-
: ANSI.color(String(value), ANSI.GREY);
|
|
5654
|
-
details.push(valueString);
|
|
5655
|
-
}
|
|
5656
|
-
if (details.length === 0) {
|
|
5657
|
-
return "";
|
|
5606
|
+
const startServerUsingModuleUrl = async (webServer, params) => {
|
|
5607
|
+
if (!existsSync(new URL(webServer.moduleUrl))) {
|
|
5608
|
+
throw new Error(`"${webServer.moduleUrl}" does not lead to a file`);
|
|
5658
5609
|
}
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5610
|
+
return startServerUsingCommand(
|
|
5611
|
+
{
|
|
5612
|
+
...webServer,
|
|
5613
|
+
command: `node ${fileURLToPath(webServer.moduleUrl)}`,
|
|
5614
|
+
},
|
|
5615
|
+
params,
|
|
5616
|
+
);
|
|
5665
5617
|
};
|
|
5666
5618
|
|
|
5667
|
-
const
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5619
|
+
const assertAndNormalizeWebServer = async (
|
|
5620
|
+
webServer,
|
|
5621
|
+
{ signal, logger, teardownCallbackSet },
|
|
5622
|
+
) => {
|
|
5623
|
+
if (!webServer) {
|
|
5624
|
+
throw new TypeError(
|
|
5625
|
+
`webServer is required when running tests on browser(s)`,
|
|
5626
|
+
);
|
|
5627
|
+
}
|
|
5628
|
+
const unexpectedParamNames = Object.keys(webServer).filter((key) => {
|
|
5629
|
+
return ![
|
|
5630
|
+
"origin",
|
|
5631
|
+
"moduleUrl",
|
|
5632
|
+
"command",
|
|
5633
|
+
"cwd",
|
|
5634
|
+
"rootDirectoryUrl",
|
|
5635
|
+
].includes(key);
|
|
5636
|
+
});
|
|
5637
|
+
if (unexpectedParamNames.length > 0) {
|
|
5638
|
+
throw new TypeError(
|
|
5639
|
+
`${unexpectedParamNames.join(",")}: there is no such param to webServer`,
|
|
5640
|
+
);
|
|
5641
|
+
}
|
|
5642
|
+
if (typeof webServer.origin !== "string") {
|
|
5643
|
+
throw new TypeError(
|
|
5644
|
+
`webServer.origin must be a string, got ${webServer.origin}`,
|
|
5645
|
+
);
|
|
5646
|
+
}
|
|
5647
|
+
await ensureWebServerIsStarted(webServer, {
|
|
5648
|
+
signal,
|
|
5649
|
+
teardownCallbackSet,
|
|
5650
|
+
logger,
|
|
5651
|
+
});
|
|
5652
|
+
const { headers } = await basicFetch(webServer.origin, {
|
|
5653
|
+
method: "GET",
|
|
5654
|
+
rejectUnauthorized: false,
|
|
5655
|
+
headers: {
|
|
5656
|
+
"x-server-inspect": "1",
|
|
5657
|
+
},
|
|
5658
|
+
});
|
|
5659
|
+
if (String(headers["server"]).includes("jsenv_dev_server")) {
|
|
5660
|
+
webServer.isJsenvDevServer = true;
|
|
5661
|
+
const { json } = await basicFetch(`${webServer.origin}/__params__.json`, {
|
|
5662
|
+
rejectUnauthorized: false,
|
|
5663
|
+
});
|
|
5664
|
+
if (webServer.rootDirectoryUrl === undefined) {
|
|
5665
|
+
const jsenvDevServerParams = await json();
|
|
5666
|
+
webServer.rootDirectoryUrl = jsenvDevServerParams.sourceDirectoryUrl;
|
|
5678
5667
|
} else {
|
|
5679
|
-
|
|
5680
|
-
|
|
5668
|
+
webServer.rootDirectoryUrl = assertAndNormalizeDirectoryUrl(
|
|
5669
|
+
webServer.rootDirectoryUrl,
|
|
5670
|
+
"webServer.rootDirectoryUrl",
|
|
5671
|
+
);
|
|
5681
5672
|
}
|
|
5682
|
-
} else if (dividend === 1) {
|
|
5683
|
-
from = 0;
|
|
5684
|
-
to = groupSize;
|
|
5685
5673
|
} else {
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
to = from + groupSize;
|
|
5691
|
-
}
|
|
5674
|
+
webServer.rootDirectoryUrl = assertAndNormalizeDirectoryUrl(
|
|
5675
|
+
webServer.rootDirectoryUrl,
|
|
5676
|
+
"webServer.rootDirectoryUrl",
|
|
5677
|
+
);
|
|
5692
5678
|
}
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
}
|
|
5679
|
+
};
|
|
5680
|
+
|
|
5681
|
+
const ensureWebServerIsStarted = async (
|
|
5682
|
+
webServer,
|
|
5683
|
+
{ signal, teardownCallbackSet, logger, allocatedMs = 5_000 },
|
|
5684
|
+
) => {
|
|
5685
|
+
const aServerIsListening = await pingServer(webServer.origin);
|
|
5686
|
+
if (aServerIsListening) {
|
|
5687
|
+
return;
|
|
5688
|
+
}
|
|
5689
|
+
if (webServer.moduleUrl) {
|
|
5690
|
+
await startServerUsingModuleUrl(webServer, {
|
|
5691
|
+
signal,
|
|
5692
|
+
allocatedMs,
|
|
5693
|
+
teardownCallbackSet,
|
|
5694
|
+
logger,
|
|
5695
|
+
});
|
|
5696
|
+
return;
|
|
5697
|
+
}
|
|
5698
|
+
if (webServer.command) {
|
|
5699
|
+
await startServerUsingCommand(webServer, {
|
|
5700
|
+
signal,
|
|
5701
|
+
allocatedMs,
|
|
5702
|
+
teardownCallbackSet,
|
|
5703
|
+
logger,
|
|
5704
|
+
});
|
|
5705
|
+
return;
|
|
5706
|
+
}
|
|
5707
|
+
throw new TypeError(
|
|
5708
|
+
`webServer.moduleUrl or webServer.command is required as there is no server listening "${webServer.origin}"`,
|
|
5709
|
+
);
|
|
5698
5710
|
};
|
|
5699
5711
|
|
|
5700
5712
|
/*
|
|
@@ -6922,6 +6934,63 @@ const WEB_URL_CONVERTER = {
|
|
|
6922
6934
|
},
|
|
6923
6935
|
};
|
|
6924
6936
|
|
|
6937
|
+
const initIstanbulMiddleware = async (
|
|
6938
|
+
page,
|
|
6939
|
+
{ webServer, rootDirectoryUrl, coverageInclude },
|
|
6940
|
+
) => {
|
|
6941
|
+
const associations = URL_META.resolveAssociations(
|
|
6942
|
+
{ cover: coverageInclude },
|
|
6943
|
+
rootDirectoryUrl,
|
|
6944
|
+
);
|
|
6945
|
+
await page.route("**", async (route) => {
|
|
6946
|
+
const request = route.request();
|
|
6947
|
+
const url = request.url(); // transform into a local url
|
|
6948
|
+
const fileUrl = WEB_URL_CONVERTER.asFileUrl(url, webServer);
|
|
6949
|
+
const needsInstrumentation = URL_META.applyAssociations({
|
|
6950
|
+
url: fileUrl,
|
|
6951
|
+
associations,
|
|
6952
|
+
}).cover;
|
|
6953
|
+
if (!needsInstrumentation) {
|
|
6954
|
+
route.fallback();
|
|
6955
|
+
return;
|
|
6956
|
+
}
|
|
6957
|
+
const response = await route.fetch();
|
|
6958
|
+
const originalBody = await response.text();
|
|
6959
|
+
try {
|
|
6960
|
+
const result = await applyBabelPlugins({
|
|
6961
|
+
babelPlugins: [babelPluginInstrument],
|
|
6962
|
+
input: originalBody,
|
|
6963
|
+
// jsenv server could send info to know it's a js module or js classic
|
|
6964
|
+
// but in the end it's not super important
|
|
6965
|
+
// - it's ok to parse js classic as js module considering it's only for istanbul instrumentation
|
|
6966
|
+
inputIsJsModule: true,
|
|
6967
|
+
inputUrl: fileUrl,
|
|
6968
|
+
});
|
|
6969
|
+
let code = result.code;
|
|
6970
|
+
code = SOURCEMAP.writeComment({
|
|
6971
|
+
contentType: "text/javascript",
|
|
6972
|
+
content: code,
|
|
6973
|
+
specifier: generateSourcemapDataUrl(result.map),
|
|
6974
|
+
});
|
|
6975
|
+
route.fulfill({
|
|
6976
|
+
response,
|
|
6977
|
+
body: code,
|
|
6978
|
+
headers: {
|
|
6979
|
+
...response.headers(),
|
|
6980
|
+
"content-length": Buffer.byteLength(code),
|
|
6981
|
+
},
|
|
6982
|
+
});
|
|
6983
|
+
} catch (e) {
|
|
6984
|
+
if (e.code === "PARSE_ERROR") {
|
|
6985
|
+
route.fulfill({ response });
|
|
6986
|
+
} else {
|
|
6987
|
+
console.error(e);
|
|
6988
|
+
route.fulfill({ response });
|
|
6989
|
+
}
|
|
6990
|
+
}
|
|
6991
|
+
});
|
|
6992
|
+
};
|
|
6993
|
+
|
|
6925
6994
|
const initJsSupervisorMiddleware = async (
|
|
6926
6995
|
page,
|
|
6927
6996
|
{ webServer, fileUrl, fileServerUrl },
|
|
@@ -7014,63 +7083,6 @@ const initJsSupervisorMiddleware = async (
|
|
|
7014
7083
|
});
|
|
7015
7084
|
};
|
|
7016
7085
|
|
|
7017
|
-
const initIstanbulMiddleware = async (
|
|
7018
|
-
page,
|
|
7019
|
-
{ webServer, rootDirectoryUrl, coverageInclude },
|
|
7020
|
-
) => {
|
|
7021
|
-
const associations = URL_META.resolveAssociations(
|
|
7022
|
-
{ cover: coverageInclude },
|
|
7023
|
-
rootDirectoryUrl,
|
|
7024
|
-
);
|
|
7025
|
-
await page.route("**", async (route) => {
|
|
7026
|
-
const request = route.request();
|
|
7027
|
-
const url = request.url(); // transform into a local url
|
|
7028
|
-
const fileUrl = WEB_URL_CONVERTER.asFileUrl(url, webServer);
|
|
7029
|
-
const needsInstrumentation = URL_META.applyAssociations({
|
|
7030
|
-
url: fileUrl,
|
|
7031
|
-
associations,
|
|
7032
|
-
}).cover;
|
|
7033
|
-
if (!needsInstrumentation) {
|
|
7034
|
-
route.fallback();
|
|
7035
|
-
return;
|
|
7036
|
-
}
|
|
7037
|
-
const response = await route.fetch();
|
|
7038
|
-
const originalBody = await response.text();
|
|
7039
|
-
try {
|
|
7040
|
-
const result = await applyBabelPlugins({
|
|
7041
|
-
babelPlugins: [babelPluginInstrument],
|
|
7042
|
-
input: originalBody,
|
|
7043
|
-
// jsenv server could send info to know it's a js module or js classic
|
|
7044
|
-
// but in the end it's not super important
|
|
7045
|
-
// - it's ok to parse js classic as js module considering it's only for istanbul instrumentation
|
|
7046
|
-
inputIsJsModule: true,
|
|
7047
|
-
inputUrl: fileUrl,
|
|
7048
|
-
});
|
|
7049
|
-
let code = result.code;
|
|
7050
|
-
code = SOURCEMAP.writeComment({
|
|
7051
|
-
contentType: "text/javascript",
|
|
7052
|
-
content: code,
|
|
7053
|
-
specifier: generateSourcemapDataUrl(result.map),
|
|
7054
|
-
});
|
|
7055
|
-
route.fulfill({
|
|
7056
|
-
response,
|
|
7057
|
-
body: code,
|
|
7058
|
-
headers: {
|
|
7059
|
-
...response.headers(),
|
|
7060
|
-
"content-length": Buffer.byteLength(code),
|
|
7061
|
-
},
|
|
7062
|
-
});
|
|
7063
|
-
} catch (e) {
|
|
7064
|
-
if (e.code === "PARSE_ERROR") {
|
|
7065
|
-
route.fulfill({ response });
|
|
7066
|
-
} else {
|
|
7067
|
-
console.error(e);
|
|
7068
|
-
route.fulfill({ response });
|
|
7069
|
-
}
|
|
7070
|
-
}
|
|
7071
|
-
});
|
|
7072
|
-
};
|
|
7073
|
-
|
|
7074
7086
|
const browserPromiseCache = new Map();
|
|
7075
7087
|
|
|
7076
7088
|
const createRuntimeUsingPlaywright = ({
|
|
@@ -8006,6 +8018,21 @@ const getDebugInfo = (processOptions) => {
|
|
|
8006
8018
|
// return typeof process.env.VSCODE_PID === "string"
|
|
8007
8019
|
// }
|
|
8008
8020
|
|
|
8021
|
+
// https://nodejs.org/api/process.html#process_signal_events
|
|
8022
|
+
const SIGINT_SIGNAL_NUMBER = 2;
|
|
8023
|
+
const SIGABORT_SIGNAL_NUMBER = 6;
|
|
8024
|
+
const SIGTERM_SIGNAL_NUMBER = 15;
|
|
8025
|
+
const EXIT_CODES = {
|
|
8026
|
+
SIGINT: 128 + SIGINT_SIGNAL_NUMBER,
|
|
8027
|
+
SIGABORT: 128 + SIGABORT_SIGNAL_NUMBER,
|
|
8028
|
+
SIGTERM: 128 + SIGTERM_SIGNAL_NUMBER,
|
|
8029
|
+
};
|
|
8030
|
+
|
|
8031
|
+
const IMPORTMAP_NODE_LOADER_FILE_URL = new URL(
|
|
8032
|
+
"./importmap_node_loader.mjs",
|
|
8033
|
+
import.meta.url,
|
|
8034
|
+
).href;
|
|
8035
|
+
|
|
8009
8036
|
// see also https://github.com/sindresorhus/execa/issues/96
|
|
8010
8037
|
const killProcessTree = async (
|
|
8011
8038
|
processId,
|
|
@@ -8081,21 +8108,6 @@ const killProcessTree = async (
|
|
|
8081
8108
|
await check();
|
|
8082
8109
|
};
|
|
8083
8110
|
|
|
8084
|
-
// https://nodejs.org/api/process.html#process_signal_events
|
|
8085
|
-
const SIGINT_SIGNAL_NUMBER = 2;
|
|
8086
|
-
const SIGABORT_SIGNAL_NUMBER = 6;
|
|
8087
|
-
const SIGTERM_SIGNAL_NUMBER = 15;
|
|
8088
|
-
const EXIT_CODES = {
|
|
8089
|
-
SIGINT: 128 + SIGINT_SIGNAL_NUMBER,
|
|
8090
|
-
SIGABORT: 128 + SIGABORT_SIGNAL_NUMBER,
|
|
8091
|
-
SIGTERM: 128 + SIGTERM_SIGNAL_NUMBER,
|
|
8092
|
-
};
|
|
8093
|
-
|
|
8094
|
-
const IMPORTMAP_NODE_LOADER_FILE_URL = new URL(
|
|
8095
|
-
"./importmap_node_loader.mjs",
|
|
8096
|
-
import.meta.url,
|
|
8097
|
-
).href;
|
|
8098
|
-
|
|
8099
8111
|
const NO_EXPERIMENTAL_WARNING_FILE_URL = new URL(
|
|
8100
8112
|
"./no_experimental_warnings.cjs",
|
|
8101
8113
|
import.meta.url,
|
|
@@ -8973,24 +8985,39 @@ const istanbulCoverageMapFromCoverage = (coverage) => {
|
|
|
8973
8985
|
return coverageMap;
|
|
8974
8986
|
};
|
|
8975
8987
|
|
|
8976
|
-
const
|
|
8988
|
+
const reportCoverageAsHtml = (
|
|
8977
8989
|
testPlanResult,
|
|
8990
|
+
directoryUrl,
|
|
8978
8991
|
{ skipEmpty, skipFull } = {},
|
|
8979
8992
|
) => {
|
|
8980
8993
|
if (testPlanResult.aborted) {
|
|
8981
8994
|
return;
|
|
8982
8995
|
}
|
|
8983
8996
|
const testPlanCoverage = testPlanResult.coverage;
|
|
8997
|
+
|
|
8998
|
+
const { rootDirectoryUrl } = testPlanResult;
|
|
8999
|
+
ensureEmptyDirectorySync(directoryUrl);
|
|
9000
|
+
const coverageHtmlDirectoryRelativeUrl = urlToRelativeUrl(
|
|
9001
|
+
directoryUrl,
|
|
9002
|
+
rootDirectoryUrl,
|
|
9003
|
+
);
|
|
9004
|
+
|
|
8984
9005
|
const libReport = importWithRequire("istanbul-lib-report");
|
|
8985
9006
|
const reports = importWithRequire("istanbul-reports");
|
|
8986
9007
|
const context = libReport.createContext({
|
|
9008
|
+
dir: fileURLToPath(rootDirectoryUrl),
|
|
8987
9009
|
coverageMap: istanbulCoverageMapFromCoverage(testPlanCoverage),
|
|
9010
|
+
sourceFinder: (path) =>
|
|
9011
|
+
readFileSync(new URL(path, rootDirectoryUrl), "utf8"),
|
|
8988
9012
|
});
|
|
8989
|
-
const report = reports.create("
|
|
9013
|
+
const report = reports.create("html", {
|
|
8990
9014
|
skipEmpty,
|
|
8991
9015
|
skipFull,
|
|
9016
|
+
subdir: coverageHtmlDirectoryRelativeUrl,
|
|
8992
9017
|
});
|
|
8993
9018
|
report.execute(context);
|
|
9019
|
+
// const htmlCoverageDirectoryIndexFileUrl = `${directoryUrl}index.html`;
|
|
9020
|
+
// console.log(`-> ${urlToFileSystemPath(htmlCoverageDirectoryIndexFileUrl)}`);
|
|
8994
9021
|
};
|
|
8995
9022
|
|
|
8996
9023
|
const reportCoverageAsJson = (
|
|
@@ -9013,39 +9040,24 @@ const reportCoverageAsJson = (
|
|
|
9013
9040
|
}
|
|
9014
9041
|
};
|
|
9015
9042
|
|
|
9016
|
-
const
|
|
9043
|
+
const reportCoverageInConsole = (
|
|
9017
9044
|
testPlanResult,
|
|
9018
|
-
directoryUrl,
|
|
9019
9045
|
{ skipEmpty, skipFull } = {},
|
|
9020
9046
|
) => {
|
|
9021
9047
|
if (testPlanResult.aborted) {
|
|
9022
9048
|
return;
|
|
9023
9049
|
}
|
|
9024
9050
|
const testPlanCoverage = testPlanResult.coverage;
|
|
9025
|
-
|
|
9026
|
-
const { rootDirectoryUrl } = testPlanResult;
|
|
9027
|
-
ensureEmptyDirectorySync(directoryUrl);
|
|
9028
|
-
const coverageHtmlDirectoryRelativeUrl = urlToRelativeUrl(
|
|
9029
|
-
directoryUrl,
|
|
9030
|
-
rootDirectoryUrl,
|
|
9031
|
-
);
|
|
9032
|
-
|
|
9033
9051
|
const libReport = importWithRequire("istanbul-lib-report");
|
|
9034
9052
|
const reports = importWithRequire("istanbul-reports");
|
|
9035
9053
|
const context = libReport.createContext({
|
|
9036
|
-
dir: fileURLToPath(rootDirectoryUrl),
|
|
9037
9054
|
coverageMap: istanbulCoverageMapFromCoverage(testPlanCoverage),
|
|
9038
|
-
sourceFinder: (path) =>
|
|
9039
|
-
readFileSync(new URL(path, rootDirectoryUrl), "utf8"),
|
|
9040
9055
|
});
|
|
9041
|
-
const report = reports.create("
|
|
9056
|
+
const report = reports.create("text", {
|
|
9042
9057
|
skipEmpty,
|
|
9043
9058
|
skipFull,
|
|
9044
|
-
subdir: coverageHtmlDirectoryRelativeUrl,
|
|
9045
9059
|
});
|
|
9046
9060
|
report.execute(context);
|
|
9047
|
-
// const htmlCoverageDirectoryIndexFileUrl = `${directoryUrl}index.html`;
|
|
9048
|
-
// console.log(`-> ${urlToFileSystemPath(htmlCoverageDirectoryIndexFileUrl)}`);
|
|
9049
9061
|
};
|
|
9050
9062
|
|
|
9051
9063
|
const createXmlGenerator = ({
|