@jsenv/test 1.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 (56) hide show
  1. package/dist/controllable_child_process.mjs +129 -0
  2. package/dist/controllable_worker_thread.mjs +91 -0
  3. package/dist/importmap_node_loader.mjs +551 -0
  4. package/dist/js/execute_using_dynamic_import.js +850 -0
  5. package/dist/js/v8_coverage.js +508 -0
  6. package/dist/jsenv_test.js +5993 -0
  7. package/dist/no_experimental_warnings.cjs +10 -0
  8. package/package.json +56 -0
  9. package/src/coverage/babel_plugin_instrument.js +48 -0
  10. package/src/coverage/coverage_reporter_html_directory.js +32 -0
  11. package/src/coverage/coverage_reporter_json_file.js +17 -0
  12. package/src/coverage/coverage_reporter_text_log.js +19 -0
  13. package/src/coverage/empty_coverage_factory.js +52 -0
  14. package/src/coverage/file_by_file_coverage.js +25 -0
  15. package/src/coverage/istanbul_coverage_composition.js +28 -0
  16. package/src/coverage/istanbul_coverage_map_from_coverage.js +16 -0
  17. package/src/coverage/list_files_not_covered.js +15 -0
  18. package/src/coverage/missing_coverage.js +41 -0
  19. package/src/coverage/report_to_coverage.js +198 -0
  20. package/src/coverage/v8_and_istanbul.js +37 -0
  21. package/src/coverage/v8_coverage.js +26 -0
  22. package/src/coverage/v8_coverage_composition.js +24 -0
  23. package/src/coverage/v8_coverage_node_directory.js +85 -0
  24. package/src/coverage/v8_coverage_to_istanbul.js +99 -0
  25. package/src/execution/execute.js +111 -0
  26. package/src/execution/execute_steps.js +423 -0
  27. package/src/execution/execute_test_plan.js +372 -0
  28. package/src/execution/execution_colors.js +10 -0
  29. package/src/execution/execution_steps.js +65 -0
  30. package/src/execution/gc.js +9 -0
  31. package/src/execution/logs_file_execution.js +427 -0
  32. package/src/execution/logs_file_execution.test.mjs +41 -0
  33. package/src/execution/run.js +160 -0
  34. package/src/execution/web_server_param.js +73 -0
  35. package/src/main.js +10 -0
  36. package/src/runtime_browsers/chromium.js +24 -0
  37. package/src/runtime_browsers/firefox.js +23 -0
  38. package/src/runtime_browsers/middleware_istanbul.js +65 -0
  39. package/src/runtime_browsers/middleware_js_supervisor.js +98 -0
  40. package/src/runtime_browsers/using_playwright.js +573 -0
  41. package/src/runtime_browsers/webkit.js +40 -0
  42. package/src/runtime_node/child_exec_options.js +166 -0
  43. package/src/runtime_node/controllable_child_process.mjs +135 -0
  44. package/src/runtime_node/controllable_worker_thread.mjs +103 -0
  45. package/src/runtime_node/exec_options.js +57 -0
  46. package/src/runtime_node/execute_using_dynamic_import.js +53 -0
  47. package/src/runtime_node/exit_codes.js +9 -0
  48. package/src/runtime_node/importmap_node_loader.mjs +51 -0
  49. package/src/runtime_node/importmap_node_loader_file_url.js +4 -0
  50. package/src/runtime_node/kill_process_tree.js +76 -0
  51. package/src/runtime_node/no_experimental_warnings.cjs +13 -0
  52. package/src/runtime_node/no_experimental_warnings_file_url.js +4 -0
  53. package/src/runtime_node/node_child_process.js +374 -0
  54. package/src/runtime_node/node_execution_performance.js +67 -0
  55. package/src/runtime_node/node_worker_thread.js +301 -0
  56. package/src/runtime_node/profiler_v8_coverage.js +56 -0
@@ -0,0 +1,10 @@
1
+ // see https://github.com/nodejs/node/issues/47478
2
+ const originalEmit = process.emit;
3
+ process.emit = (event, error) => {
4
+ if (event === "warning" && error.name === "ExperimentalWarning") {
5
+ if (error.message.includes("--experimental-loader") || error.message.includes("Custom ESM Loaders is an experimental")) {
6
+ return false;
7
+ }
8
+ }
9
+ return originalEmit.call(process, event, error);
10
+ };
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@jsenv/test",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/jsenv/core",
8
+ "directory": "packages/test"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "type": "module",
14
+ "exports": {
15
+ ".": {
16
+ "import": {
17
+ "development": "./src/main.js",
18
+ "default": "./dist/jsenv_test.js"
19
+ }
20
+ },
21
+ "./*": "./*"
22
+ },
23
+ "files": [
24
+ "/dist/",
25
+ "/src/"
26
+ ],
27
+ "scripts": {
28
+ "test": "node --conditions=development ./scripts/test.mjs",
29
+ "build": "node --conditions=development ./scripts/build.mjs",
30
+ "prepublishOnly": "npm run build"
31
+ },
32
+ "optionalDependencies": {
33
+ "playwright": "1.x"
34
+ },
35
+ "dependencies": {
36
+ "@jsenv/core": "35.0.0",
37
+ "@jsenv/abort": "4.2.4",
38
+ "@jsenv/ast": "3.0.6",
39
+ "@jsenv/filesystem": "4.2.3",
40
+ "@jsenv/log": "3.3.4",
41
+ "@jsenv/urls": "2.0.0",
42
+ "@jsenv/url-meta": "8.1.0",
43
+ "@jsenv/uneval": "1.6.0",
44
+ "@jsenv/utils": "2.0.1",
45
+ "v8-to-istanbul": "9.1.0",
46
+ "istanbul-lib-coverage": "3.2.0",
47
+ "istanbul-lib-instrument": "5.2.1",
48
+ "istanbul-lib-report": "3.0.0",
49
+ "istanbul-reports": "3.1.5",
50
+ "@paralleldrive/cuid2": "2.2.0",
51
+ "@c88/v8-coverage": "0.1.1",
52
+ "strip-ansi": "7.0.1",
53
+ "wrap-ansi": "8.1.0",
54
+ "pidtree": "0.6.0"
55
+ }
56
+ }
@@ -0,0 +1,48 @@
1
+ import { requireFromJsenv } from "@jsenv/core/src/helpers/require_from_jsenv.js"
2
+
3
+ // https://github.com/istanbuljs/babel-plugin-istanbul/blob/321740f7b25d803f881466ea819d870f7ed6a254/src/index.js
4
+
5
+ export const babelPluginInstrument = (api, { useInlineSourceMaps = false }) => {
6
+ const { programVisitor } = requireFromJsenv("istanbul-lib-instrument")
7
+ const { types } = api
8
+
9
+ return {
10
+ name: "transform-instrument",
11
+ visitor: {
12
+ Program: {
13
+ enter(path) {
14
+ const { file } = this
15
+ const { opts } = file
16
+ let inputSourceMap
17
+ if (useInlineSourceMaps) {
18
+ // https://github.com/istanbuljs/babel-plugin-istanbul/commit/a9e15643d249a2985e4387e4308022053b2cd0ad#diff-1fdf421c05c1140f6d71444ea2b27638R65
19
+ inputSourceMap =
20
+ opts.inputSourceMap || file.inputMap
21
+ ? file.inputMap.sourcemap
22
+ : null
23
+ } else {
24
+ inputSourceMap = opts.inputSourceMap
25
+ }
26
+ this.__dv__ = programVisitor(
27
+ types,
28
+ opts.filenameRelative || opts.filename,
29
+ {
30
+ coverageVariable: "__coverage__",
31
+ inputSourceMap,
32
+ },
33
+ )
34
+ this.__dv__.enter(path)
35
+ },
36
+
37
+ exit(path) {
38
+ if (!this.__dv__) {
39
+ return
40
+ }
41
+ const object = this.__dv__.exit(path)
42
+ // object got two properties: fileCoverage and sourceMappingURL
43
+ this.file.metadata.coverage = object.fileCoverage
44
+ },
45
+ },
46
+ },
47
+ }
48
+ }
@@ -0,0 +1,32 @@
1
+ import { fileURLToPath } from "node:url"
2
+ import { readFileSync } from "node:fs"
3
+
4
+ import { requireFromJsenv } from "@jsenv/core/src/helpers/require_from_jsenv.js"
5
+ import { istanbulCoverageMapFromCoverage } from "./istanbul_coverage_map_from_coverage.js"
6
+
7
+ export const generateCoverageHtmlDirectory = async (
8
+ coverage,
9
+ {
10
+ rootDirectoryUrl,
11
+ coverageHtmlDirectoryRelativeUrl,
12
+ coverageReportSkipEmpty,
13
+ coverageReportSkipFull,
14
+ },
15
+ ) => {
16
+ const libReport = requireFromJsenv("istanbul-lib-report")
17
+ const reports = requireFromJsenv("istanbul-reports")
18
+
19
+ const context = libReport.createContext({
20
+ dir: fileURLToPath(rootDirectoryUrl),
21
+ coverageMap: istanbulCoverageMapFromCoverage(coverage),
22
+ sourceFinder: (path) =>
23
+ readFileSync(new URL(path, rootDirectoryUrl), "utf8"),
24
+ })
25
+
26
+ const report = reports.create("html", {
27
+ skipEmpty: coverageReportSkipEmpty,
28
+ skipFull: coverageReportSkipFull,
29
+ subdir: coverageHtmlDirectoryRelativeUrl,
30
+ })
31
+ report.execute(context)
32
+ }
@@ -0,0 +1,17 @@
1
+ import { writeFile } from "@jsenv/filesystem"
2
+ import { urlToFileSystemPath } from "@jsenv/urls"
3
+ import { byteAsFileSize } from "@jsenv/log"
4
+
5
+ export const generateCoverageJsonFile = async ({
6
+ coverage,
7
+ coverageJsonFileUrl,
8
+ logger,
9
+ }) => {
10
+ const coverageAsText = JSON.stringify(coverage, null, " ")
11
+ logger.info(
12
+ `-> ${urlToFileSystemPath(coverageJsonFileUrl)} (${byteAsFileSize(
13
+ Buffer.byteLength(coverageAsText),
14
+ )})`,
15
+ )
16
+ await writeFile(coverageJsonFileUrl, coverageAsText)
17
+ }
@@ -0,0 +1,19 @@
1
+ import { requireFromJsenv } from "@jsenv/core/src/helpers/require_from_jsenv.js"
2
+ import { istanbulCoverageMapFromCoverage } from "./istanbul_coverage_map_from_coverage.js"
3
+
4
+ export const generateCoverageTextLog = (
5
+ coverage,
6
+ { coverageReportSkipEmpty, coverageReportSkipFull },
7
+ ) => {
8
+ const libReport = requireFromJsenv("istanbul-lib-report")
9
+ const reports = requireFromJsenv("istanbul-reports")
10
+
11
+ const context = libReport.createContext({
12
+ coverageMap: istanbulCoverageMapFromCoverage(coverage),
13
+ })
14
+ const report = reports.create("text", {
15
+ skipEmpty: coverageReportSkipEmpty,
16
+ skipFull: coverageReportSkipFull,
17
+ })
18
+ report.execute(context)
19
+ }
@@ -0,0 +1,52 @@
1
+ import { readFile } from "@jsenv/filesystem"
2
+ import { resolveUrl } from "@jsenv/urls"
3
+ import { Abort } from "@jsenv/abort"
4
+ import { applyBabelPlugins } from "@jsenv/ast"
5
+ import { requireFromJsenv } from "@jsenv/core/src/helpers/require_from_jsenv.js"
6
+
7
+ import { babelPluginInstrument } from "./babel_plugin_instrument.js"
8
+
9
+ export const relativeUrlToEmptyCoverage = async (
10
+ relativeUrl,
11
+ { signal, rootDirectoryUrl },
12
+ ) => {
13
+ const operation = Abort.startOperation()
14
+ operation.addAbortSignal(signal)
15
+
16
+ try {
17
+ const fileUrl = resolveUrl(relativeUrl, rootDirectoryUrl)
18
+ const content = await readFile(fileUrl, { as: "string" })
19
+
20
+ operation.throwIfAborted()
21
+ const { metadata } = await applyBabelPlugins({
22
+ babelPlugins: [babelPluginInstrument],
23
+ urlInfo: {
24
+ originalUrl: fileUrl,
25
+ content,
26
+ },
27
+ })
28
+ const { coverage } = metadata
29
+ if (!coverage) {
30
+ throw new Error(`missing coverage for file`)
31
+ }
32
+ // https://github.com/gotwarlost/istanbul/blob/bc84c315271a5dd4d39bcefc5925cfb61a3d174a/lib/command/common/run-with-cover.js#L229
33
+ Object.keys(coverage.s).forEach(function (key) {
34
+ coverage.s[key] = 0
35
+ })
36
+ return coverage
37
+ } catch (e) {
38
+ if (e && e.code === "PARSE_ERROR") {
39
+ // return an empty coverage for that file when
40
+ // it contains a syntax error
41
+ return createEmptyCoverage(relativeUrl)
42
+ }
43
+ throw e
44
+ } finally {
45
+ await operation.end()
46
+ }
47
+ }
48
+
49
+ const createEmptyCoverage = (relativeUrl) => {
50
+ const { createFileCoverage } = requireFromJsenv("istanbul-lib-coverage")
51
+ return createFileCoverage(relativeUrl).toJSON()
52
+ }
@@ -0,0 +1,25 @@
1
+ import {
2
+ urlToRelativeUrl,
3
+ fileSystemPathToUrl,
4
+ isFileSystemPath,
5
+ } from "@jsenv/urls"
6
+
7
+ export const normalizeFileByFileCoveragePaths = (
8
+ fileByFileCoverage,
9
+ rootDirectoryUrl,
10
+ ) => {
11
+ const fileByFileNormalized = {}
12
+ Object.keys(fileByFileCoverage).forEach((key) => {
13
+ const fileCoverage = fileByFileCoverage[key]
14
+ const { path } = fileCoverage
15
+ const url = isFileSystemPath(path)
16
+ ? fileSystemPathToUrl(path)
17
+ : new URL(path, rootDirectoryUrl).href
18
+ const relativeUrl = urlToRelativeUrl(url, rootDirectoryUrl)
19
+ fileByFileNormalized[`./${relativeUrl}`] = {
20
+ ...fileCoverage,
21
+ path: `./${relativeUrl}`,
22
+ }
23
+ })
24
+ return fileByFileNormalized
25
+ }
@@ -0,0 +1,28 @@
1
+ import { requireFromJsenv } from "@jsenv/core/src/helpers/require_from_jsenv.js"
2
+
3
+ export const composeTwoFileByFileIstanbulCoverages = (
4
+ firstFileByFileIstanbulCoverage,
5
+ secondFileByFileIstanbulCoverage,
6
+ ) => {
7
+ const fileByFileIstanbulCoverage = {}
8
+ Object.keys(firstFileByFileIstanbulCoverage).forEach((key) => {
9
+ fileByFileIstanbulCoverage[key] = firstFileByFileIstanbulCoverage[key]
10
+ })
11
+ Object.keys(secondFileByFileIstanbulCoverage).forEach((key) => {
12
+ const firstCoverage = firstFileByFileIstanbulCoverage[key]
13
+ const secondCoverage = secondFileByFileIstanbulCoverage[key]
14
+ fileByFileIstanbulCoverage[key] = firstCoverage
15
+ ? merge(firstCoverage, secondCoverage)
16
+ : secondCoverage
17
+ })
18
+
19
+ return fileByFileIstanbulCoverage
20
+ }
21
+
22
+ const merge = (firstIstanbulCoverage, secondIstanbulCoverage) => {
23
+ const { createFileCoverage } = requireFromJsenv("istanbul-lib-coverage")
24
+ const istanbulFileCoverageObject = createFileCoverage(firstIstanbulCoverage)
25
+ istanbulFileCoverageObject.merge(secondIstanbulCoverage)
26
+ const istanbulCoverage = istanbulFileCoverageObject.toJSON()
27
+ return istanbulCoverage
28
+ }
@@ -0,0 +1,16 @@
1
+ import { requireFromJsenv } from "@jsenv/core/src/helpers/require_from_jsenv.js"
2
+
3
+ export const istanbulCoverageMapFromCoverage = (coverage) => {
4
+ const { createCoverageMap } = requireFromJsenv("istanbul-lib-coverage")
5
+
6
+ const coverageAdjusted = {}
7
+ Object.keys(coverage).forEach((key) => {
8
+ coverageAdjusted[key.slice(2)] = {
9
+ ...coverage[key],
10
+ path: key.slice(2),
11
+ }
12
+ })
13
+
14
+ const coverageMap = createCoverageMap(coverageAdjusted)
15
+ return coverageMap
16
+ }
@@ -0,0 +1,15 @@
1
+ import { collectFiles } from "@jsenv/filesystem"
2
+
3
+ export const listRelativeFileUrlToCover = async ({
4
+ signal,
5
+ rootDirectoryUrl,
6
+ coverageConfig,
7
+ }) => {
8
+ const matchingFileResultArray = await collectFiles({
9
+ signal,
10
+ directoryUrl: rootDirectoryUrl,
11
+ associations: { cover: coverageConfig },
12
+ predicate: ({ cover }) => cover,
13
+ })
14
+ return matchingFileResultArray.map(({ relativeUrl }) => relativeUrl)
15
+ }
@@ -0,0 +1,41 @@
1
+ import { Abort } from "@jsenv/abort"
2
+
3
+ import { listRelativeFileUrlToCover } from "./list_files_not_covered.js"
4
+ import { relativeUrlToEmptyCoverage } from "./empty_coverage_factory.js"
5
+
6
+ export const getMissingFileByFileCoverage = async ({
7
+ signal,
8
+ rootDirectoryUrl,
9
+ coverageConfig,
10
+ fileByFileCoverage,
11
+ }) => {
12
+ const relativeUrlsToCover = await listRelativeFileUrlToCover({
13
+ signal,
14
+ rootDirectoryUrl,
15
+ coverageConfig,
16
+ })
17
+ const relativeUrlsMissing = relativeUrlsToCover.filter((relativeUrlToCover) =>
18
+ Object.keys(fileByFileCoverage).every((key) => {
19
+ return key !== `./${relativeUrlToCover}`
20
+ }),
21
+ )
22
+
23
+ const operation = Abort.startOperation()
24
+ operation.addAbortSignal(signal)
25
+ const missingFileByFileCoverage = {}
26
+ await relativeUrlsMissing.reduce(async (previous, relativeUrlMissing) => {
27
+ operation.throwIfAborted()
28
+ await previous
29
+ await operation.withSignal(async (signal) => {
30
+ const emptyCoverage = await relativeUrlToEmptyCoverage(
31
+ relativeUrlMissing,
32
+ {
33
+ signal,
34
+ rootDirectoryUrl,
35
+ },
36
+ )
37
+ missingFileByFileCoverage[`./${relativeUrlMissing}`] = emptyCoverage
38
+ })
39
+ }, Promise.resolve())
40
+ return missingFileByFileCoverage
41
+ }
@@ -0,0 +1,198 @@
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 reportToCoverage = async (
14
+ report,
15
+ {
16
+ signal,
17
+ logger,
18
+ rootDirectoryUrl,
19
+ coverageConfig,
20
+ coverageIncludeMissing,
21
+ coverageMethodForNodeJs,
22
+ coverageV8ConflictWarning,
23
+ },
24
+ ) => {
25
+ // collect v8 and istanbul coverage from executions
26
+ let { v8Coverage, fileByFileIstanbulCoverage } = await getCoverageFromReport({
27
+ signal,
28
+ report,
29
+ onMissing: ({ file, executionResult, executionName }) => {
30
+ // several reasons not to have coverage here:
31
+ // 1. the file we executed did not import an instrumented file.
32
+ // - a test file without import
33
+ // - a test file importing only file excluded from coverage
34
+ // - a coverDescription badly configured so that we don't realize
35
+ // a file should be covered
36
+
37
+ // 2. the file we wanted to executed timedout
38
+ // - infinite loop
39
+ // - too extensive operation
40
+ // - a badly configured or too low allocatedMs for that execution.
41
+
42
+ // 3. the file we wanted to execute contains syntax-error
43
+
44
+ // in any scenario we are fine because
45
+ // coverDescription will generate empty coverage for files
46
+ // that were suppose to be coverage but were not.
47
+ if (
48
+ executionResult.status === "completed" &&
49
+ executionResult.type === "node" &&
50
+ coverageMethodForNodeJs !== "NODE_V8_COVERAGE"
51
+ ) {
52
+ logger.warn(
53
+ `"${executionName}" execution of ${file} did not properly write coverage into ${executionResult.coverageFileUrl}`,
54
+ )
55
+ }
56
+ },
57
+ })
58
+
59
+ if (coverageMethodForNodeJs === "NODE_V8_COVERAGE") {
60
+ await readNodeV8CoverageDirectory({
61
+ logger,
62
+ signal,
63
+ onV8Coverage: async (nodeV8Coverage) => {
64
+ const nodeV8CoverageLight = await filterV8Coverage(nodeV8Coverage, {
65
+ rootDirectoryUrl,
66
+ coverageConfig,
67
+ })
68
+ v8Coverage = v8Coverage
69
+ ? composeTwoV8Coverages(v8Coverage, nodeV8CoverageLight)
70
+ : nodeV8CoverageLight
71
+ },
72
+ })
73
+ }
74
+
75
+ // try to merge v8 with istanbul, if any
76
+ let fileByFileCoverage
77
+ if (v8Coverage) {
78
+ let v8FileByFileCoverage = await v8CoverageToIstanbul(v8Coverage, {
79
+ signal,
80
+ })
81
+
82
+ v8FileByFileCoverage = normalizeFileByFileCoveragePaths(
83
+ v8FileByFileCoverage,
84
+ rootDirectoryUrl,
85
+ )
86
+
87
+ if (fileByFileIstanbulCoverage) {
88
+ fileByFileIstanbulCoverage = normalizeFileByFileCoveragePaths(
89
+ fileByFileIstanbulCoverage,
90
+ rootDirectoryUrl,
91
+ )
92
+ fileByFileCoverage = composeV8AndIstanbul(
93
+ v8FileByFileCoverage,
94
+ fileByFileIstanbulCoverage,
95
+ { coverageV8ConflictWarning },
96
+ )
97
+ } else {
98
+ fileByFileCoverage = v8FileByFileCoverage
99
+ }
100
+ }
101
+ // get istanbul only
102
+ else if (fileByFileIstanbulCoverage) {
103
+ fileByFileCoverage = normalizeFileByFileCoveragePaths(
104
+ fileByFileIstanbulCoverage,
105
+ rootDirectoryUrl,
106
+ )
107
+ }
108
+ // no coverage found in execution (or zero file where executed)
109
+ else {
110
+ fileByFileCoverage = {}
111
+ }
112
+
113
+ // now add coverage for file not covered
114
+ if (coverageIncludeMissing) {
115
+ const missingFileByFileCoverage = await getMissingFileByFileCoverage({
116
+ signal,
117
+ rootDirectoryUrl,
118
+ coverageConfig,
119
+ fileByFileCoverage,
120
+ })
121
+ Object.assign(
122
+ fileByFileCoverage,
123
+ normalizeFileByFileCoveragePaths(
124
+ missingFileByFileCoverage,
125
+ rootDirectoryUrl,
126
+ ),
127
+ )
128
+ }
129
+
130
+ return fileByFileCoverage
131
+ }
132
+
133
+ const getCoverageFromReport = async ({ signal, report, onMissing }) => {
134
+ const operation = Abort.startOperation()
135
+ operation.addAbortSignal(signal)
136
+
137
+ try {
138
+ let v8Coverage
139
+ let fileByFileIstanbulCoverage
140
+
141
+ // collect v8 and istanbul coverage from executions
142
+ await Object.keys(report).reduce(async (previous, file) => {
143
+ operation.throwIfAborted()
144
+ await previous
145
+
146
+ const executionResultForFile = report[file]
147
+ await Object.keys(executionResultForFile).reduce(
148
+ async (previous, executionName) => {
149
+ operation.throwIfAborted()
150
+ await previous
151
+
152
+ const executionResultForFileOnRuntime =
153
+ executionResultForFile[executionName]
154
+ const { coverageFileUrl } = executionResultForFileOnRuntime
155
+ let executionCoverage
156
+ try {
157
+ executionCoverage = JSON.parse(
158
+ String(readFileSync(new URL(coverageFileUrl))),
159
+ )
160
+ } catch (e) {
161
+ if (e.code === "ENOENT" || e.name === "SyntaxError") {
162
+ onMissing({
163
+ executionName,
164
+ file,
165
+ executionResult: executionResultForFileOnRuntime,
166
+ })
167
+ return
168
+ }
169
+ throw e
170
+ }
171
+
172
+ if (isV8Coverage(executionCoverage)) {
173
+ v8Coverage = v8Coverage
174
+ ? composeTwoV8Coverages(v8Coverage, executionCoverage)
175
+ : executionCoverage
176
+ } else {
177
+ fileByFileIstanbulCoverage = fileByFileIstanbulCoverage
178
+ ? composeTwoFileByFileIstanbulCoverages(
179
+ fileByFileIstanbulCoverage,
180
+ executionCoverage,
181
+ )
182
+ : executionCoverage
183
+ }
184
+ },
185
+ Promise.resolve(),
186
+ )
187
+ }, Promise.resolve())
188
+
189
+ return {
190
+ v8Coverage,
191
+ fileByFileIstanbulCoverage,
192
+ }
193
+ } finally {
194
+ await operation.end()
195
+ }
196
+ }
197
+
198
+ const isV8Coverage = (coverage) => Boolean(coverage.result)
@@ -0,0 +1,37 @@
1
+ import { createDetailedMessage } from "@jsenv/log"
2
+
3
+ export const composeV8AndIstanbul = (
4
+ v8FileByFileCoverage,
5
+ istanbulFileByFileCoverage,
6
+ { coverageV8ConflictWarning },
7
+ ) => {
8
+ const fileByFileCoverage = {}
9
+ const v8Files = Object.keys(v8FileByFileCoverage)
10
+ const istanbulFiles = Object.keys(istanbulFileByFileCoverage)
11
+
12
+ v8Files.forEach((key) => {
13
+ fileByFileCoverage[key] = v8FileByFileCoverage[key]
14
+ })
15
+ istanbulFiles.forEach((key) => {
16
+ const v8Coverage = v8FileByFileCoverage[key]
17
+ if (v8Coverage) {
18
+ if (coverageV8ConflictWarning) {
19
+ console.warn(
20
+ createDetailedMessage(
21
+ `Coverage conflict on "${key}", found two coverage that cannot be merged together: v8 and istanbul. The istanbul coverage will be ignored.`,
22
+ {
23
+ 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)`,
24
+ suggestion:
25
+ "You can disable this warning with coverageV8ConflictWarning: false",
26
+ },
27
+ ),
28
+ )
29
+ }
30
+ fileByFileCoverage[key] = v8Coverage
31
+ } else {
32
+ fileByFileCoverage[key] = istanbulFileByFileCoverage[key]
33
+ }
34
+ })
35
+
36
+ return fileByFileCoverage
37
+ }
@@ -0,0 +1,26 @@
1
+ import { URL_META } from "@jsenv/url-meta"
2
+
3
+ export const filterV8Coverage = async (
4
+ v8Coverage,
5
+ { rootDirectoryUrl, coverageConfig },
6
+ ) => {
7
+ const associations = URL_META.resolveAssociations(
8
+ { cover: coverageConfig },
9
+ rootDirectoryUrl,
10
+ )
11
+ const urlShouldBeCovered = (url) => {
12
+ const { cover } = URL_META.applyAssociations({
13
+ url: new URL(url, rootDirectoryUrl).href,
14
+ associations,
15
+ })
16
+ return cover
17
+ }
18
+
19
+ const v8CoverageFiltered = {
20
+ ...v8Coverage,
21
+ result: v8Coverage.result.filter((fileReport) =>
22
+ urlShouldBeCovered(fileReport.url),
23
+ ),
24
+ }
25
+ return v8CoverageFiltered
26
+ }
@@ -0,0 +1,24 @@
1
+ import { requireFromJsenv } from "@jsenv/core/src/helpers/require_from_jsenv.js"
2
+
3
+ export const composeTwoV8Coverages = (firstV8Coverage, secondV8Coverage) => {
4
+ if (secondV8Coverage.result.length === 0) {
5
+ return firstV8Coverage
6
+ }
7
+
8
+ // eslint-disable-next-line import/no-unresolved
9
+ const { mergeProcessCovs } = requireFromJsenv("@c88/v8-coverage")
10
+ // "mergeProcessCovs" do not preserves source-map-cache during the merge
11
+ // so we store sourcemap cache now
12
+ const sourceMapCache = {}
13
+ const visit = (coverageReport) => {
14
+ if (coverageReport["source-map-cache"]) {
15
+ Object.assign(sourceMapCache, coverageReport["source-map-cache"])
16
+ }
17
+ }
18
+ visit(firstV8Coverage)
19
+ visit(secondV8Coverage)
20
+ const v8Coverage = mergeProcessCovs([firstV8Coverage, secondV8Coverage])
21
+ v8Coverage["source-map-cache"] = sourceMapCache
22
+
23
+ return v8Coverage
24
+ }