@jsenv/core 23.2.2 → 23.4.2

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/{license → LICENSE} +0 -0
  2. package/package.json +13 -14
  3. package/readme.md +4 -4
  4. package/src/buildProject.js +12 -13
  5. package/src/execute.js +92 -93
  6. package/src/executeTestPlan.js +9 -8
  7. package/src/internal/browser-launcher/executeHtmlFile.js +26 -23
  8. package/src/internal/building/buildUsingRollup.js +3 -4
  9. package/src/internal/building/build_logs.js +7 -6
  10. package/src/internal/building/createJsenvRollupPlugin.js +9 -14
  11. package/src/internal/building/url_trace.js +3 -4
  12. package/src/internal/compiling/createCompiledFileService.js +21 -6
  13. package/src/internal/compiling/startCompileServer.js +55 -46
  14. package/src/internal/executing/coverage/babel_plugin_instrument.js +1 -0
  15. package/src/internal/executing/coverage/reportToCoverage.js +147 -120
  16. package/src/internal/executing/{coverage → coverage_empty}/createEmptyCoverage.js +0 -0
  17. package/src/internal/executing/coverage_empty/list_files_not_covered.js +20 -0
  18. package/src/internal/executing/{coverage → coverage_empty}/relativeUrlToEmptyCoverage.js +3 -4
  19. package/src/internal/executing/{coverage/generateCoverageHtmlDirectory.js → coverage_reporter/coverage_reporter_html_directory.js} +11 -4
  20. package/src/internal/executing/{coverage/generateCoverageJsonFile.js → coverage_reporter/coverage_reporter_json_file.js} +0 -0
  21. package/src/internal/executing/{coverage/generateCoverageTextLog.js → coverage_reporter/coverage_reporter_text_log.js} +4 -2
  22. package/src/internal/executing/{coverage → coverage_reporter}/istanbulCoverageMapFromCoverage.js +0 -0
  23. package/src/internal/executing/{coverage/normalizeIstanbulCoverage.js → coverage_utils/file_by_file_coverage.js} +9 -7
  24. package/src/internal/executing/coverage_utils/istanbul_coverage_composition.js +28 -0
  25. package/src/internal/executing/coverage_utils/v8_and_istanbul.js +38 -0
  26. package/src/internal/executing/coverage_utils/v8_coverage_composition.js +23 -0
  27. package/src/internal/executing/coverage_utils/v8_coverage_from_directory.js +65 -0
  28. package/src/internal/executing/coverage_utils/v8_coverage_to_istanbul.js +90 -0
  29. package/src/internal/executing/createSummaryLog.js +15 -15
  30. package/src/internal/executing/executeConcurrently.js +92 -32
  31. package/src/internal/executing/executePlan.js +84 -81
  32. package/src/internal/executing/executionLogs.js +14 -18
  33. package/src/internal/executing/execution_colors.js +6 -12
  34. package/src/internal/executing/launchAndExecute.js +172 -169
  35. package/src/internal/node-launcher/createControllableNodeProcess.js +26 -23
  36. package/src/launchBrowser.js +72 -69
  37. package/src/launchNode.js +11 -99
  38. package/src/startExploring.js +2 -17
  39. package/src/abort/abortable.js +0 -172
  40. package/src/abort/callback_list.js +0 -64
  41. package/src/abort/callback_race.js +0 -34
  42. package/src/abort/cleaner.js +0 -22
  43. package/src/abort/main.js +0 -32
  44. package/src/abort/process_teardown_events.js +0 -59
  45. package/src/internal/createCallbackList.js +0 -21
  46. package/src/internal/executing/coverage/composeIstanbulCoverages.js +0 -108
  47. package/src/internal/executing/coverage/composeV8Coverages.js +0 -20
  48. package/src/internal/executing/coverage/istanbulCoverageFromCoverages.js +0 -43
  49. package/src/internal/executing/coverage/istanbulCoverageFromV8Coverage.js +0 -79
  50. package/src/internal/executing/coverage/v8CoverageFromAllV8Coverages.js +0 -40
  51. package/src/internal/executing/coverage/v8CoverageFromNodeV8Directory.js +0 -67
  52. package/src/internal/executing/logUtils.js +0 -30
  53. package/src/internal/executing/writeLog.js +0 -106
  54. package/src/internal/executing/writeLog.test-manual.js +0 -62
  55. package/src/internal/logs/log_style.js +0 -40
  56. package/src/signal/signal.js +0 -65
@@ -1,8 +1,16 @@
1
- import { collectFiles } from "@jsenv/filesystem"
2
-
3
- import { relativeUrlToEmptyCoverage } from "./relativeUrlToEmptyCoverage.js"
4
- import { istanbulCoverageFromCoverages } from "./istanbulCoverageFromCoverages.js"
5
- import { normalizeIstanbulCoverage } from "./normalizeIstanbulCoverage.js"
1
+ import { readFile } from "@jsenv/filesystem"
2
+
3
+ import {
4
+ visitNodeV8Directory,
5
+ filterV8Coverage,
6
+ } from "../coverage_utils/v8_coverage_from_directory.js"
7
+ import { composeTwoV8Coverages } from "../coverage_utils/v8_coverage_composition.js"
8
+ import { composeTwoFileByFileIstanbulCoverages } from "../coverage_utils/istanbul_coverage_composition.js"
9
+ import { v8CoverageToIstanbul } from "../coverage_utils/v8_coverage_to_istanbul.js"
10
+ import { composeV8AndIstanbul } from "../coverage_utils/v8_and_istanbul.js"
11
+ import { normalizeFileByFileCoveragePaths } from "../coverage_utils/file_by_file_coverage.js"
12
+ import { listRelativeFileUrlToCover } from "../coverage_empty/list_files_not_covered.js"
13
+ import { relativeUrlToEmptyCoverage } from "../coverage_empty/relativeUrlToEmptyCoverage.js"
6
14
 
7
15
  export const reportToCoverage = async (
8
16
  report,
@@ -13,134 +21,153 @@ export const reportToCoverage = async (
13
21
  babelPluginMap,
14
22
  coverageConfig,
15
23
  coverageIncludeMissing,
16
- coverageV8MergeConflictIsExpected,
24
+ coverageIgnorePredicate,
25
+ coverageForceIstanbul,
26
+ coverageV8ConflictWarning,
17
27
  },
18
28
  ) => {
19
- // here we should forward multipleExecutionsOperation.signal
20
- // to allow aborting this too
21
- const istanbulCoverageFromExecution = await executionReportToCoverage(
22
- report,
23
- {
24
- logger,
25
- projectDirectoryUrl,
26
- coverageV8MergeConflictIsExpected,
27
- },
28
- )
29
+ let v8Coverage
30
+ let fileByFileIstanbulCoverage
29
31
 
30
- if (!coverageIncludeMissing) {
31
- return istanbulCoverageFromExecution
32
- }
32
+ // collect v8 and istanbul coverage from executions
33
+ await Object.keys(report).reduce(async (previous, file) => {
34
+ await previous
33
35
 
34
- const relativeFileUrlToCoverArray = await listRelativeFileUrlToCover({
35
- multipleExecutionsOperation,
36
- projectDirectoryUrl,
37
- coverageConfig,
38
- })
36
+ const executionResultForFile = report[file]
37
+ await Object.keys(executionResultForFile).reduce(
38
+ async (previous, executionName) => {
39
+ await previous
40
+
41
+ const executionResultForFileOnRuntime =
42
+ executionResultForFile[executionName]
43
+ const { status, coverageFileUrl } = executionResultForFileOnRuntime
44
+ if (!coverageFileUrl) {
45
+ // several reasons not to have coverage here:
46
+ // 1. the file we executed did not import an instrumented file.
47
+ // - a test file without import
48
+ // - a test file importing only file excluded from coverage
49
+ // - a coverDescription badly configured so that we don't realize
50
+ // a file should be covered
51
+
52
+ // 2. the file we wanted to executed timedout
53
+ // - infinite loop
54
+ // - too extensive operation
55
+ // - a badly configured or too low allocatedMs for that execution.
56
+
57
+ // 3. the file we wanted to execute contains syntax-error
58
+
59
+ // in any scenario we are fine because
60
+ // coverDescription will generate empty coverage for files
61
+ // that were suppose to be coverage but were not.
62
+ if (status === "completed") {
63
+ logger.debug(
64
+ `No execution.coverageFileUrl from execution named "${executionName}" of ${file}`,
65
+ )
66
+ }
67
+ return
68
+ }
39
69
 
40
- const relativeFileUrlMissingCoverageArray =
41
- relativeFileUrlToCoverArray.filter((relativeFileUrlToCover) =>
42
- Object.keys(istanbulCoverageFromExecution).every((key) => {
43
- return key !== `./${relativeFileUrlToCover}`
44
- }),
70
+ const executionCoverage = await readFile(coverageFileUrl, {
71
+ as: "json",
72
+ })
73
+ if (isV8Coverage(executionCoverage)) {
74
+ v8Coverage = v8Coverage
75
+ ? composeTwoV8Coverages(v8Coverage, executionCoverage)
76
+ : executionCoverage
77
+ } else {
78
+ fileByFileIstanbulCoverage = fileByFileIstanbulCoverage
79
+ ? composeTwoFileByFileIstanbulCoverages(
80
+ fileByFileIstanbulCoverage,
81
+ executionCoverage,
82
+ )
83
+ : executionCoverage
84
+ }
85
+ },
86
+ Promise.resolve(),
45
87
  )
46
-
47
- const istanbulCoverageFromMissedFiles = {}
48
- // maybe we should prefer reduce over Promise.all here
49
- // because it creates a LOT of things to do
50
- await Promise.all(
51
- relativeFileUrlMissingCoverageArray.map(
52
- async (relativeFileUrlMissingCoverage) => {
53
- const emptyCoverage = await relativeUrlToEmptyCoverage(
54
- relativeFileUrlMissingCoverage,
55
- {
56
- multipleExecutionsOperation,
57
- projectDirectoryUrl,
58
- babelPluginMap,
59
- },
60
- )
61
- istanbulCoverageFromMissedFiles[relativeFileUrlMissingCoverage] =
62
- emptyCoverage
63
- return emptyCoverage
88
+ }, Promise.resolve())
89
+
90
+ if (!coverageForceIstanbul && process.env.NODE_V8_COVERAGE) {
91
+ await visitNodeV8Directory({
92
+ signal: multipleExecutionsOperation.signal,
93
+ NODE_V8_COVERAGE: process.env.NODE_V8_COVERAGE,
94
+ onV8Coverage: (nodeV8Coverage) => {
95
+ const nodeV8CoverageLight = filterV8Coverage(nodeV8Coverage, {
96
+ coverageIgnorePredicate,
97
+ })
98
+ v8Coverage = v8Coverage
99
+ ? composeTwoV8Coverages(v8Coverage, nodeV8CoverageLight)
100
+ : nodeV8CoverageLight
64
101
  },
65
- ),
66
- )
67
-
68
- return {
69
- ...istanbulCoverageFromExecution, // already normalized
70
- ...normalizeIstanbulCoverage(
71
- istanbulCoverageFromMissedFiles,
72
- projectDirectoryUrl,
73
- ),
74
- }
75
- }
76
-
77
- const listRelativeFileUrlToCover = async ({
78
- multipleExecutionsOperation,
79
- projectDirectoryUrl,
80
- coverageConfig,
81
- }) => {
82
- const structuredMetaMapForCoverage = {
83
- cover: coverageConfig,
102
+ })
84
103
  }
85
104
 
86
- const matchingFileResultArray = await collectFiles({
87
- signal: multipleExecutionsOperation.signal,
88
- directoryUrl: projectDirectoryUrl,
89
- structuredMetaMap: structuredMetaMapForCoverage,
90
- predicate: ({ cover }) => cover,
91
- })
92
-
93
- return matchingFileResultArray.map(({ relativeUrl }) => relativeUrl)
94
- }
105
+ // try to merge v8 with istanbul, if any
106
+ let fileByFileCoverage
107
+ if (v8Coverage) {
108
+ let v8FileByFileCoverage = await v8CoverageToIstanbul(v8Coverage)
95
109
 
96
- const executionReportToCoverage = async (
97
- report,
98
- { logger, projectDirectoryUrl, coverageV8MergeConflictIsExpected },
99
- ) => {
100
- const coverages = []
110
+ v8FileByFileCoverage = normalizeFileByFileCoveragePaths(
111
+ v8FileByFileCoverage,
112
+ projectDirectoryUrl,
113
+ )
101
114
 
102
- Object.keys(report).forEach((file) => {
103
- const executionResultForFile = report[file]
104
- Object.keys(executionResultForFile).forEach((executionName) => {
105
- const executionResultForFileOnRuntime =
106
- executionResultForFile[executionName]
107
-
108
- const { status, coverage } = executionResultForFileOnRuntime
109
- if (!coverage) {
110
- // several reasons not to have coverage here:
111
- // 1. the file we executed did not import an instrumented file.
112
- // - a test file without import
113
- // - a test file importing only file excluded from coverage
114
- // - a coverDescription badly configured so that we don't realize
115
- // a file should be covered
116
-
117
- // 2. the file we wanted to executed timedout
118
- // - infinite loop
119
- // - too extensive operation
120
- // - a badly configured or too low allocatedMs for that execution.
121
-
122
- // 3. the file we wanted to execute contains syntax-error
123
-
124
- // in any scenario we are fine because
125
- // coverDescription will generate empty coverage for files
126
- // that were suppose to be coverage but were not.
127
-
128
- if (status === "completed") {
129
- logger.warn(
130
- `No execution.coverage from execution named "${executionName}" of ${file}`,
131
- )
132
- }
133
- return
134
- }
115
+ if (fileByFileIstanbulCoverage) {
116
+ fileByFileIstanbulCoverage = normalizeFileByFileCoveragePaths(
117
+ fileByFileIstanbulCoverage,
118
+ projectDirectoryUrl,
119
+ )
120
+ fileByFileCoverage = composeV8AndIstanbul(
121
+ v8FileByFileCoverage,
122
+ fileByFileIstanbulCoverage,
123
+ { coverageV8ConflictWarning },
124
+ )
125
+ } else {
126
+ fileByFileCoverage = v8FileByFileCoverage
127
+ }
128
+ }
129
+ // get istanbul only
130
+ else if (fileByFileIstanbulCoverage) {
131
+ fileByFileCoverage = normalizeFileByFileCoveragePaths(
132
+ fileByFileIstanbulCoverage,
133
+ projectDirectoryUrl,
134
+ )
135
+ }
136
+ // no coverage found in execution (or zero file where executed)
137
+ else {
138
+ fileByFileCoverage = {}
139
+ }
135
140
 
136
- coverages.push(coverage)
141
+ // now add coverage for file not covered
142
+ if (coverageIncludeMissing) {
143
+ const relativeUrlsToCover = await listRelativeFileUrlToCover({
144
+ multipleExecutionsOperation,
145
+ projectDirectoryUrl,
146
+ coverageConfig,
137
147
  })
138
- })
139
148
 
140
- const istanbulCoverage = await istanbulCoverageFromCoverages(coverages, {
141
- projectDirectoryUrl,
142
- coverageV8MergeConflictIsExpected,
143
- })
149
+ const relativeUrlsMissing = relativeUrlsToCover.filter(
150
+ (relativeUrlToCover) =>
151
+ Object.keys(fileByFileCoverage).every((key) => {
152
+ return key !== `./${relativeUrlToCover}`
153
+ }),
154
+ )
155
+
156
+ await relativeUrlsMissing.reduce(async (previous, relativeUrlMissing) => {
157
+ const emptyCoverage = await relativeUrlToEmptyCoverage(
158
+ relativeUrlMissing,
159
+ {
160
+ multipleExecutionsOperation,
161
+ projectDirectoryUrl,
162
+ babelPluginMap,
163
+ },
164
+ )
165
+ fileByFileCoverage[`./${relativeUrlMissing}`] = emptyCoverage
166
+ return emptyCoverage
167
+ }, Promise.resolve())
168
+ }
144
169
 
145
- return istanbulCoverage
170
+ return fileByFileCoverage
146
171
  }
172
+
173
+ const isV8Coverage = (coverage) => Boolean(coverage.result)
@@ -0,0 +1,20 @@
1
+ import { collectFiles } from "@jsenv/filesystem"
2
+
3
+ export const listRelativeFileUrlToCover = async ({
4
+ multipleExecutionsOperation,
5
+ projectDirectoryUrl,
6
+ coverageConfig,
7
+ }) => {
8
+ const structuredMetaMapForCoverage = {
9
+ cover: coverageConfig,
10
+ }
11
+
12
+ const matchingFileResultArray = await collectFiles({
13
+ signal: multipleExecutionsOperation.signal,
14
+ directoryUrl: projectDirectoryUrl,
15
+ structuredMetaMap: structuredMetaMapForCoverage,
16
+ predicate: ({ cover }) => cover,
17
+ })
18
+
19
+ return matchingFileResultArray.map(({ relativeUrl }) => relativeUrl)
20
+ }
@@ -1,11 +1,10 @@
1
1
  import { resolveUrl, urlToFileSystemPath, readFile } from "@jsenv/filesystem"
2
2
 
3
- import { Abortable } from "@jsenv/core/src/abort/main.js"
4
3
  import {
5
4
  babelPluginsFromBabelPluginMap,
6
5
  getMinimalBabelPluginMap,
7
6
  } from "@jsenv/core/src/internal/compiling/babel_plugins.js"
8
- import { babelPluginInstrument } from "./babel_plugin_instrument.js"
7
+ import { babelPluginInstrument } from "../coverage/babel_plugin_instrument.js"
9
8
  import { createEmptyCoverage } from "./createEmptyCoverage.js"
10
9
 
11
10
  export const relativeUrlToEmptyCoverage = async (
@@ -15,7 +14,7 @@ export const relativeUrlToEmptyCoverage = async (
15
14
  const { transformAsync } = await import("@babel/core")
16
15
 
17
16
  const fileUrl = resolveUrl(relativeUrl, projectDirectoryUrl)
18
- Abortable.throwIfAborted(multipleExecutionsOperation)
17
+ multipleExecutionsOperation.throwIfAborted()
19
18
  const source = await readFile(fileUrl)
20
19
 
21
20
  try {
@@ -25,7 +24,7 @@ export const relativeUrlToEmptyCoverage = async (
25
24
  "transform-instrument": [babelPluginInstrument, { projectDirectoryUrl }],
26
25
  }
27
26
 
28
- Abortable.throwIfAborted(multipleExecutionsOperation)
27
+ multipleExecutionsOperation.throwIfAborted()
29
28
  const { metadata } = await transformAsync(source, {
30
29
  filename: urlToFileSystemPath(fileUrl),
31
30
  filenameRelative: relativeUrl,
@@ -1,13 +1,17 @@
1
- import { readFileSync } from "fs"
1
+ import { readFileSync } from "node:fs"
2
2
  import { resolveUrl, urlToFileSystemPath } from "@jsenv/filesystem"
3
3
 
4
4
  import { require } from "../../require.js"
5
-
6
5
  import { istanbulCoverageMapFromCoverage } from "./istanbulCoverageMapFromCoverage.js"
7
6
 
8
7
  export const generateCoverageHtmlDirectory = async (
9
8
  coverage,
10
- { projectDirectoryUrl, coverageHtmlDirectoryRelativeUrl, coverageSkipEmpty, coverageSkipFull },
9
+ {
10
+ projectDirectoryUrl,
11
+ coverageHtmlDirectoryRelativeUrl,
12
+ coverageSkipEmpty,
13
+ coverageSkipFull,
14
+ },
11
15
  ) => {
12
16
  const libReport = require("istanbul-lib-report")
13
17
  const reports = require("istanbul-reports")
@@ -16,7 +20,10 @@ export const generateCoverageHtmlDirectory = async (
16
20
  dir: urlToFileSystemPath(projectDirectoryUrl),
17
21
  coverageMap: istanbulCoverageMapFromCoverage(coverage),
18
22
  sourceFinder: (path) => {
19
- return readFileSync(urlToFileSystemPath(resolveUrl(path, projectDirectoryUrl)), "utf8")
23
+ return readFileSync(
24
+ urlToFileSystemPath(resolveUrl(path, projectDirectoryUrl)),
25
+ "utf8",
26
+ )
20
27
  },
21
28
  })
22
29
 
@@ -1,8 +1,10 @@
1
1
  import { require } from "../../require.js"
2
-
3
2
  import { istanbulCoverageMapFromCoverage } from "./istanbulCoverageMapFromCoverage.js"
4
3
 
5
- export const generateCoverageTextLog = (coverage, { coverageSkipEmpty, coverageSkipFull }) => {
4
+ export const generateCoverageTextLog = (
5
+ coverage,
6
+ { coverageSkipEmpty, coverageSkipFull },
7
+ ) => {
6
8
  const libReport = require("istanbul-lib-report")
7
9
  const reports = require("istanbul-reports")
8
10
 
@@ -5,22 +5,24 @@ import {
5
5
  resolveUrl,
6
6
  } from "@jsenv/filesystem"
7
7
 
8
- export const normalizeIstanbulCoverage = (istanbulCoverage, projectDirectoryUrl) => {
9
- const istanbulCoverageNormalized = {}
8
+ export const normalizeFileByFileCoveragePaths = (
9
+ fileByFileCoverage,
10
+ projectDirectoryUrl,
11
+ ) => {
12
+ const fileByFileNormalized = {}
10
13
 
11
- Object.keys(istanbulCoverage).forEach((key) => {
12
- const fileCoverage = istanbulCoverage[key]
14
+ Object.keys(fileByFileCoverage).forEach((key) => {
15
+ const fileCoverage = fileByFileCoverage[key]
13
16
  const { path } = fileCoverage
14
17
  const url = isFileSystemPath(path)
15
18
  ? fileSystemPathToUrl(path)
16
19
  : resolveUrl(path, projectDirectoryUrl)
17
20
  const relativeUrl = urlToRelativeUrl(url, projectDirectoryUrl)
18
-
19
- istanbulCoverageNormalized[`./${relativeUrl}`] = {
21
+ fileByFileNormalized[`./${relativeUrl}`] = {
20
22
  ...fileCoverage,
21
23
  path: `./${relativeUrl}`,
22
24
  }
23
25
  })
24
26
 
25
- return istanbulCoverageNormalized
27
+ return fileByFileNormalized
26
28
  }
@@ -0,0 +1,28 @@
1
+ import { require } from "../../require.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 } = require("istanbul-lib-coverage")
24
+ const istanbulFileCoverageObject = createFileCoverage(firstIstanbulCoverage)
25
+ istanbulFileCoverageObject.merge(secondIstanbulCoverage)
26
+ const istanbulCoverage = istanbulFileCoverageObject.toJSON()
27
+ return istanbulCoverage
28
+ }
@@ -0,0 +1,38 @@
1
+ import { createDetailedMessage } from "@jsenv/logger"
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
+ "suggestion 2": `You can force usage of istanbul to prevent this conflict with coverageForceIstanbul: true`,
27
+ },
28
+ ),
29
+ )
30
+ }
31
+ fileByFileCoverage[key] = v8Coverage
32
+ } else {
33
+ fileByFileCoverage[key] = istanbulFileByFileCoverage[key]
34
+ }
35
+ })
36
+
37
+ return fileByFileCoverage
38
+ }
@@ -0,0 +1,23 @@
1
+ import { require } from "@jsenv/core/src/internal/require.js"
2
+
3
+ export const composeTwoV8Coverages = (firstV8Coverage, secondV8Coverage) => {
4
+ if (secondV8Coverage.result.length === 0) {
5
+ return firstV8Coverage
6
+ }
7
+
8
+ const { mergeProcessCovs } = require("@c88/v8-coverage")
9
+ // "mergeProcessCovs" do not preserves source-map-cache during the merge
10
+ // so we store sourcemap cache now
11
+ const sourceMapCache = {}
12
+ const visit = (coverageReport) => {
13
+ if (coverageReport["source-map-cache"]) {
14
+ Object.assign(sourceMapCache, coverageReport["source-map-cache"])
15
+ }
16
+ }
17
+ visit(firstV8Coverage)
18
+ visit(secondV8Coverage)
19
+ const v8Coverage = mergeProcessCovs([firstV8Coverage, secondV8Coverage])
20
+ v8Coverage["source-map-cache"] = sourceMapCache
21
+
22
+ return v8Coverage
23
+ }
@@ -0,0 +1,65 @@
1
+ import {
2
+ assertAndNormalizeDirectoryUrl,
3
+ readDirectory,
4
+ readFile,
5
+ resolveUrl,
6
+ } from "@jsenv/filesystem"
7
+ import { createDetailedMessage } from "@jsenv/logger"
8
+
9
+ export const visitNodeV8Directory = async ({
10
+ // signal
11
+ NODE_V8_COVERAGE,
12
+ onV8Coverage,
13
+ }) => {
14
+ const tryReadDirectory = async () => {
15
+ const dirContent = await readDirectory(NODE_V8_COVERAGE)
16
+ if (dirContent.length > 0) {
17
+ return dirContent
18
+ }
19
+ console.warn(`v8 coverage directory is empty at ${NODE_V8_COVERAGE}`)
20
+ return dirContent
21
+ }
22
+ const dirContent = await tryReadDirectory()
23
+
24
+ const coverageDirectoryUrl = assertAndNormalizeDirectoryUrl(NODE_V8_COVERAGE)
25
+ await dirContent.reduce(async (previous, dirEntry) => {
26
+ await previous
27
+
28
+ const dirEntryUrl = resolveUrl(dirEntry, coverageDirectoryUrl)
29
+ const tryReadJsonFile = async () => {
30
+ const fileContent = await readFile(dirEntryUrl, { as: "string" })
31
+ if (fileContent === "") {
32
+ console.warn(`Coverage JSON file is empty at ${dirEntryUrl}`)
33
+ return null
34
+ }
35
+
36
+ try {
37
+ const fileAsJson = JSON.parse(fileContent)
38
+ return fileAsJson
39
+ } catch (e) {
40
+ console.warn(
41
+ createDetailedMessage(`Error while reading coverage file`, {
42
+ "error stack": e.stack,
43
+ "file": dirEntryUrl,
44
+ }),
45
+ )
46
+ return null
47
+ }
48
+ }
49
+
50
+ const fileContent = await tryReadJsonFile()
51
+ if (fileContent) {
52
+ onV8Coverage(fileContent)
53
+ }
54
+ }, Promise.resolve())
55
+ }
56
+
57
+ export const filterV8Coverage = (v8Coverage, { coverageIgnorePredicate }) => {
58
+ const v8CoverageFiltered = {
59
+ ...v8Coverage,
60
+ result: v8Coverage.result.filter((fileReport) => {
61
+ return !coverageIgnorePredicate(fileReport.url)
62
+ }),
63
+ }
64
+ return v8CoverageFiltered
65
+ }