@jsenv/test 2.1.3 → 3.0.0

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 (46) hide show
  1. package/README.md +2 -1
  2. package/dist/js/exception.js +908 -0
  3. package/dist/js/execute_using_dynamic_import.js +76 -167
  4. package/dist/js/v8_coverage.js +4 -482
  5. package/dist/jsenv_test.js +4608 -2917
  6. package/dist/node_child_process_controlled.mjs +95 -76
  7. package/dist/node_worker_thread_controlled.mjs +72 -54
  8. package/package.json +23 -14
  9. package/src/coverage/generate_coverage.js +186 -0
  10. package/src/coverage/list_files_not_covered.js +2 -2
  11. package/src/coverage/missing_coverage.js +2 -2
  12. package/src/coverage/report_coverage_as_html.js +45 -0
  13. package/src/coverage/report_coverage_as_json.js +20 -0
  14. package/src/coverage/{coverage_reporter_text_log.js → report_coverage_in_console.js} +13 -7
  15. package/src/coverage/v8_and_istanbul.js +4 -4
  16. package/src/coverage/v8_coverage.js +2 -2
  17. package/src/execution/exception.js +151 -68
  18. package/src/execution/execute.js +24 -11
  19. package/src/execution/execute_test_plan.js +834 -750
  20. package/src/execution/format_error_for_terminal.js +187 -0
  21. package/src/execution/github_annotation_from_error.js +3 -3
  22. package/src/execution/junit_xml_file/report_as_junit_xml.js +149 -0
  23. package/src/execution/junit_xml_file/xml_generator.js +141 -0
  24. package/src/execution/reporters/reporter_list.js +687 -0
  25. package/src/execution/run.js +92 -30
  26. package/src/execution/web_server/start_using_command.js +2 -2
  27. package/src/execution/web_server_param.js +5 -5
  28. package/src/helpers/cpu_usage.js +142 -0
  29. package/src/helpers/cpu_usage.test_manual.js +7 -0
  30. package/src/main.js +8 -0
  31. package/src/runtime_browsers/chromium.js +1 -3
  32. package/src/runtime_browsers/firefox.js +0 -3
  33. package/src/runtime_browsers/middleware_istanbul.js +2 -2
  34. package/src/runtime_browsers/using_playwright.js +296 -220
  35. package/src/runtime_browsers/webkit.js +0 -3
  36. package/src/runtime_node/execute_using_dynamic_import.js +75 -28
  37. package/src/runtime_node/node_child_process.js +250 -139
  38. package/src/runtime_node/node_child_process_controlled.mjs +94 -75
  39. package/src/runtime_node/node_worker_thread.js +206 -120
  40. package/src/runtime_node/node_worker_thread_controlled.mjs +70 -53
  41. package/src/coverage/coverage_reporter_html_directory.js +0 -32
  42. package/src/coverage/coverage_reporter_json_file.js +0 -17
  43. package/src/coverage/report_to_coverage.js +0 -198
  44. package/src/execution/execution_colors.js +0 -10
  45. package/src/execution/execution_steps.js +0 -65
  46. package/src/execution/logs_file_execution.js +0 -495
@@ -1,55 +1,54 @@
1
- import { createException, executeUsingDynamicImport } from "./js/execute_using_dynamic_import.js";
1
+ import { memoryUsage } from "node:process";
2
+ import { createException } from "./js/exception.js";
3
+ import { executeUsingDynamicImport } from "./js/execute_using_dynamic_import.js";
2
4
  import "node:fs";
3
5
  import "node:inspector";
4
6
  import "node:perf_hooks";
5
7
 
8
+ let memoryHeapUsedAtStart;
9
+ if (process.env.MEASURE_MEMORY_AT_START) {
10
+ global.gc();
11
+ memoryHeapUsedAtStart = memoryUsage().heapUsed;
12
+ }
13
+
6
14
  const ACTIONS_AVAILABLE = {
7
- "execute-using-dynamic-import": executeUsingDynamicImport,
15
+ "execute-using-dynamic-import": (params) => {
16
+ return executeUsingDynamicImport(params);
17
+ },
8
18
  "execute-using-require": async ({ fileUrl }) => {
9
- const { createRequire } = await import("node:module");
10
- const { fileURLToPath } = await import("node:url");
11
- const filePath = fileURLToPath(fileUrl);
12
- const require = createRequire(fileUrl);
13
- // eslint-disable-next-line import/no-dynamic-require
14
- const namespace = require(filePath);
15
- const namespaceResolved = {};
16
- await Promise.all(
17
- Object.keys(namespace).map(async (key) => {
18
- const value = await namespace[key];
19
- namespaceResolved[key] = value;
20
- }),
21
- );
22
- return namespaceResolved;
19
+ const result = {
20
+ timings: {
21
+ start: null,
22
+ end: null,
23
+ },
24
+ namespace: null,
25
+ };
26
+ try {
27
+ const { createRequire } = await import("node:module");
28
+ const { fileURLToPath } = await import("node:url");
29
+ const filePath = fileURLToPath(fileUrl);
30
+ const require = createRequire(fileUrl);
31
+ result.timings.start = Date.now();
32
+ // eslint-disable-next-line import/no-dynamic-require
33
+ const namespace = require(filePath);
34
+ const namespaceResolved = {};
35
+ await Promise.all(
36
+ Object.keys(namespace).map(async (key) => {
37
+ const value = await namespace[key];
38
+ namespaceResolved[key] = value;
39
+ }),
40
+ );
41
+ result.namespace = namespaceResolved;
42
+ } finally {
43
+ result.timings.end = Date.now();
44
+ return result;
45
+ }
46
+ },
47
+ "measure-memory-usage": () => {
48
+ // we compare usage - usageAtstart to prevent
49
+ // node or os specificities to have too much influences on the measures
50
+ return memoryUsage().heapUsed - memoryHeapUsedAtStart;
23
51
  },
24
- };
25
- const ACTION_REQUEST_EVENT_NAME = "action";
26
- const ACTION_RESPONSE_EVENT_NAME = "action-result";
27
- const ACTION_RESPONSE_STATUS_FAILED = "action-failed";
28
- const ACTION_RESPONSE_STATUS_COMPLETED = "action-completed";
29
-
30
- const sendActionFailed = (error) => {
31
- const exception = createException(error);
32
- sendToParent(
33
- ACTION_RESPONSE_EVENT_NAME,
34
- JSON.stringify({
35
- status: ACTION_RESPONSE_STATUS_FAILED,
36
- value: exception,
37
- }),
38
- );
39
- };
40
-
41
- const sendActionCompleted = (value) => {
42
- sendToParent(
43
- ACTION_RESPONSE_EVENT_NAME,
44
- // here we use JSON.stringify because we should not
45
- // have non enumerable value (unlike there is on Error objects)
46
- // otherwise uneval is quite slow to turn a giant object
47
- // into a string (and value can be giant when using coverage)
48
- JSON.stringify({
49
- status: ACTION_RESPONSE_STATUS_COMPLETED,
50
- value,
51
- }),
52
- );
53
52
  };
54
53
 
55
54
  const sendToParent = (type, data) => {
@@ -63,16 +62,13 @@ const sendToParent = (type, data) => {
63
62
  // It means node process may stay alive longer than expected
64
63
  // the time to send the data to the parent.
65
64
  process.send({
66
- jsenv: true,
67
- type,
65
+ __jsenv__: type,
68
66
  data,
69
67
  });
70
68
  };
71
-
72
- const onceParentMessage = (type, callback) => {
69
+ const onActionRequestedByParent = (callback) => {
73
70
  const listener = (message) => {
74
- if (message && message.jsenv && message.type === type) {
75
- removeListener(); // commenting this line keep this process alive
71
+ if (message && message.__jsenv__ === "action") {
76
72
  callback(message.data);
77
73
  }
78
74
  };
@@ -83,37 +79,60 @@ const onceParentMessage = (type, callback) => {
83
79
  return removeListener;
84
80
  };
85
81
 
86
- const removeActionRequestListener = onceParentMessage(
87
- ACTION_REQUEST_EVENT_NAME,
88
- async ({ actionType, actionParams }) => {
89
- const action = ACTIONS_AVAILABLE[actionType];
82
+ const removeActionRequestListener = onActionRequestedByParent(
83
+ async ({ id, type, params = {} }) => {
84
+ const sendActionInternalError = (id, error) => {
85
+ const exception = createException(error);
86
+ sendToParent(
87
+ "action-result",
88
+ JSON.stringify({
89
+ id,
90
+ status: "error",
91
+ value: exception,
92
+ }),
93
+ );
94
+ };
95
+ const sendActionCompleted = (id, value) => {
96
+ sendToParent(
97
+ "action-result",
98
+ // here we use JSON.stringify because we should not
99
+ // have non enumerable value (unlike there is on Error objects)
100
+ // otherwise uneval is quite slow to turn a giant object
101
+ // into a string (and value can be giant when using coverage)
102
+ JSON.stringify({
103
+ id,
104
+ status: "completed",
105
+ value,
106
+ }),
107
+ );
108
+ };
109
+
110
+ const action = ACTIONS_AVAILABLE[type];
90
111
  if (!action) {
91
- sendActionFailed(new Error(`unknown action ${actionType}`));
112
+ sendActionInternalError(id, new Error(`unknown action ${type}`));
92
113
  return;
93
114
  }
94
115
 
95
- let value;
96
- let failed = false;
116
+ let gotInternalError = false;
117
+ const onUncaughtException = (err) => {
118
+ gotInternalError = true;
119
+ sendActionInternalError(id, err);
120
+ process.exit(1);
121
+ };
122
+ process.on("uncaughtException", onUncaughtException);
123
+
97
124
  try {
98
- value = await action(actionParams);
125
+ const value = await action(params);
126
+ if (!gotInternalError) {
127
+ sendActionCompleted(id, value);
128
+ }
99
129
  } catch (e) {
100
- failed = true;
101
- value = e;
102
- }
103
-
104
- // setTimeout(() => {}, 100)
105
-
106
- if (failed) {
107
- sendActionFailed(value);
108
- } else {
109
- sendActionCompleted(value);
110
- }
111
-
112
- // removeActionRequestListener()
113
- if (actionParams.exitAfterAction) {
114
- removeActionRequestListener();
115
- // for some reason this fixes v8 coverage directory sometimes empty on Ubuntu
116
- // process.exit()
130
+ sendActionInternalError(id, e);
131
+ } finally {
132
+ process.removeListener("uncaughtException", onUncaughtException);
133
+ if (params.exitAfterAction) {
134
+ removeActionRequestListener();
135
+ }
117
136
  }
118
137
  },
119
138
  );
@@ -1,40 +1,31 @@
1
1
  import { parentPort } from "node:worker_threads";
2
- import { createException, executeUsingDynamicImport } from "./js/execute_using_dynamic_import.js";
2
+ import { memoryUsage } from "node:process";
3
+ import { setFlagsFromString } from "node:v8";
4
+ import { runInNewContext } from "node:vm";
5
+ import { createException } from "./js/exception.js";
6
+ import { executeUsingDynamicImport } from "./js/execute_using_dynamic_import.js";
3
7
  import "node:fs";
4
8
  import "node:inspector";
5
9
  import "node:perf_hooks";
6
10
 
7
- const ACTIONS_AVAILABLE = {
8
- "execute-using-dynamic-import": executeUsingDynamicImport,
9
- };
10
- const ACTION_REQUEST_EVENT_NAME = "action";
11
- const ACTION_RESPONSE_EVENT_NAME = "action-result";
12
- const ACTION_RESPONSE_STATUS_FAILED = "action-failed";
13
- const ACTION_RESPONSE_STATUS_COMPLETED = "action-completed";
11
+ setFlagsFromString("--expose_gc");
12
+ global.gc = runInNewContext("gc");
14
13
 
15
- const sendActionFailed = (error) => {
16
- const exception = createException(error);
17
- sendToParent(
18
- ACTION_RESPONSE_EVENT_NAME,
19
- JSON.stringify({
20
- status: ACTION_RESPONSE_STATUS_FAILED,
21
- value: exception,
22
- }),
23
- );
24
- };
14
+ let memoryHeapUsedAtStart;
15
+ if (process.env.MEASURE_MEMORY_AT_START) {
16
+ global.gc();
17
+ memoryHeapUsedAtStart = memoryUsage().heapUsed;
18
+ }
25
19
 
26
- const sendActionCompleted = (value) => {
27
- sendToParent(
28
- ACTION_RESPONSE_EVENT_NAME,
29
- // here we use JSON.stringify because we should not
30
- // have non enumerable value (unlike there is on Error objects)
31
- // otherwise uneval is quite slow to turn a giant object
32
- // into a string (and value can be giant when using coverage)
33
- JSON.stringify({
34
- status: ACTION_RESPONSE_STATUS_COMPLETED,
35
- value,
36
- }),
37
- );
20
+ const ACTIONS_AVAILABLE = {
21
+ "execute-using-dynamic-import": (params) => {
22
+ return executeUsingDynamicImport(params);
23
+ },
24
+ "measure-memory-usage": () => {
25
+ // we compare usage - usageAtstart to prevent
26
+ // node or os specificities to have too much influences on the measures
27
+ return memoryUsage().heapUsed - memoryHeapUsedAtStart;
28
+ },
38
29
  };
39
30
 
40
31
  const sendToParent = (type, data) => {
@@ -43,16 +34,14 @@ const sendToParent = (type, data) => {
43
34
  // It means node process may stay alive longer than expected
44
35
  // the time to send the data to the parent.
45
36
  parentPort.postMessage({
46
- jsenv: true,
47
- type,
37
+ __jsenv__: type,
48
38
  data,
49
39
  });
50
40
  };
51
41
 
52
- const onceParentMessage = (type, callback) => {
42
+ const onActionRequestedByParent = (callback) => {
53
43
  const listener = (message) => {
54
- if (message && message.jsenv && message.type === type) {
55
- removeListener(); // commenting this line keep this worker alive
44
+ if (message && message.__jsenv__ === "action") {
56
45
  callback(message.data);
57
46
  }
58
47
  };
@@ -63,31 +52,60 @@ const onceParentMessage = (type, callback) => {
63
52
  return removeListener;
64
53
  };
65
54
 
66
- const removeActionRequestListener = onceParentMessage(
67
- ACTION_REQUEST_EVENT_NAME,
68
- async ({ actionType, actionParams }) => {
69
- const action = ACTIONS_AVAILABLE[actionType];
55
+ const removeActionRequestListener = onActionRequestedByParent(
56
+ async ({ id, type, params = {} }) => {
57
+ const sendActionInternalError = (id, error) => {
58
+ const exception = createException(error);
59
+ sendToParent(
60
+ "action-result",
61
+ JSON.stringify({
62
+ id,
63
+ status: "error",
64
+ value: exception,
65
+ }),
66
+ );
67
+ };
68
+ const sendActionCompleted = (id, value) => {
69
+ sendToParent(
70
+ "action-result",
71
+ // here we use JSON.stringify because we should not
72
+ // have non enumerable value (unlike there is on Error objects)
73
+ // otherwise uneval is quite slow to turn a giant object
74
+ // into a string (and value can be giant when using coverage)
75
+ JSON.stringify({
76
+ id,
77
+ status: "completed",
78
+ value,
79
+ }),
80
+ );
81
+ };
82
+
83
+ const action = ACTIONS_AVAILABLE[type];
70
84
  if (!action) {
71
- sendActionFailed(new Error(`unknown action ${actionType}`));
85
+ sendActionInternalError(id, new Error(`unknown action ${type}`));
72
86
  return;
73
87
  }
74
88
 
75
- let value;
76
- let failed = false;
89
+ let gotInternalError = false;
90
+ const onUncaughtException = (err) => {
91
+ gotInternalError = true;
92
+ sendActionInternalError(id, err);
93
+ process.exit(1);
94
+ };
95
+ process.on("uncaughtException", onUncaughtException);
96
+
77
97
  try {
78
- value = await action(actionParams);
98
+ const value = await action(params);
99
+ if (!gotInternalError) {
100
+ sendActionCompleted(id, value);
101
+ }
79
102
  } catch (e) {
80
- failed = true;
81
- value = e;
82
- }
83
-
84
- if (failed) {
85
- sendActionFailed(value);
86
- } else {
87
- sendActionCompleted(value);
88
- }
89
- if (actionParams.exitAfterAction) {
90
- removeActionRequestListener();
103
+ sendActionInternalError(id, e);
104
+ } finally {
105
+ process.removeListener("uncaughtException", onUncaughtException);
106
+ if (params.exitAfterAction) {
107
+ removeActionRequestListener();
108
+ }
91
109
  }
92
110
  },
93
111
  );
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@jsenv/test",
3
- "version": "2.1.3",
3
+ "version": "3.0.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/jsenv/core",
8
8
  "directory": "packages/related/test"
9
9
  },
10
+ "engines": {
11
+ "node": ">=20.11.0"
12
+ },
10
13
  "publishConfig": {
11
14
  "access": "public"
12
15
  },
@@ -33,23 +36,29 @@
33
36
  "playwright": "1.x"
34
37
  },
35
38
  "dependencies": {
36
- "@jsenv/abort": "4.2.4",
37
- "@jsenv/ast": "6.0.1",
38
- "@jsenv/filesystem": "4.6.0",
39
- "@jsenv/log": "3.4.3",
40
- "@jsenv/plugin-supervisor": "1.4.0",
41
- "@jsenv/urls": "2.2.1",
42
- "@jsenv/url-meta": "8.2.0",
43
- "@jsenv/utils": "2.1.0",
44
- "@jsenv/github-check-run": "0.1.6",
45
- "v8-to-istanbul": "9.2.0",
39
+ "@c88/v8-coverage": "0.1.1",
40
+ "@jsenv/abort": "4.3.0",
41
+ "@jsenv/ast": "6.0.2",
42
+ "@jsenv/filesystem": "4.6.3",
43
+ "@jsenv/github-check-run": "0.1.7",
44
+ "@jsenv/inspect": "2.0.0",
45
+ "@jsenv/log": "3.5.1",
46
+ "@jsenv/plugin-supervisor": "1.4.1",
47
+ "@jsenv/url-meta": "8.4.0",
48
+ "@jsenv/urls": "2.2.2",
49
+ "@jsenv/utils": "2.1.1",
46
50
  "istanbul-lib-coverage": "3.2.2",
47
51
  "istanbul-lib-instrument": "6.0.1",
48
52
  "istanbul-lib-report": "3.0.1",
49
53
  "istanbul-reports": "3.1.6",
50
- "@c88/v8-coverage": "0.1.1",
54
+ "he": "1.2.0",
55
+ "pidtree": "0.6.0",
51
56
  "strip-ansi": "7.1.0",
52
- "wrap-ansi": "9.0.0",
53
- "pidtree": "0.6.0"
57
+ "v8-to-istanbul": "9.2.0",
58
+ "wrap-ansi": "9.0.0"
59
+ },
60
+ "devDependencies": {
61
+ "@jsenv/terminal-recorder": "../independent/terminal-recorder/",
62
+ "errorstacks": "2.4.1"
54
63
  }
55
64
  }
@@ -0,0 +1,186 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { Abort } from "@jsenv/abort";
3
+
4
+ import { filterV8Coverage } from "./v8_coverage.js";
5
+ import { readNodeV8CoverageDirectory } from "./v8_coverage_node_directory.js";
6
+ import { composeTwoV8Coverages } from "./v8_coverage_composition.js";
7
+ import { composeTwoFileByFileIstanbulCoverages } from "./istanbul_coverage_composition.js";
8
+ import { v8CoverageToIstanbul } from "./v8_coverage_to_istanbul.js";
9
+ import { composeV8AndIstanbul } from "./v8_and_istanbul.js";
10
+ import { normalizeFileByFileCoveragePaths } from "./file_by_file_coverage.js";
11
+ import { getMissingFileByFileCoverage } from "./missing_coverage.js";
12
+
13
+ export const generateCoverage = async (
14
+ testPlanResult,
15
+ { signal, logger, rootDirectoryUrl, coverage },
16
+ ) => {
17
+ // collect v8 and istanbul coverage from executions
18
+ let { v8Coverage, fileByFileIstanbulCoverage } =
19
+ await getCoverageFromTestPlanResults(testPlanResult.results, {
20
+ signal,
21
+ onMissing: ({ file, executionResult, executionName }) => {
22
+ // several reasons not to have coverage here:
23
+ // 1. the file we executed did not import an instrumented file.
24
+ // - a test file without import
25
+ // - a test file importing only file excluded from coverage
26
+ // - a coverDescription badly configured so that we don't realize
27
+ // a file should be covered
28
+
29
+ // 2. the file we wanted to executed timedout
30
+ // - infinite loop
31
+ // - too extensive operation
32
+ // - a badly configured or too low allocatedMs for that execution.
33
+
34
+ // 3. the file we wanted to execute contains syntax-error
35
+
36
+ // in any scenario we are fine because
37
+ // coverDescription will generate empty coverage for files
38
+ // that were suppose to be coverage but were not.
39
+ if (
40
+ executionResult.status === "completed" &&
41
+ executionResult.type === "node" &&
42
+ coverage.methodForNodeJs !== "NODE_V8_COVERAGE"
43
+ ) {
44
+ logger.warn(
45
+ `"${executionName}" execution of ${file} did not properly write coverage into ${executionResult.coverageFileUrl}`,
46
+ );
47
+ }
48
+ },
49
+ });
50
+
51
+ if (coverage.methodForNodeJs === "NODE_V8_COVERAGE") {
52
+ await readNodeV8CoverageDirectory({
53
+ logger,
54
+ signal,
55
+ onV8Coverage: async (nodeV8Coverage) => {
56
+ const nodeV8CoverageLight = await filterV8Coverage(nodeV8Coverage, {
57
+ rootDirectoryUrl,
58
+ coverageInclude: coverage.include,
59
+ });
60
+ v8Coverage = v8Coverage
61
+ ? composeTwoV8Coverages(v8Coverage, nodeV8CoverageLight)
62
+ : nodeV8CoverageLight;
63
+ },
64
+ });
65
+ }
66
+
67
+ // try to merge v8 with istanbul, if any
68
+ let fileByFileCoverage;
69
+ if (v8Coverage) {
70
+ let v8FileByFileCoverage = await v8CoverageToIstanbul(v8Coverage, {
71
+ signal,
72
+ });
73
+
74
+ v8FileByFileCoverage = normalizeFileByFileCoveragePaths(
75
+ v8FileByFileCoverage,
76
+ rootDirectoryUrl,
77
+ );
78
+
79
+ if (fileByFileIstanbulCoverage) {
80
+ fileByFileIstanbulCoverage = normalizeFileByFileCoveragePaths(
81
+ fileByFileIstanbulCoverage,
82
+ rootDirectoryUrl,
83
+ );
84
+ fileByFileCoverage = composeV8AndIstanbul(
85
+ v8FileByFileCoverage,
86
+ fileByFileIstanbulCoverage,
87
+ { v8ConflictWarning: coverage.v8ConflictWarning },
88
+ );
89
+ } else {
90
+ fileByFileCoverage = v8FileByFileCoverage;
91
+ }
92
+ }
93
+ // get istanbul only
94
+ else if (fileByFileIstanbulCoverage) {
95
+ fileByFileCoverage = normalizeFileByFileCoveragePaths(
96
+ fileByFileIstanbulCoverage,
97
+ rootDirectoryUrl,
98
+ );
99
+ }
100
+ // no coverage found in execution (or zero file where executed)
101
+ else {
102
+ fileByFileCoverage = {};
103
+ }
104
+
105
+ // now add coverage for file not covered
106
+ if (coverage.includeMissing) {
107
+ const missingFileByFileCoverage = await getMissingFileByFileCoverage({
108
+ signal,
109
+ rootDirectoryUrl,
110
+ coverageInclude: coverage.include,
111
+ fileByFileCoverage,
112
+ });
113
+ Object.assign(
114
+ fileByFileCoverage,
115
+ normalizeFileByFileCoveragePaths(
116
+ missingFileByFileCoverage,
117
+ rootDirectoryUrl,
118
+ ),
119
+ );
120
+ }
121
+
122
+ return fileByFileCoverage;
123
+ };
124
+
125
+ const getCoverageFromTestPlanResults = async (
126
+ executionResults,
127
+ { signal, onMissing },
128
+ ) => {
129
+ const operation = Abort.startOperation();
130
+ operation.addAbortSignal(signal);
131
+
132
+ try {
133
+ let v8Coverage;
134
+ let fileByFileIstanbulCoverage;
135
+
136
+ // collect v8 and istanbul coverage from executions
137
+ for (const file of Object.keys(executionResults)) {
138
+ const executionResultForFile = executionResults[file];
139
+ for (const executionName of Object.keys(executionResultForFile)) {
140
+ operation.throwIfAborted();
141
+
142
+ const executionResultForFileOnRuntime =
143
+ executionResultForFile[executionName];
144
+ const { coverageFileUrl } = executionResultForFileOnRuntime;
145
+ let executionCoverage;
146
+ try {
147
+ executionCoverage = JSON.parse(
148
+ String(readFileSync(new URL(coverageFileUrl))),
149
+ );
150
+ } catch (e) {
151
+ if (e.code === "ENOENT" || e.name === "SyntaxError") {
152
+ onMissing({
153
+ executionName,
154
+ file,
155
+ executionResult: executionResultForFileOnRuntime,
156
+ });
157
+ continue;
158
+ }
159
+ throw e;
160
+ }
161
+
162
+ if (isV8Coverage(executionCoverage)) {
163
+ v8Coverage = v8Coverage
164
+ ? composeTwoV8Coverages(v8Coverage, executionCoverage)
165
+ : executionCoverage;
166
+ } else {
167
+ fileByFileIstanbulCoverage = fileByFileIstanbulCoverage
168
+ ? composeTwoFileByFileIstanbulCoverages(
169
+ fileByFileIstanbulCoverage,
170
+ executionCoverage,
171
+ )
172
+ : executionCoverage;
173
+ }
174
+ }
175
+ }
176
+
177
+ return {
178
+ v8Coverage,
179
+ fileByFileIstanbulCoverage,
180
+ };
181
+ } finally {
182
+ await operation.end();
183
+ }
184
+ };
185
+
186
+ const isV8Coverage = (coverage) => Boolean(coverage.result);
@@ -3,12 +3,12 @@ import { collectFiles } from "@jsenv/filesystem";
3
3
  export const listRelativeFileUrlToCover = async ({
4
4
  signal,
5
5
  rootDirectoryUrl,
6
- coverageConfig,
6
+ coverageInclude,
7
7
  }) => {
8
8
  const matchingFileResultArray = await collectFiles({
9
9
  signal,
10
10
  directoryUrl: rootDirectoryUrl,
11
- associations: { cover: coverageConfig },
11
+ associations: { cover: coverageInclude },
12
12
  predicate: ({ cover }) => cover,
13
13
  });
14
14
  return matchingFileResultArray.map(({ relativeUrl }) => relativeUrl);
@@ -6,13 +6,13 @@ import { relativeUrlToEmptyCoverage } from "./empty_coverage_factory.js";
6
6
  export const getMissingFileByFileCoverage = async ({
7
7
  signal,
8
8
  rootDirectoryUrl,
9
- coverageConfig,
9
+ coverageInclude,
10
10
  fileByFileCoverage,
11
11
  }) => {
12
12
  const relativeUrlsToCover = await listRelativeFileUrlToCover({
13
13
  signal,
14
14
  rootDirectoryUrl,
15
- coverageConfig,
15
+ coverageInclude,
16
16
  });
17
17
  const relativeUrlsMissing = relativeUrlsToCover.filter((relativeUrlToCover) =>
18
18
  Object.keys(fileByFileCoverage).every((key) => {