@jsenv/test 3.3.12 → 3.3.13

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.
Files changed (32) hide show
  1. package/dist/js/execute_using_dynamic_import.js +58 -58
  2. package/dist/jsenv_test.js +1458 -1458
  3. package/dist/node_child_process_controlled.mjs +1 -1
  4. package/dist/node_worker_thread_controlled.mjs +2 -2
  5. package/package.json +8 -8
  6. package/src/coverage/empty_coverage_factory.js +2 -2
  7. package/src/coverage/file_by_file_coverage.js +1 -1
  8. package/src/coverage/generate_coverage.js +6 -6
  9. package/src/coverage/missing_coverage.js +1 -1
  10. package/src/coverage/report_coverage_as_html.js +3 -3
  11. package/src/coverage/report_coverage_as_json.js +1 -1
  12. package/src/coverage/v8_coverage_node_directory.js +2 -2
  13. package/src/coverage/v8_coverage_to_istanbul.js +1 -1
  14. package/src/execution/exception.js +1 -1
  15. package/src/execution/execute.js +1 -1
  16. package/src/execution/execute_test_plan.js +20 -20
  17. package/src/execution/format_error_for_terminal.js +2 -2
  18. package/src/execution/reporters/reporter_list.js +3 -3
  19. package/src/execution/run.js +1 -1
  20. package/src/execution/web_server/start_using_command.js +1 -1
  21. package/src/execution/web_server_param.js +3 -3
  22. package/src/helpers/cpu_usage.test_manual.js +1 -1
  23. package/src/helpers/web_url_converter.js +1 -1
  24. package/src/main.js +4 -2
  25. package/src/runtime_browsers/middleware_istanbul.js +2 -2
  26. package/src/runtime_browsers/middleware_js_supervisor.js +2 -2
  27. package/src/runtime_browsers/using_playwright.js +5 -5
  28. package/src/runtime_node/execute_using_dynamic_import.js +2 -2
  29. package/src/runtime_node/importmap_hooks.mjs +1 -1
  30. package/src/runtime_node/node_child_process.js +4 -4
  31. package/src/runtime_node/node_worker_thread.js +3 -3
  32. package/src/runtime_node/node_worker_thread_controlled.mjs +1 -1
@@ -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 { 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";
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 { filterV8Coverage } from "./js/v8_coverage.js";
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 { spawn, spawnSync, fork } from "node:child_process";
15
+ import { filterV8Coverage } from "./js/v8_coverage.js";
17
16
  import { createServer } from "node:net";
18
- import { injectSupervisorIntoHTML, supervisorFileUrl } from "@jsenv/plugin-supervisor";
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
- // https://gist.github.com/GaetanoPiazzolla/c40e1ebb9f709d091208e89baf9f4e00
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 startMeasuringTotalCpuUsage = () => {
3104
- let previousCpuArray = cpus();
3105
- let previousMs = Date.now();
3106
- let previousCpuUsage = cpuUsage();
3120
+ const importWithRequire = createRequire(import.meta.url);
3107
3121
 
3108
- const overall = {
3109
- inactive: 100,
3110
- active: 0,
3111
- system: 0,
3112
- user: 0,
3113
- };
3114
- const thisProcess = {
3115
- active: 0,
3116
- system: 0,
3117
- user: 0,
3118
- };
3119
- const details = previousCpuArray.map(() => {
3120
- return {
3121
- inactive: 100,
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
- const samples = [];
3129
- const interval = setInterval(() => {
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
- overallSystemMs += systemMs;
3156
- overallUserMs += userMs;
3157
- overallInactiveMs += inactiveMs;
3158
- overallActiveMs += activeMs;
3159
- overallMsEllapsed += ellapsedMs;
3160
- index++;
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
- const processCpuUsage = cpuUsage();
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
- const thisProcessActiveMs = thisProcessSystemMs + thisProcessUserMs;
3181
- const thisProcessInactiveMs = overallMsEllapsed - thisProcessActiveMs;
3182
- const thisProcessSample = {
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
- overall,
3263
- thisProcess,
3264
- details,
3265
- stop: () => {
3266
- clearInterval(interval);
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 createCallOrderer = () => {
3272
- const queue = [];
3273
- const callWhenPreviousExecutionAreDone = (executionIndex, callback) => {
3274
- if (queue[executionIndex]) {
3275
- throw new Error(`${executionIndex} already used`);
3276
- }
3198
+ const relativeUrlToEmptyCoverage = async (
3199
+ relativeUrl,
3200
+ { signal, rootDirectoryUrl },
3201
+ ) => {
3202
+ const operation = Abort.startOperation();
3203
+ operation.addAbortSignal(signal);
3277
3204
 
3278
- let allBeforeAreDone = true;
3279
- if (executionIndex > 0) {
3280
- let beforeIndex = executionIndex - 1;
3281
- do {
3282
- const value = queue[beforeIndex];
3283
- if (!value) {
3284
- allBeforeAreDone = false;
3285
- break;
3286
- }
3287
- } while (beforeIndex--);
3288
- }
3289
- if (!allBeforeAreDone) {
3290
- queue[executionIndex] = callback;
3291
- return;
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
- queue[executionIndex] = true;
3294
- callback();
3295
- let afterIndex = executionIndex + 1;
3296
- while (afterIndex < queue.length) {
3297
- const value = queue[afterIndex];
3298
- if (value === undefined) {
3299
- break;
3300
- }
3301
- if (typeof value === "function") {
3302
- queue[afterIndex] = true;
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
- return callWhenPreviousExecutionAreDone;
3231
+ throw e;
3232
+ } finally {
3233
+ await operation.end();
3234
+ }
3309
3235
  };
3310
3236
 
3311
- const readNodeV8CoverageDirectory = async ({
3312
- logger,
3313
- warn,
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
- onV8Coverage,
3316
- maxMsWaitingForNodeToWriteCoverageFile = 2000,
3244
+ rootDirectoryUrl,
3245
+ coverageInclude,
3317
3246
  }) => {
3318
- const NODE_V8_COVERAGE = process.env.NODE_V8_COVERAGE;
3319
- const operation = Abort.startOperation();
3320
- operation.addAbortSignal(signal);
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
- let timeSpentTrying = 0;
3323
- const tryReadDirectory = async () => {
3324
- const dirContent = readdirSync(NODE_V8_COVERAGE);
3325
- if (dirContent.length > 0) {
3326
- return dirContent;
3327
- }
3328
- if (timeSpentTrying < maxMsWaitingForNodeToWriteCoverageFile) {
3329
- await new Promise((resolve) => setTimeout(resolve, 200));
3330
- timeSpentTrying += 200;
3331
- logger.debug("retry to read coverage directory");
3332
- return tryReadDirectory();
3333
- }
3334
- warn({
3335
- code: "V8_COVERAGE_EMPTY",
3336
- message: `v8 coverage directory is empty at ${NODE_V8_COVERAGE}`,
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
- try {
3273
+ const operation = Abort.startOperation();
3274
+ operation.addAbortSignal(signal);
3275
+ const missingFileByFileCoverage = {};
3276
+ await relativeUrlsMissing.reduce(async (previous, relativeUrlMissing) => {
3342
3277
  operation.throwIfAborted();
3343
- const dirContent = await tryReadDirectory();
3344
-
3345
- const coverageDirectoryUrl = assertAndNormalizeDirectoryUrl(
3346
- NODE_V8_COVERAGE,
3347
- "NODE_V8_COVERAGE",
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
- await dirContent.reduce(async (previous, dirEntry) => {
3351
- operation.throwIfAborted();
3352
- await previous;
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
- const dirEntryUrl = new URL(dirEntry, coverageDirectoryUrl);
3355
- const tryReadJsonFile = async () => {
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 composeV8AndIstanbul = (
3543
- v8FileByFileCoverage,
3544
- istanbulFileByFileCoverage,
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
- const fileByFileNormalized = {};
3585
- Object.keys(fileByFileCoverage).forEach((key) => {
3586
- const fileCoverage = fileByFileCoverage[key];
3587
- const { path } = fileCoverage;
3588
- const url = isFileSystemPath(path)
3589
- ? fileSystemPathToUrl(path)
3590
- : new URL(path, rootDirectoryUrl).href;
3591
- const relativeUrl = urlToRelativeUrl(url, rootDirectoryUrl);
3592
- fileByFileNormalized[`./${relativeUrl}`] = {
3593
- ...fileCoverage,
3594
- path: `./${relativeUrl}`,
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
- const listRelativeFileUrlToCover = async ({
3601
- signal,
3602
- rootDirectoryUrl,
3603
- coverageInclude,
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
- // https://github.com/istanbuljs/babel-plugin-istanbul/blob/321740f7b25d803f881466ea819d870f7ed6a254/src/index.js
3554
+ // 3. the file we wanted to execute contains syntax-error
3615
3555
 
3616
- const babelPluginInstrument = (api, { useInlineSourceMaps = false }) => {
3617
- const { programVisitor } = importWithRequire("istanbul-lib-instrument");
3618
- const { types } = api;
3619
-
3620
- return {
3621
- name: "transform-instrument",
3622
- visitor: {
3623
- Program: {
3624
- enter(path, state) {
3625
- const { file } = state;
3626
- const { opts } = file;
3627
- let inputSourceMap;
3628
- if (useInlineSourceMaps) {
3629
- // https://github.com/istanbuljs/babel-plugin-istanbul/commit/a9e15643d249a2985e4387e4308022053b2cd0ad#diff-1fdf421c05c1140f6d71444ea2b27638R65
3630
- inputSourceMap =
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 pingServer = async (url) => {
3925
- const server = createServer();
3926
- const { hostname, port } = new URL(url);
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
- try {
3929
- await new Promise((resolve, reject) => {
3930
- server.on("error", reject);
3931
- server.on("listening", () => {
3932
- resolve();
3933
- });
3934
- server.listen(port, hostname);
3935
- });
3936
- } catch (error) {
3937
- if (error && error.code === "EADDRINUSE") {
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 (error && error.code === "EACCES") {
3941
- return true;
3731
+ if (!allBeforeAreDone) {
3732
+ queue[executionIndex] = callback;
3733
+ return;
3942
3734
  }
3943
- throw error;
3944
- }
3945
- await new Promise((resolve, reject) => {
3946
- server.on("error", reject);
3947
- server.on("close", resolve);
3948
- server.close();
3949
- });
3950
- return false;
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
- const startServerUsingCommand = async (
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
- // const stdout = readline.createInterface({ input: spawnedProcess.stdout });
3983
- // stdout.on("line", () => {
3984
- // logger.debug(`[pid=${spawnedProcess.pid}][out] ${data}`);
3985
- // });
3986
- // const stderr = readline.createInterface({ input: spawnedProcess.stderr });
3987
- // stderr.on("line", (data) => {
3988
- // logger.debug(`[pid=${spawnedProcess.pid}][err] ${data}`);
3989
- // });
3990
- let processClosed = false;
3991
- const closedPromise = new Promise((resolve) => {
3992
- spawnedProcess.once("exit", (exitCode, signal) => {
3993
- logger.debug(
3994
- `[pid=${spawnedProcess.pid}] <process did exit: exitCode=${exitCode}, signal=${signal}>`,
3995
- );
3996
- processClosed = true;
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 startOperation = Abort.startOperation();
4039
- startOperation.addAbortSignal(signal);
4040
- const timeoutAbortSource = startOperation.timeout(allocatedMs);
4041
- startOperation.addAbortCallback(killProcess);
4042
- teardownCallbackSet.add(killProcess);
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
- const startedPromise = (async () => {
4045
- const logScale = [100, 250, 500];
4046
- // eslint-disable-next-line no-constant-condition
4047
- while (true) {
4048
- if (errorReceived) {
4049
- break;
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
- try {
4063
- await Promise.race([errorPromise, startedPromise]);
4064
- } catch (e) {
4065
- if (Abort.isAbortError(e)) {
4066
- if (timeoutAbortSource.signal.aborted) {
4067
- // aborted by timeout
4068
- throw new Error(
4069
- `"${webServer.command}" command did not start a server in less than ${allocatedMs}ms`,
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
- if (signal.aborted) {
4073
- // aborted from outside
4074
- return;
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
- throw e;
4078
- } finally {
4079
- await startOperation.end();
4080
- }
4081
- };
3911
+ }, 15);
3912
+ interval.unref();
4082
3913
 
4083
- const startServerUsingModuleUrl = async (webServer, params) => {
4084
- if (!existsSync(new URL(webServer.moduleUrl))) {
4085
- throw new Error(`"${webServer.moduleUrl}" does not lead to a file`);
4086
- }
4087
- return startServerUsingCommand(
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
- params,
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
- * Export a function capable to run a file on a runtime.
4376
- *
4377
- * - Used internally by "executeTestPlan" part of the documented API
4378
- * - Used internally by "execute" an advanced API not documented
4379
- * - logs generated during file execution can be collected
4380
- * - logs generated during file execution can be mirrored (re-logged to the console)
4381
- * - File is given allocatedMs to complete
4382
- * - Errors are collected
4383
- * - File execution result is returned, it contains status/errors/namespace/consoleCalls
4384
- */
4385
-
4386
-
4387
- const run = async ({
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
- if (collectConsole) {
4451
- consoleCalls.push({ type, text });
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
- // we do not keep coverage in memory, it can grow very big
4459
- // instead we store it on the filesystem
4460
- // and they can be read later at "coverageFileUrl"
4461
- let coverageFileUrl;
4462
- if (coverageEnabled) {
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)
@@ -5385,316 +4878,823 @@ const prefixFirstAndIndentRemainingLines = ({
5385
4878
  ? ""
5386
4879
  : `\n`;
5387
4880
  }
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: " ",
4881
+ return result;
4882
+ };
4883
+ const CONSOLE_ICONS = {
4884
+ get debug() {
4885
+ return UNICODE.DEBUG;
4886
+ },
4887
+ get info() {
4888
+ return UNICODE.INFO;
4889
+ },
4890
+ get warning() {
4891
+ return UNICODE.WARNING;
4892
+ },
4893
+ get error() {
4894
+ return UNICODE.FAILURE;
4895
+ },
4896
+ log: " ",
4897
+ };
4898
+
4899
+ const renderErrors = (execution, logOptions) => {
4900
+ const { errors = [] } = execution.result;
4901
+ if (errors.length === 0) {
4902
+ return "";
4903
+ }
4904
+
4905
+ if (errors.length === 1) {
4906
+ return renderSection({
4907
+ dashColor: ANSI.GREY,
4908
+ title: "error",
4909
+ content: formatErrorForTerminal(errors[0], {
4910
+ rootDirectoryUrl: execution.rootDirectoryUrl,
4911
+ mainFileRelativeUrl: execution.fileRelativeUrl,
4912
+ mockFluctuatingValues: logOptions.mockFluctuatingValues,
4913
+ tryColors: true,
4914
+ }),
4915
+ });
4916
+ }
4917
+
4918
+ let output = [];
4919
+ errors.forEach((error) => {
4920
+ output.push(
4921
+ prefixFirstAndIndentRemainingLines({
4922
+ prefix: `${UNICODE.CIRCLE_CROSS} `,
4923
+ indentation: " ",
4924
+ text: formatErrorForTerminal(error, {
4925
+ rootDirectoryUrl: execution.rootDirectoryUrl,
4926
+ mainFileRelativeUrl: execution.fileRelativeUrl,
4927
+ mockFluctuatingValues: logOptions.mockFluctuatingValues,
4928
+ tryColors: true,
4929
+ }),
4930
+ }),
4931
+ );
4932
+ });
4933
+ return renderSection({
4934
+ dashColor: ANSI.GREY,
4935
+ title: `errors (${errors.length})`,
4936
+ content: output.join(`\n`),
4937
+ });
4938
+ };
4939
+
4940
+ const renderOutro = (testPlanResult, logOptions = {}) => {
4941
+ const { counters } = testPlanResult;
4942
+ const { planified } = counters;
4943
+ if (planified === 0) {
4944
+ return "";
4945
+ }
4946
+ let title;
4947
+ if (planified === 0) {
4948
+ title = `no execution`;
4949
+ } else if (planified === 1) {
4950
+ title = `1 execution done`;
4951
+ } else {
4952
+ title = `${planified} executions done`;
4953
+ }
4954
+ return `${renderBigSection({
4955
+ title,
4956
+ content: renderOutroContent(testPlanResult, logOptions),
4957
+ })}\n`;
4958
+ };
4959
+
4960
+ const renderOutroContent = (testPlanResult, logOptions = {}) => {
4961
+ const lines = [];
4962
+ const { counters } = testPlanResult;
4963
+
4964
+ let executionLine = `status: ${renderStatusRepartition(counters)}`;
4965
+ lines.push(executionLine);
4966
+
4967
+ let durationLine = `duration: `;
4968
+ const { timings } = testPlanResult;
4969
+ if (logOptions.mockFluctuatingValues) {
4970
+ durationLine += "<mock>s";
4971
+ } else {
4972
+ durationLine += humanizeDuration(timings.end, { short: true });
4973
+ const namedTimings = {
4974
+ setup: humanizeTiming(timings.executionStart),
4975
+ execution: humanizeTiming(timings.executionEnd),
4976
+ teardown: humanizeTiming(timings.teardownEnd - timings.executionEnd),
4977
+ ...(testPlanResult.coverage
4978
+ ? {
4979
+ coverage: humanizeTiming(
4980
+ timings.coverageTeardownEnd - timings.teardownEnd,
4981
+ ),
4982
+ }
4983
+ : {}),
4984
+ };
4985
+ durationLine += renderDetails(namedTimings);
4986
+ }
4987
+ lines.push(durationLine);
4988
+
4989
+ if (logOptions.cpuUsage) {
4990
+ const processCpuUsage = testPlanResult.cpuUsage.process;
4991
+ let cpuUsageLine = "cpu: ";
4992
+ cpuUsageLine += `${humanizeProcessCpuUsage(processCpuUsage.end)}`;
4993
+ cpuUsageLine += renderDetails({
4994
+ med: humanizeProcessCpuUsage(processCpuUsage.median),
4995
+ min: humanizeProcessCpuUsage(processCpuUsage.min),
4996
+ max: humanizeProcessCpuUsage(processCpuUsage.max),
4997
+ });
4998
+ lines.push(cpuUsageLine);
4999
+ }
5000
+ if (logOptions.memoryUsage) {
5001
+ const processMemoryUsage = testPlanResult.memoryUsage.process;
5002
+ let memoryUsageLine = "memory: ";
5003
+ memoryUsageLine += `${humanizeProcessMemoryUsage(processMemoryUsage.end)}`;
5004
+ memoryUsageLine += renderDetails({
5005
+ med: humanizeProcessMemoryUsage(processMemoryUsage.median),
5006
+ min: humanizeProcessMemoryUsage(processMemoryUsage.min),
5007
+ max: humanizeProcessMemoryUsage(processMemoryUsage.max),
5008
+ });
5009
+ lines.push(memoryUsageLine);
5010
+ }
5011
+ return lines.join("\n");
5012
+ };
5013
+
5014
+ const humanizeTiming = (value) => {
5015
+ return humanizeDuration(value, { short: true });
5016
+ };
5017
+
5018
+ const humanizeProcessCpuUsage = (ratio) => {
5019
+ const percentageAsNumber = ratio * 100;
5020
+ const percentageAsNumberRounded = Math.round(percentageAsNumber);
5021
+ const percentage = `${percentageAsNumberRounded}%`;
5022
+ return percentage;
5023
+ };
5024
+
5025
+ const humanizeProcessMemoryUsage = (value) => {
5026
+ return humanizeMemory(value, { short: true, decimals: 0 });
5027
+ };
5028
+
5029
+ const renderStatusRepartition = (counters, { showProgression } = {}) => {
5030
+ if (counters.planified === 0) {
5031
+ return ``;
5032
+ }
5033
+ if (counters.skipped === counters.planified) {
5034
+ return `all ${ANSI.color(`skipped`, COLOR_SKIPPED)}`;
5035
+ }
5036
+ if (counters.aborted === counters.planified) {
5037
+ return `all ${ANSI.color(`aborted`, COLOR_ABORTED)}`;
5038
+ }
5039
+ if (counters.cancelled === counters.planified) {
5040
+ return `all ${ANSI.color(`cancelled`, COLOR_CANCELLED)}`;
5041
+ }
5042
+ if (counters.timedout === counters.planified) {
5043
+ return `all ${ANSI.color(`timed out`, COLOR_TIMEOUT)}`;
5044
+ }
5045
+ if (counters.failed === counters.planified) {
5046
+ return `all ${ANSI.color(`failed`, COLOR_FAILED)}`;
5047
+ }
5048
+ if (counters.completed === counters.planified) {
5049
+ return `all ${ANSI.color(`completed`, COLOR_COMPLETED)}`;
5050
+ }
5051
+ const parts = [];
5052
+ if (showProgression) {
5053
+ if (counters.executing) {
5054
+ parts.push(`${counters.executing} executing`);
5055
+ }
5056
+ if (counters.waiting) {
5057
+ parts.push(`${counters.waiting} waiting`);
5058
+ }
5059
+ }
5060
+ if (counters.timedout) {
5061
+ parts.push(
5062
+ `${counters.timedout} ${ANSI.color(`timed out`, COLOR_TIMEOUT)}`,
5063
+ );
5064
+ }
5065
+ if (counters.failed) {
5066
+ parts.push(`${counters.failed} ${ANSI.color(`failed`, COLOR_FAILED)}`);
5067
+ }
5068
+ if (counters.completed) {
5069
+ parts.push(
5070
+ `${counters.completed} ${ANSI.color(`completed`, COLOR_COMPLETED)}`,
5071
+ );
5072
+ }
5073
+ if (counters.aborted) {
5074
+ parts.push(`${counters.aborted} ${ANSI.color(`aborted`, COLOR_ABORTED)}`);
5075
+ }
5076
+ if (counters.cancelled) {
5077
+ parts.push(
5078
+ `${counters.cancelled} ${ANSI.color(`cancelled`, COLOR_CANCELLED)}`,
5079
+ );
5080
+ }
5081
+ if (counters.skipped) {
5082
+ parts.push(`${counters.skipped} ${ANSI.color(`skipped`, COLOR_SKIPPED)}`);
5083
+ }
5084
+ return `${parts.join(", ")}`;
5085
+ };
5086
+
5087
+ const renderBigSection = (params) => {
5088
+ return renderSection({
5089
+ width: 45,
5090
+ ...params,
5091
+ });
5092
+ };
5093
+
5094
+ const renderSection = ({
5095
+ title,
5096
+ content,
5097
+ dashColor = ANSI.GREY,
5098
+ width = 38,
5099
+ bottomSeparator = true,
5100
+ }) => {
5101
+ let section = "";
5102
+
5103
+ if (title) {
5104
+ const titleWidth = stripAnsi(title).length;
5105
+ const minWidthRequired = `--- … ---`.length;
5106
+ const needsTruncate = titleWidth + minWidthRequired >= width;
5107
+ if (needsTruncate) {
5108
+ const titleTruncated = title.slice(0, width - minWidthRequired);
5109
+ const leftDashes = ANSI.color("---", dashColor);
5110
+ const rightDashes = ANSI.color("---", dashColor);
5111
+ section += `${leftDashes} ${titleTruncated}… ${rightDashes}`;
5112
+ } else {
5113
+ const remainingWidth = width - titleWidth - 2; // 2 for spaces around the title
5114
+ const dashLeftCount = Math.floor(remainingWidth / 2);
5115
+ const dashRightCount = remainingWidth - dashLeftCount;
5116
+ const leftDashes = ANSI.color("-".repeat(dashLeftCount), dashColor);
5117
+ const rightDashes = ANSI.color("-".repeat(dashRightCount), dashColor);
5118
+ section += `${leftDashes} ${title} ${rightDashes}`;
5119
+ }
5120
+ section += "\n";
5121
+ } else {
5122
+ const topDashes = ANSI.color(`-`.repeat(width), dashColor);
5123
+ section += topDashes;
5124
+ section += "\n";
5125
+ }
5126
+ section += `${content}`;
5127
+ if (bottomSeparator) {
5128
+ section += "\n";
5129
+ const bottomDashes = ANSI.color(`-`.repeat(width), dashColor);
5130
+ section += bottomDashes;
5131
+ }
5132
+ return section;
5133
+ };
5134
+
5135
+ const renderDetails = (data) => {
5136
+ const details = [];
5137
+ for (const key of Object.keys(data)) {
5138
+ const value = data[key];
5139
+ let valueString = "";
5140
+ valueString += ANSI.color(`${key}:`, ANSI.GREY);
5141
+ const useNonGreyAnsiColor =
5142
+ typeof value === "string" && value.includes("\x1b");
5143
+ valueString += " ";
5144
+ valueString += useNonGreyAnsiColor
5145
+ ? value
5146
+ : ANSI.color(String(value), ANSI.GREY);
5147
+ details.push(valueString);
5148
+ }
5149
+ if (details.length === 0) {
5150
+ return "";
5151
+ }
5152
+
5153
+ let string = "";
5154
+ string += ` ${ANSI.color("(", ANSI.GREY)}`;
5155
+ string += details.join(ANSI.color(", ", ANSI.GREY));
5156
+ string += ANSI.color(")", ANSI.GREY);
5157
+ return string;
5158
+ };
5159
+
5160
+ /*
5161
+ * Export a function capable to run a file on a runtime.
5162
+ *
5163
+ * - Used internally by "executeTestPlan" part of the documented API
5164
+ * - Used internally by "execute" an advanced API not documented
5165
+ * - logs generated during file execution can be collected
5166
+ * - logs generated during file execution can be mirrored (re-logged to the console)
5167
+ * - File is given allocatedMs to complete
5168
+ * - Errors are collected
5169
+ * - File execution result is returned, it contains status/errors/namespace/consoleCalls
5170
+ */
5171
+
5172
+
5173
+ const run = async ({
5174
+ signal = new AbortController().signal,
5175
+ logger,
5176
+ allocatedMs,
5177
+ keepRunning = false,
5178
+ mirrorConsole = false,
5179
+ collectConsole = false,
5180
+ measureMemoryUsage = false,
5181
+ onMeasureMemoryAvailable,
5182
+ collectPerformance = false,
5183
+ coverageEnabled = false,
5184
+ coverageTempDirectoryUrl,
5185
+ runtime,
5186
+ runtimeParams,
5187
+ }) => {
5188
+ if (keepRunning) {
5189
+ allocatedMs = Infinity;
5190
+ }
5191
+ if (allocatedMs === Infinity) {
5192
+ allocatedMs = 0;
5193
+ }
5194
+
5195
+ const timingsOrigin = Date.now();
5196
+ const takeTiming = () => {
5197
+ return Date.now() - timingsOrigin;
5198
+ };
5199
+ const result = {
5200
+ status: "pending",
5201
+ errors: [],
5202
+ namespace: null,
5203
+ consoleCalls: null,
5204
+ timings: {
5205
+ origin: timingsOrigin,
5206
+ start: 0,
5207
+ runtimeStart: null,
5208
+ executionStart: null,
5209
+ executionEnd: null,
5210
+ runtimeEnd: null,
5211
+ end: null,
5212
+ },
5213
+ memoryUsage: null,
5214
+ performance: null,
5215
+ coverageFileUrl: null,
5216
+ };
5217
+ const onConsoleRef = { current: () => {} };
5218
+ const stopSignal = { notify: () => {} };
5219
+ const runtimeLabel = `${runtime.name}/${runtime.version}`;
5220
+
5221
+ const runOperation = Abort.startOperation();
5222
+ runOperation.addAbortSignal(signal);
5223
+ let timeoutAbortSource;
5224
+ if (allocatedMs) {
5225
+ timeoutAbortSource = runOperation.timeout(allocatedMs);
5226
+ }
5227
+ const consoleCalls = [];
5228
+ onConsoleRef.current = ({ type, text }) => {
5229
+ if (mirrorConsole) {
5230
+ if (type === "error") {
5231
+ process.stderr.write(text);
5232
+ } else {
5233
+ process.stdout.write(text);
5234
+ }
5235
+ }
5236
+ if (collectConsole) {
5237
+ consoleCalls.push({ type, text });
5238
+ }
5239
+ };
5240
+ if (collectConsole) {
5241
+ result.consoleCalls = consoleCalls;
5242
+ }
5243
+
5244
+ // we do not keep coverage in memory, it can grow very big
5245
+ // instead we store it on the filesystem
5246
+ // and they can be read later at "coverageFileUrl"
5247
+ let coverageFileUrl;
5248
+ if (coverageEnabled) {
5249
+ coverageFileUrl = new URL(
5250
+ `./${runtime.name}/${crypto.randomUUID()}.json`,
5251
+ coverageTempDirectoryUrl,
5252
+ ).href;
5253
+ await ensureParentDirectories(coverageFileUrl);
5254
+ result.coverageFileUrl = coverageFileUrl;
5255
+ // written within the child_process/worker_thread or during runtime.run()
5256
+ // for browsers
5257
+ // (because it takes time to serialize and transfer the coverage object)
5258
+ }
5259
+
5260
+ try {
5261
+ logger.debug(`run() ${runtimeLabel}`);
5262
+ runOperation.throwIfAborted();
5263
+ const winnerPromise = new Promise((resolve) => {
5264
+ raceCallbacks(
5265
+ {
5266
+ aborted: (cb) => {
5267
+ runOperation.signal.addEventListener("abort", cb);
5268
+ return () => {
5269
+ runOperation.signal.removeEventListener("abort", cb);
5270
+ };
5271
+ },
5272
+ runned: async (cb) => {
5273
+ let runtimeStatus = "starting";
5274
+ try {
5275
+ const runResult = await runtime.run({
5276
+ signal: runOperation.signal,
5277
+ logger,
5278
+ ...runtimeParams,
5279
+ collectConsole,
5280
+ measureMemoryUsage,
5281
+ onMeasureMemoryAvailable,
5282
+ collectPerformance,
5283
+ coverageFileUrl,
5284
+ keepRunning,
5285
+ stopSignal,
5286
+ onConsole: (log) => onConsoleRef.current(log),
5287
+ onRuntimeStarted: () => {
5288
+ runtimeStatus = "started";
5289
+ result.timings.runtimeStart = takeTiming();
5290
+ },
5291
+ onRuntimeStopped: () => {
5292
+ if (runtimeStatus === "stopped") return; // ignore double calls
5293
+ runtimeStatus = "stopped";
5294
+ result.timings.runtimeEnd = takeTiming();
5295
+ },
5296
+ });
5297
+ cb(runResult);
5298
+ } catch (e) {
5299
+ cb({
5300
+ status: "failed",
5301
+ errors: [e],
5302
+ });
5303
+ }
5304
+ },
5305
+ },
5306
+ resolve,
5307
+ );
5308
+ });
5309
+ const winner = await winnerPromise;
5310
+ if (winner.name === "aborted") {
5311
+ runOperation.throwIfAborted();
5312
+ }
5313
+ const {
5314
+ status,
5315
+ errors,
5316
+ namespace,
5317
+ timings = {},
5318
+ memoryUsage,
5319
+ performance,
5320
+ } = winner.data;
5321
+ result.status = status;
5322
+ for (let error of errors) {
5323
+ const errorProxy = normalizeRuntimeError(error);
5324
+ result.errors.push(errorProxy);
5325
+ }
5326
+ result.namespace = namespace;
5327
+ if (timings && typeof timings.start === "number") {
5328
+ const diff = timings.origin - result.timings.origin;
5329
+ result.timings.executionStart = timings.start + diff;
5330
+ result.timings.executionEnd = timings.end + diff;
5331
+
5332
+ if (result.timings.executionStart < result.timings.runtimeStart) {
5333
+ // can happen for browsers where navigationStart can be earlier
5334
+ // than when node.js calls runtimeStarted()
5335
+ result.timings.runtimeStart = result.timings.executionStart;
5336
+ }
5337
+ }
5338
+ result.memoryUsage =
5339
+ typeof memoryUsage === "number"
5340
+ ? memoryUsage < 0
5341
+ ? 0
5342
+ : memoryUsage
5343
+ : memoryUsage;
5344
+ result.performance = performance;
5345
+ } catch (e) {
5346
+ if (Abort.isAbortError(e)) {
5347
+ if (timeoutAbortSource && timeoutAbortSource.signal.aborted) {
5348
+ result.status = "timedout";
5349
+ } else {
5350
+ result.status = "aborted";
5351
+ }
5352
+ } else {
5353
+ result.status = "failed";
5354
+ result.errors.push(e);
5355
+ }
5356
+ } finally {
5357
+ await runOperation.end();
5358
+ result.timings.end = takeTiming();
5359
+ return result;
5360
+ }
5404
5361
  };
5405
-
5406
- const renderErrors = (execution, logOptions) => {
5407
- const { errors = [] } = execution.result;
5408
- if (errors.length === 0) {
5409
- return "";
5410
- }
5411
-
5412
- if (errors.length === 1) {
5413
- return renderSection({
5414
- dashColor: ANSI.GREY,
5415
- title: "error",
5416
- content: formatErrorForTerminal(errors[0], {
5417
- rootDirectoryUrl: execution.rootDirectoryUrl,
5418
- mainFileRelativeUrl: execution.fileRelativeUrl,
5419
- mockFluctuatingValues: logOptions.mockFluctuatingValues,
5420
- tryColors: true,
5421
- }),
5362
+ const normalizeRuntimeError = (runtimeError) => {
5363
+ // the goal here is to obtain a "regular error"
5364
+ // that can be thrown using "throw" keyword
5365
+ // so we wrap the error hapenning inside the runtime
5366
+ // into "errorProxy" and put .stack property on it
5367
+ // the other properties are set by defineProperty so they are not enumerable
5368
+ // otherwise they would pollute the error displayed by Node.js
5369
+ const errorProxy = new Error(runtimeError.message);
5370
+ const exception = createException(runtimeError);
5371
+ for (const ownPropertyName of Object.getOwnPropertyNames(exception)) {
5372
+ Object.defineProperty(errorProxy, ownPropertyName, {
5373
+ writable: true,
5374
+ configurable: true,
5375
+ value: runtimeError[ownPropertyName],
5422
5376
  });
5423
5377
  }
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
- });
5378
+ return errorProxy;
5445
5379
  };
5446
5380
 
5447
- const renderOutro = (testPlanResult, logOptions = {}) => {
5448
- const { counters } = testPlanResult;
5449
- const { planified } = counters;
5450
- if (planified === 0) {
5451
- return "";
5452
- }
5453
- let title;
5454
- if (planified === 0) {
5455
- title = `no execution`;
5456
- } else if (planified === 1) {
5457
- title = `1 execution done`;
5381
+ const basicFetch = async (
5382
+ url,
5383
+ { rejectUnauthorized = true, method = "GET", headers = {} } = {},
5384
+ ) => {
5385
+ let requestModule;
5386
+ if (url.startsWith("http:")) {
5387
+ requestModule = await import("node:http");
5458
5388
  } else {
5459
- title = `${planified} executions done`;
5389
+ requestModule = await import("node:https");
5460
5390
  }
5461
- return `${renderBigSection({
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);
5391
+ const { request } = requestModule;
5473
5392
 
5474
- let durationLine = `duration: `;
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);
5393
+ const urlObject = new URL(url);
5495
5394
 
5496
- if (logOptions.cpuUsage) {
5497
- const processCpuUsage = testPlanResult.cpuUsage.process;
5498
- let cpuUsageLine = "cpu: ";
5499
- cpuUsageLine += `${humanizeProcessCpuUsage(processCpuUsage.end)}`;
5500
- cpuUsageLine += renderDetails({
5501
- med: humanizeProcessCpuUsage(processCpuUsage.median),
5502
- min: humanizeProcessCpuUsage(processCpuUsage.min),
5503
- max: humanizeProcessCpuUsage(processCpuUsage.max),
5395
+ return new Promise((resolve, reject) => {
5396
+ const req = request({
5397
+ rejectUnauthorized,
5398
+ hostname: urlObject.hostname,
5399
+ port: urlObject.port,
5400
+ path: urlObject.pathname,
5401
+ method,
5402
+ headers,
5504
5403
  });
5505
- lines.push(cpuUsageLine);
5506
- }
5507
- if (logOptions.memoryUsage) {
5508
- const processMemoryUsage = testPlanResult.memoryUsage.process;
5509
- let memoryUsageLine = "memory: ";
5510
- memoryUsageLine += `${humanizeProcessMemoryUsage(processMemoryUsage.end)}`;
5511
- memoryUsageLine += renderDetails({
5512
- med: humanizeProcessMemoryUsage(processMemoryUsage.median),
5513
- min: humanizeProcessMemoryUsage(processMemoryUsage.min),
5514
- max: humanizeProcessMemoryUsage(processMemoryUsage.max),
5404
+ req.on("response", (response) => {
5405
+ resolve({
5406
+ status: response.statusCode,
5407
+ headers: response.headers,
5408
+ json: () => {
5409
+ req.setTimeout(0);
5410
+ req.destroy();
5411
+ return new Promise((resolve) => {
5412
+ if (response.headers["content-type"] !== "application/json") {
5413
+ console.warn("not json");
5414
+ }
5415
+ let responseBody = "";
5416
+ response.setEncoding("utf8");
5417
+ response.on("data", (chunk) => {
5418
+ responseBody += chunk;
5419
+ });
5420
+ response.on("end", () => {
5421
+ resolve(JSON.parse(responseBody));
5422
+ });
5423
+ response.on("error", (e) => {
5424
+ reject(e);
5425
+ });
5426
+ });
5427
+ },
5428
+ });
5515
5429
  });
5516
- lines.push(memoryUsageLine);
5517
- }
5518
- return lines.join("\n");
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;
5430
+ req.on("error", reject);
5431
+ req.end();
5432
+ });
5530
5433
  };
5531
5434
 
5532
- const humanizeProcessMemoryUsage = (value) => {
5533
- return humanizeMemory(value, { short: true, decimals: 0 });
5534
- };
5435
+ const pingServer = async (url) => {
5436
+ const server = createServer();
5437
+ const { hostname, port } = new URL(url);
5535
5438
 
5536
- const renderStatusRepartition = (counters, { showProgression } = {}) => {
5537
- if (counters.planified === 0) {
5538
- return ``;
5539
- }
5540
- if (counters.skipped === counters.planified) {
5541
- return `all ${ANSI.color(`skipped`, COLOR_SKIPPED)}`;
5542
- }
5543
- if (counters.aborted === counters.planified) {
5544
- return `all ${ANSI.color(`aborted`, COLOR_ABORTED)}`;
5545
- }
5546
- if (counters.cancelled === counters.planified) {
5547
- return `all ${ANSI.color(`cancelled`, COLOR_CANCELLED)}`;
5548
- }
5549
- if (counters.timedout === counters.planified) {
5550
- return `all ${ANSI.color(`timed out`, COLOR_TIMEOUT)}`;
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)}`);
5439
+ try {
5440
+ await new Promise((resolve, reject) => {
5441
+ server.on("error", reject);
5442
+ server.on("listening", () => {
5443
+ resolve();
5444
+ });
5445
+ server.listen(port, hostname);
5446
+ });
5447
+ } catch (error) {
5448
+ if (error && error.code === "EADDRINUSE") {
5449
+ return true;
5450
+ }
5451
+ if (error && error.code === "EACCES") {
5452
+ return true;
5453
+ }
5454
+ throw error;
5590
5455
  }
5591
- return `${parts.join(", ")}`;
5456
+ await new Promise((resolve, reject) => {
5457
+ server.on("error", reject);
5458
+ server.on("close", resolve);
5459
+ server.close();
5460
+ });
5461
+ return false;
5592
5462
  };
5593
5463
 
5594
- const renderBigSection = (params) => {
5595
- return renderSection({
5596
- width: 45,
5597
- ...params,
5464
+ const startServerUsingCommand = async (
5465
+ webServer,
5466
+ { signal, allocatedMs, logger, teardownCallbackSet },
5467
+ ) => {
5468
+ const spawnedProcess = spawn(webServer.command, [], {
5469
+ // On non-windows platforms, `detached: true` makes child process a leader of a new
5470
+ // process group, making it possible to kill child process tree with `.kill(-pid)` command.
5471
+ // @see https://nodejs.org/api/child_process.html#child_process_options_detached
5472
+ detached: process.platform !== "win32",
5473
+ stdio: ["pipe", "pipe", "pipe"],
5474
+ shell: true,
5475
+ cwd: webServer.cwd,
5598
5476
  });
5599
- };
5477
+ if (!spawnedProcess.pid) {
5478
+ await new Promise((resolve, reject) => {
5479
+ spawnedProcess.once("error", (error) => {
5480
+ reject(new Error(`Failed to launch: ${error}`));
5481
+ });
5482
+ });
5483
+ }
5600
5484
 
5601
- const renderSection = ({
5602
- title,
5603
- content,
5604
- dashColor = ANSI.GREY,
5605
- width = 38,
5606
- bottomSeparator = true,
5607
- }) => {
5608
- let section = "";
5485
+ let errorReceived = false;
5486
+ const errorPromise = new Promise((resolve, reject) => {
5487
+ spawnedProcess.on("error", (e) => {
5488
+ errorReceived = true;
5489
+ reject(e);
5490
+ });
5491
+ });
5609
5492
 
5610
- if (title) {
5611
- const titleWidth = stripAnsi(title).length;
5612
- const minWidthRequired = `--- … ---`.length;
5613
- const needsTruncate = titleWidth + minWidthRequired >= width;
5614
- if (needsTruncate) {
5615
- const titleTruncated = title.slice(0, width - minWidthRequired);
5616
- const leftDashes = ANSI.color("---", dashColor);
5617
- const rightDashes = ANSI.color("---", dashColor);
5618
- section += `${leftDashes} ${titleTruncated}… ${rightDashes}`;
5619
- } else {
5620
- const remainingWidth = width - titleWidth - 2; // 2 for spaces around the title
5621
- const dashLeftCount = Math.floor(remainingWidth / 2);
5622
- const dashRightCount = remainingWidth - dashLeftCount;
5623
- const leftDashes = ANSI.color("-".repeat(dashLeftCount), dashColor);
5624
- const rightDashes = ANSI.color("-".repeat(dashRightCount), dashColor);
5625
- section += `${leftDashes} ${title} ${rightDashes}`;
5493
+ // const stdout = readline.createInterface({ input: spawnedProcess.stdout });
5494
+ // stdout.on("line", () => {
5495
+ // logger.debug(`[pid=${spawnedProcess.pid}][out] ${data}`);
5496
+ // });
5497
+ // const stderr = readline.createInterface({ input: spawnedProcess.stderr });
5498
+ // stderr.on("line", (data) => {
5499
+ // logger.debug(`[pid=${spawnedProcess.pid}][err] ${data}`);
5500
+ // });
5501
+ let processClosed = false;
5502
+ const closedPromise = new Promise((resolve) => {
5503
+ spawnedProcess.once("exit", (exitCode, signal) => {
5504
+ logger.debug(
5505
+ `[pid=${spawnedProcess.pid}] <process did exit: exitCode=${exitCode}, signal=${signal}>`,
5506
+ );
5507
+ processClosed = true;
5508
+ resolve();
5509
+ });
5510
+ });
5511
+ const killProcess = async () => {
5512
+ logger.debug(`[pid=${spawnedProcess.pid}] <kill>`);
5513
+ if (!spawnedProcess.pid || spawnedProcess.killed || processClosed) {
5514
+ logger.debug(
5515
+ `[pid=${spawnedProcess.pid}] <skipped force kill spawnedProcess.killed=${spawnedProcess.killed} processClosed=${processClosed}>`,
5516
+ );
5517
+ return;
5626
5518
  }
5627
- section += "\n";
5628
- } else {
5629
- const topDashes = ANSI.color(`-`.repeat(width), dashColor);
5630
- section += topDashes;
5631
- section += "\n";
5632
- }
5633
- section += `${content}`;
5634
- if (bottomSeparator) {
5635
- section += "\n";
5636
- const bottomDashes = ANSI.color(`-`.repeat(width), dashColor);
5637
- section += bottomDashes;
5519
+ logger.debug(`[pid=${spawnedProcess.pid}] <will force kill>`);
5520
+ // Force kill the browser.
5521
+ try {
5522
+ if (process.platform === "win32") {
5523
+ const taskkillProcess = spawnSync(
5524
+ `taskkill /pid ${spawnedProcess.pid} /T /F`,
5525
+ { shell: true },
5526
+ );
5527
+ const [stdout, stderr] = [
5528
+ taskkillProcess.stdout.toString(),
5529
+ taskkillProcess.stderr.toString(),
5530
+ ];
5531
+ if (stdout)
5532
+ logger.debug(
5533
+ `[pid=${spawnedProcess.pid}] taskkill stdout: ${stdout}`,
5534
+ );
5535
+ if (stderr)
5536
+ logger.info(`[pid=${spawnedProcess.pid}] taskkill stderr: ${stderr}`);
5537
+ } else {
5538
+ process.kill(-spawnedProcess.pid, "SIGKILL");
5539
+ }
5540
+ } catch (e) {
5541
+ logger.info(
5542
+ `[pid=${spawnedProcess.pid}] exception while trying to kill process: ${e}`,
5543
+ );
5544
+ // the process might have already stopped
5545
+ }
5546
+ await closedPromise;
5547
+ };
5548
+
5549
+ const startOperation = Abort.startOperation();
5550
+ startOperation.addAbortSignal(signal);
5551
+ const timeoutAbortSource = startOperation.timeout(allocatedMs);
5552
+ startOperation.addAbortCallback(killProcess);
5553
+ teardownCallbackSet.add(killProcess);
5554
+
5555
+ const startedPromise = (async () => {
5556
+ const logScale = [100, 250, 500];
5557
+ // eslint-disable-next-line no-constant-condition
5558
+ while (true) {
5559
+ if (errorReceived) {
5560
+ break;
5561
+ }
5562
+ const connected = await pingServer(webServer.origin);
5563
+ if (connected) {
5564
+ break;
5565
+ }
5566
+ startOperation.throwIfAborted();
5567
+ const delay = logScale.shift() || 1000;
5568
+ logger.debug(`Waiting ${delay}ms`);
5569
+ await new Promise((x) => setTimeout(x, delay));
5570
+ }
5571
+ })();
5572
+
5573
+ try {
5574
+ await Promise.race([errorPromise, startedPromise]);
5575
+ } catch (e) {
5576
+ if (Abort.isAbortError(e)) {
5577
+ if (timeoutAbortSource.signal.aborted) {
5578
+ // aborted by timeout
5579
+ throw new Error(
5580
+ `"${webServer.command}" command did not start a server in less than ${allocatedMs}ms`,
5581
+ );
5582
+ }
5583
+ if (signal.aborted) {
5584
+ // aborted from outside
5585
+ return;
5586
+ }
5587
+ }
5588
+ throw e;
5589
+ } finally {
5590
+ await startOperation.end();
5638
5591
  }
5639
- return section;
5640
5592
  };
5641
5593
 
5642
- const renderDetails = (data) => {
5643
- const details = [];
5644
- for (const key of Object.keys(data)) {
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 "";
5594
+ const startServerUsingModuleUrl = async (webServer, params) => {
5595
+ if (!existsSync(new URL(webServer.moduleUrl))) {
5596
+ throw new Error(`"${webServer.moduleUrl}" does not lead to a file`);
5658
5597
  }
5659
-
5660
- let string = "";
5661
- string += ` ${ANSI.color("(", ANSI.GREY)}`;
5662
- string += details.join(ANSI.color(", ", ANSI.GREY));
5663
- string += ANSI.color(")", ANSI.GREY);
5664
- return string;
5598
+ return startServerUsingCommand(
5599
+ {
5600
+ ...webServer,
5601
+ command: `node ${fileURLToPath(webServer.moduleUrl)}`,
5602
+ },
5603
+ params,
5604
+ );
5665
5605
  };
5666
5606
 
5667
- const createIsInsideFragment = (fragment, total) => {
5668
- let [dividend, divisor] = fragment.split("/");
5669
- dividend = parseInt(dividend);
5670
- divisor = parseInt(divisor);
5671
- const groupSize = Math.ceil(total / divisor);
5672
- let from;
5673
- let to;
5674
- if (groupSize === 0) {
5675
- if (dividend === 1) {
5676
- from = 0;
5677
- to = 0;
5607
+ const assertAndNormalizeWebServer = async (
5608
+ webServer,
5609
+ { signal, logger, teardownCallbackSet },
5610
+ ) => {
5611
+ if (!webServer) {
5612
+ throw new TypeError(
5613
+ `webServer is required when running tests on browser(s)`,
5614
+ );
5615
+ }
5616
+ const unexpectedParamNames = Object.keys(webServer).filter((key) => {
5617
+ return ![
5618
+ "origin",
5619
+ "moduleUrl",
5620
+ "command",
5621
+ "cwd",
5622
+ "rootDirectoryUrl",
5623
+ ].includes(key);
5624
+ });
5625
+ if (unexpectedParamNames.length > 0) {
5626
+ throw new TypeError(
5627
+ `${unexpectedParamNames.join(",")}: there is no such param to webServer`,
5628
+ );
5629
+ }
5630
+ if (typeof webServer.origin !== "string") {
5631
+ throw new TypeError(
5632
+ `webServer.origin must be a string, got ${webServer.origin}`,
5633
+ );
5634
+ }
5635
+ await ensureWebServerIsStarted(webServer, {
5636
+ signal,
5637
+ teardownCallbackSet,
5638
+ logger,
5639
+ });
5640
+ const { headers } = await basicFetch(webServer.origin, {
5641
+ method: "GET",
5642
+ rejectUnauthorized: false,
5643
+ headers: {
5644
+ "x-server-inspect": "1",
5645
+ },
5646
+ });
5647
+ if (String(headers["server"]).includes("jsenv_dev_server")) {
5648
+ webServer.isJsenvDevServer = true;
5649
+ const { json } = await basicFetch(`${webServer.origin}/__params__.json`, {
5650
+ rejectUnauthorized: false,
5651
+ });
5652
+ if (webServer.rootDirectoryUrl === undefined) {
5653
+ const jsenvDevServerParams = await json();
5654
+ webServer.rootDirectoryUrl = jsenvDevServerParams.sourceDirectoryUrl;
5678
5655
  } else {
5679
- from = Infinity;
5680
- to = -1;
5656
+ webServer.rootDirectoryUrl = assertAndNormalizeDirectoryUrl(
5657
+ webServer.rootDirectoryUrl,
5658
+ "webServer.rootDirectoryUrl",
5659
+ );
5681
5660
  }
5682
- } else if (dividend === 1) {
5683
- from = 0;
5684
- to = groupSize;
5685
5661
  } else {
5686
- from = (dividend - 1) * groupSize;
5687
- if (dividend === divisor) {
5688
- to = total;
5689
- } else {
5690
- to = from + groupSize;
5691
- }
5662
+ webServer.rootDirectoryUrl = assertAndNormalizeDirectoryUrl(
5663
+ webServer.rootDirectoryUrl,
5664
+ "webServer.rootDirectoryUrl",
5665
+ );
5692
5666
  }
5693
- return (index) => {
5694
- if (index < from) return false;
5695
- if (index >= to) return false;
5696
- return true;
5697
- };
5667
+ };
5668
+
5669
+ const ensureWebServerIsStarted = async (
5670
+ webServer,
5671
+ { signal, teardownCallbackSet, logger, allocatedMs = 5_000 },
5672
+ ) => {
5673
+ const aServerIsListening = await pingServer(webServer.origin);
5674
+ if (aServerIsListening) {
5675
+ return;
5676
+ }
5677
+ if (webServer.moduleUrl) {
5678
+ await startServerUsingModuleUrl(webServer, {
5679
+ signal,
5680
+ allocatedMs,
5681
+ teardownCallbackSet,
5682
+ logger,
5683
+ });
5684
+ return;
5685
+ }
5686
+ if (webServer.command) {
5687
+ await startServerUsingCommand(webServer, {
5688
+ signal,
5689
+ allocatedMs,
5690
+ teardownCallbackSet,
5691
+ logger,
5692
+ });
5693
+ return;
5694
+ }
5695
+ throw new TypeError(
5696
+ `webServer.moduleUrl or webServer.command is required as there is no server listening "${webServer.origin}"`,
5697
+ );
5698
5698
  };
5699
5699
 
5700
5700
  /*
@@ -6922,6 +6922,63 @@ const WEB_URL_CONVERTER = {
6922
6922
  },
6923
6923
  };
6924
6924
 
6925
+ const initIstanbulMiddleware = async (
6926
+ page,
6927
+ { webServer, rootDirectoryUrl, coverageInclude },
6928
+ ) => {
6929
+ const associations = URL_META.resolveAssociations(
6930
+ { cover: coverageInclude },
6931
+ rootDirectoryUrl,
6932
+ );
6933
+ await page.route("**", async (route) => {
6934
+ const request = route.request();
6935
+ const url = request.url(); // transform into a local url
6936
+ const fileUrl = WEB_URL_CONVERTER.asFileUrl(url, webServer);
6937
+ const needsInstrumentation = URL_META.applyAssociations({
6938
+ url: fileUrl,
6939
+ associations,
6940
+ }).cover;
6941
+ if (!needsInstrumentation) {
6942
+ route.fallback();
6943
+ return;
6944
+ }
6945
+ const response = await route.fetch();
6946
+ const originalBody = await response.text();
6947
+ try {
6948
+ const result = await applyBabelPlugins({
6949
+ babelPlugins: [babelPluginInstrument],
6950
+ input: originalBody,
6951
+ // jsenv server could send info to know it's a js module or js classic
6952
+ // but in the end it's not super important
6953
+ // - it's ok to parse js classic as js module considering it's only for istanbul instrumentation
6954
+ inputIsJsModule: true,
6955
+ inputUrl: fileUrl,
6956
+ });
6957
+ let code = result.code;
6958
+ code = SOURCEMAP.writeComment({
6959
+ contentType: "text/javascript",
6960
+ content: code,
6961
+ specifier: generateSourcemapDataUrl(result.map),
6962
+ });
6963
+ route.fulfill({
6964
+ response,
6965
+ body: code,
6966
+ headers: {
6967
+ ...response.headers(),
6968
+ "content-length": Buffer.byteLength(code),
6969
+ },
6970
+ });
6971
+ } catch (e) {
6972
+ if (e.code === "PARSE_ERROR") {
6973
+ route.fulfill({ response });
6974
+ } else {
6975
+ console.error(e);
6976
+ route.fulfill({ response });
6977
+ }
6978
+ }
6979
+ });
6980
+ };
6981
+
6925
6982
  const initJsSupervisorMiddleware = async (
6926
6983
  page,
6927
6984
  { webServer, fileUrl, fileServerUrl },
@@ -7014,63 +7071,6 @@ const initJsSupervisorMiddleware = async (
7014
7071
  });
7015
7072
  };
7016
7073
 
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
7074
  const browserPromiseCache = new Map();
7075
7075
 
7076
7076
  const createRuntimeUsingPlaywright = ({
@@ -8006,6 +8006,21 @@ const getDebugInfo = (processOptions) => {
8006
8006
  // return typeof process.env.VSCODE_PID === "string"
8007
8007
  // }
8008
8008
 
8009
+ // https://nodejs.org/api/process.html#process_signal_events
8010
+ const SIGINT_SIGNAL_NUMBER = 2;
8011
+ const SIGABORT_SIGNAL_NUMBER = 6;
8012
+ const SIGTERM_SIGNAL_NUMBER = 15;
8013
+ const EXIT_CODES = {
8014
+ SIGINT: 128 + SIGINT_SIGNAL_NUMBER,
8015
+ SIGABORT: 128 + SIGABORT_SIGNAL_NUMBER,
8016
+ SIGTERM: 128 + SIGTERM_SIGNAL_NUMBER,
8017
+ };
8018
+
8019
+ const IMPORTMAP_NODE_LOADER_FILE_URL = new URL(
8020
+ "./importmap_node_loader.mjs",
8021
+ import.meta.url,
8022
+ ).href;
8023
+
8009
8024
  // see also https://github.com/sindresorhus/execa/issues/96
8010
8025
  const killProcessTree = async (
8011
8026
  processId,
@@ -8081,21 +8096,6 @@ const killProcessTree = async (
8081
8096
  await check();
8082
8097
  };
8083
8098
 
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
8099
  const NO_EXPERIMENTAL_WARNING_FILE_URL = new URL(
8100
8100
  "./no_experimental_warnings.cjs",
8101
8101
  import.meta.url,
@@ -8973,24 +8973,39 @@ const istanbulCoverageMapFromCoverage = (coverage) => {
8973
8973
  return coverageMap;
8974
8974
  };
8975
8975
 
8976
- const reportCoverageInConsole = (
8976
+ const reportCoverageAsHtml = (
8977
8977
  testPlanResult,
8978
+ directoryUrl,
8978
8979
  { skipEmpty, skipFull } = {},
8979
8980
  ) => {
8980
8981
  if (testPlanResult.aborted) {
8981
8982
  return;
8982
8983
  }
8983
8984
  const testPlanCoverage = testPlanResult.coverage;
8985
+
8986
+ const { rootDirectoryUrl } = testPlanResult;
8987
+ ensureEmptyDirectorySync(directoryUrl);
8988
+ const coverageHtmlDirectoryRelativeUrl = urlToRelativeUrl(
8989
+ directoryUrl,
8990
+ rootDirectoryUrl,
8991
+ );
8992
+
8984
8993
  const libReport = importWithRequire("istanbul-lib-report");
8985
8994
  const reports = importWithRequire("istanbul-reports");
8986
8995
  const context = libReport.createContext({
8996
+ dir: fileURLToPath(rootDirectoryUrl),
8987
8997
  coverageMap: istanbulCoverageMapFromCoverage(testPlanCoverage),
8998
+ sourceFinder: (path) =>
8999
+ readFileSync(new URL(path, rootDirectoryUrl), "utf8"),
8988
9000
  });
8989
- const report = reports.create("text", {
9001
+ const report = reports.create("html", {
8990
9002
  skipEmpty,
8991
9003
  skipFull,
9004
+ subdir: coverageHtmlDirectoryRelativeUrl,
8992
9005
  });
8993
9006
  report.execute(context);
9007
+ // const htmlCoverageDirectoryIndexFileUrl = `${directoryUrl}index.html`;
9008
+ // console.log(`-> ${urlToFileSystemPath(htmlCoverageDirectoryIndexFileUrl)}`);
8994
9009
  };
8995
9010
 
8996
9011
  const reportCoverageAsJson = (
@@ -9013,39 +9028,24 @@ const reportCoverageAsJson = (
9013
9028
  }
9014
9029
  };
9015
9030
 
9016
- const reportCoverageAsHtml = (
9031
+ const reportCoverageInConsole = (
9017
9032
  testPlanResult,
9018
- directoryUrl,
9019
9033
  { skipEmpty, skipFull } = {},
9020
9034
  ) => {
9021
9035
  if (testPlanResult.aborted) {
9022
9036
  return;
9023
9037
  }
9024
9038
  const testPlanCoverage = testPlanResult.coverage;
9025
-
9026
- const { rootDirectoryUrl } = testPlanResult;
9027
- ensureEmptyDirectorySync(directoryUrl);
9028
- const coverageHtmlDirectoryRelativeUrl = urlToRelativeUrl(
9029
- directoryUrl,
9030
- rootDirectoryUrl,
9031
- );
9032
-
9033
9039
  const libReport = importWithRequire("istanbul-lib-report");
9034
9040
  const reports = importWithRequire("istanbul-reports");
9035
9041
  const context = libReport.createContext({
9036
- dir: fileURLToPath(rootDirectoryUrl),
9037
9042
  coverageMap: istanbulCoverageMapFromCoverage(testPlanCoverage),
9038
- sourceFinder: (path) =>
9039
- readFileSync(new URL(path, rootDirectoryUrl), "utf8"),
9040
9043
  });
9041
- const report = reports.create("html", {
9044
+ const report = reports.create("text", {
9042
9045
  skipEmpty,
9043
9046
  skipFull,
9044
- subdir: coverageHtmlDirectoryRelativeUrl,
9045
9047
  });
9046
9048
  report.execute(context);
9047
- // const htmlCoverageDirectoryIndexFileUrl = `${directoryUrl}index.html`;
9048
- // console.log(`-> ${urlToFileSystemPath(htmlCoverageDirectoryIndexFileUrl)}`);
9049
9049
  };
9050
9050
 
9051
9051
  const createXmlGenerator = ({