@jsenv/core 23.3.0 → 23.5.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 (36) hide show
  1. package/package.json +5 -5
  2. package/readme.md +4 -4
  3. package/src/execute.js +0 -6
  4. package/src/executeTestPlan.js +12 -10
  5. package/src/internal/browser-launcher/executeHtmlFile.js +20 -16
  6. package/src/internal/compiling/createCompiledFileService.js +13 -1
  7. package/src/internal/executing/coverage/babel_plugin_instrument.js +1 -0
  8. package/src/internal/executing/coverage/reportToCoverage.js +160 -119
  9. package/src/internal/executing/{coverage → coverage_missing}/createEmptyCoverage.js +0 -0
  10. package/src/internal/executing/coverage_missing/list_files_not_covered.js +20 -0
  11. package/src/internal/executing/coverage_missing/missing_coverage.js +46 -0
  12. package/src/internal/executing/{coverage → coverage_missing}/relativeUrlToEmptyCoverage.js +12 -8
  13. package/src/internal/executing/{coverage/generateCoverageHtmlDirectory.js → coverage_reporter/coverage_reporter_html_directory.js} +11 -4
  14. package/src/internal/executing/{coverage/generateCoverageJsonFile.js → coverage_reporter/coverage_reporter_json_file.js} +0 -0
  15. package/src/internal/executing/{coverage/generateCoverageTextLog.js → coverage_reporter/coverage_reporter_text_log.js} +4 -2
  16. package/src/internal/executing/{coverage → coverage_reporter}/istanbulCoverageMapFromCoverage.js +0 -0
  17. package/src/internal/executing/{coverage/normalizeIstanbulCoverage.js → coverage_utils/file_by_file_coverage.js} +9 -7
  18. package/src/internal/executing/coverage_utils/istanbul_coverage_composition.js +28 -0
  19. package/src/internal/executing/coverage_utils/v8_and_istanbul.js +38 -0
  20. package/src/internal/executing/coverage_utils/v8_coverage_composition.js +23 -0
  21. package/src/internal/executing/coverage_utils/v8_coverage_from_directory.js +77 -0
  22. package/src/internal/executing/{coverage/istanbulCoverageFromV8Coverage.js → coverage_utils/v8_coverage_to_istanbul.js} +38 -17
  23. package/src/internal/executing/createSummaryLog.js +5 -9
  24. package/src/internal/executing/executeConcurrently.js +199 -115
  25. package/src/internal/executing/executePlan.js +8 -5
  26. package/src/internal/executing/executionLogs.js +66 -37
  27. package/src/internal/executing/execution_colors.js +2 -1
  28. package/src/internal/executing/launchAndExecute.js +63 -58
  29. package/src/internal/logs/msAsDuration.js +4 -3
  30. package/src/launchBrowser.js +8 -8
  31. package/src/launchNode.js +5 -93
  32. package/src/internal/executing/coverage/composeIstanbulCoverages.js +0 -108
  33. package/src/internal/executing/coverage/composeV8Coverages.js +0 -20
  34. package/src/internal/executing/coverage/istanbulCoverageFromCoverages.js +0 -43
  35. package/src/internal/executing/coverage/v8CoverageFromAllV8Coverages.js +0 -40
  36. package/src/internal/executing/coverage/v8CoverageFromNodeV8Directory.js +0 -67
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "23.3.0",
3
+ "version": "23.5.0",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -63,12 +63,12 @@
63
63
  "@babel/plugin-syntax-numeric-separator": "7.10.4",
64
64
  "@babel/plugin-transform-modules-systemjs": "7.15.4",
65
65
  "@c88/v8-coverage": "0.1.1",
66
- "@jsenv/abort": "4.0.0",
67
- "@jsenv/filesystem": "2.5.0",
66
+ "@jsenv/abort": "4.1.1",
67
+ "@jsenv/filesystem": "2.5.1",
68
68
  "@jsenv/importmap": "1.1.0",
69
- "@jsenv/log": "1.1.0",
69
+ "@jsenv/log": "1.4.0",
70
70
  "@jsenv/logger": "4.0.1",
71
- "@jsenv/server": "10.0.0",
71
+ "@jsenv/server": "10.0.1",
72
72
  "@jsenv/uneval": "1.6.0",
73
73
  "@jsenv/workers": "1.2.0",
74
74
  "@rollup/plugin-commonjs": "21.0.0",
package/readme.md CHANGED
@@ -99,16 +99,16 @@ executeTestPlan({
99
99
  ✔ execution 1 of 2 completed (all completed)
100
100
  file: animals.test.html
101
101
  runtime: chromium/82.0.4057.0
102
- duration: 1.28 seconds
102
+ duration: 1.2 seconds
103
103
 
104
104
  ✔ execution 2 of 2 completed (all completed)
105
105
  file: animals.test.html
106
106
  runtime: firefox/73.0b13
107
- duration: 2.42 seconds
107
+ duration: 2.4 seconds
108
108
 
109
109
  -------------- summary -----------------
110
- 2 execution: all completed
111
- total duration: 3.72 seconds
110
+ 2 executions: all completed
111
+ total duration: 3.6 seconds
112
112
  ----------------------------------------
113
113
  ```
114
114
 
package/src/execute.js CHANGED
@@ -27,11 +27,8 @@ export const execute = async ({
27
27
  runtimeParams,
28
28
 
29
29
  allocatedMs,
30
- measureDuration,
31
30
  mirrorConsole = true,
32
31
  captureConsole,
33
- collectRuntimeName,
34
- collectRuntimeVersion,
35
32
  inheritCoverage,
36
33
  collectCoverage,
37
34
  measurePerformance,
@@ -139,11 +136,8 @@ export const execute = async ({
139
136
  },
140
137
 
141
138
  allocatedMs,
142
- measureDuration,
143
139
  mirrorConsole,
144
140
  captureConsole,
145
- collectRuntimeName,
146
- collectRuntimeVersion,
147
141
  inheritCoverage,
148
142
  collectCoverage,
149
143
  measurePerformance,
@@ -14,9 +14,9 @@ import {
14
14
  } from "./internal/argUtils.js"
15
15
  import { executePlan } from "./internal/executing/executePlan.js"
16
16
  import { executionIsPassed } from "./internal/executing/executionIsPassed.js"
17
- import { generateCoverageJsonFile } from "./internal/executing/coverage/generateCoverageJsonFile.js"
18
- import { generateCoverageHtmlDirectory } from "./internal/executing/coverage/generateCoverageHtmlDirectory.js"
19
- import { generateCoverageTextLog } from "./internal/executing/coverage/generateCoverageTextLog.js"
17
+ import { generateCoverageJsonFile } from "./internal/executing/coverage_reporter/coverage_reporter_json_file.js"
18
+ import { generateCoverageHtmlDirectory } from "./internal/executing/coverage_reporter/coverage_reporter_html_directory.js"
19
+ import { generateCoverageTextLog } from "./internal/executing/coverage_reporter/coverage_reporter_text_log.js"
20
20
  import { jsenvCoverageConfig } from "./jsenvCoverageConfig.js"
21
21
 
22
22
  export const executeTestPlan = async ({
@@ -33,29 +33,29 @@ export const executeTestPlan = async ({
33
33
 
34
34
  testPlan,
35
35
  defaultMsAllocatedPerExecution,
36
+ cooldownBetweenExecutions,
36
37
 
37
38
  maxExecutionsInParallel,
38
39
 
39
40
  completedExecutionLogAbbreviation = false,
40
41
  completedExecutionLogMerging = false,
41
42
  logSummary = true,
42
- measureGlobalDuration = true,
43
43
  updateProcessExitCode = true,
44
44
 
45
45
  coverage = process.argv.includes("--cover") ||
46
46
  process.argv.includes("--coverage"),
47
+ coverageTempDirectoryRelativeUrl = "./coverage/tmp/",
47
48
  coverageConfig = jsenvCoverageConfig,
48
49
  coverageIncludeMissing = true,
49
50
  coverageAndExecutionAllowed = false,
50
51
  coverageForceIstanbul = false,
51
- coverageV8MergeConflictIsExpected = false,
52
-
52
+ coverageV8ConflictWarning = true,
53
53
  coverageTextLog = true,
54
54
  coverageJsonFile = Boolean(process.env.CI),
55
55
  coverageJsonFileLog = true,
56
56
  coverageJsonFileRelativeUrl = "./coverage/coverage.json",
57
57
  coverageHtmlDirectory = !process.env.CI,
58
- coverageHtmlDirectoryRelativeUrl = "./coverage",
58
+ coverageHtmlDirectoryRelativeUrl = "./coverage/",
59
59
  coverageHtmlDirectoryIndexLog = true,
60
60
  // skip empty means empty files won't appear in the coverage reports (log and html)
61
61
  coverageSkipEmpty = false,
@@ -144,16 +144,17 @@ export const executeTestPlan = async ({
144
144
 
145
145
  defaultMsAllocatedPerExecution,
146
146
  maxExecutionsInParallel,
147
+ cooldownBetweenExecutions,
147
148
  completedExecutionLogMerging,
148
149
  completedExecutionLogAbbreviation,
149
150
  logSummary,
150
- measureGlobalDuration,
151
151
 
152
152
  coverage,
153
153
  coverageConfig,
154
154
  coverageIncludeMissing,
155
155
  coverageForceIstanbul,
156
- coverageV8MergeConflictIsExpected,
156
+ coverageV8ConflictWarning,
157
+ coverageTempDirectoryRelativeUrl,
157
158
 
158
159
  jsenvDirectoryClean,
159
160
  compileServerProtocol,
@@ -173,7 +174,7 @@ export const executeTestPlan = async ({
173
174
  }
174
175
 
175
176
  const planCoverage = result.planCoverage
176
- // planCoverage can be null when execution is abortes
177
+ // planCoverage can be null when execution is aborted
177
178
  if (planCoverage) {
178
179
  const promises = []
179
180
  // keep this one first because it does ensureEmptyDirectory
@@ -224,6 +225,7 @@ export const executeTestPlan = async ({
224
225
  }
225
226
 
226
227
  return {
228
+ testPlanAborted: result.aborted,
227
229
  testPlanSummary: result.planSummary,
228
230
  testPlanReport: result.planReport,
229
231
  testPlanCoverage: planCoverage,
@@ -6,8 +6,8 @@ import {
6
6
  } from "@jsenv/filesystem"
7
7
 
8
8
  import { jsenvCompileProxyHtmlFileInfo } from "@jsenv/core/src/internal/jsenvInternalFiles.js"
9
- import { v8CoverageFromAllV8Coverages } from "@jsenv/core/src/internal/executing/coverage/v8CoverageFromAllV8Coverages.js"
10
- import { composeIstanbulCoverages } from "@jsenv/core/src/internal/executing/coverage/composeIstanbulCoverages.js"
9
+ import { filterV8Coverage } from "@jsenv/core/src/internal/executing/coverage_utils/v8_coverage_from_directory.js"
10
+ import { composeTwoFileByFileIstanbulCoverages } from "@jsenv/core/src/internal/executing/coverage_utils/istanbul_coverage_composition.js"
11
11
  import { evalSource } from "../runtime/createNodeRuntime/evalSource.js"
12
12
  import { escapeRegexpSpecialCharacters } from "../escapeRegexpSpecialCharacters.js"
13
13
 
@@ -23,7 +23,7 @@ export const executeHtmlFile = async (
23
23
  // measurePerformance,
24
24
  collectPerformance,
25
25
  collectCoverage,
26
- coverageConfig,
26
+ coverageIgnorePredicate,
27
27
  coverageForceIstanbul,
28
28
  coveragePlaywrightAPIAvailable,
29
29
  transformErrorHook,
@@ -76,7 +76,7 @@ export const executeHtmlFile = async (
76
76
  fileRelativeUrl,
77
77
  page,
78
78
  collectCoverage,
79
- coverageConfig,
79
+ coverageIgnorePredicate,
80
80
  transformErrorHook,
81
81
  })
82
82
  } else {
@@ -149,7 +149,7 @@ const executeSource = async ({
149
149
  fileRelativeUrl,
150
150
  page,
151
151
  collectCoverage,
152
- coverageConfig,
152
+ coverageIgnorePredicate,
153
153
  transformErrorHook,
154
154
  }) => {
155
155
  let transformResult = (result) => result
@@ -175,11 +175,12 @@ const executeSource = async ({
175
175
  }
176
176
  },
177
177
  )
178
- const allV8Coverages = [{ result: v8CoveragesWithFsUrls }]
179
- const coverage = v8CoverageFromAllV8Coverages(allV8Coverages, {
180
- coverageRootUrl: projectDirectoryUrl,
181
- coverageConfig,
182
- })
178
+ const coverage = filterV8Coverage(
179
+ { result: v8CoveragesWithFsUrls },
180
+ {
181
+ coverageIgnorePredicate,
182
+ },
183
+ )
183
184
  return {
184
185
  ...result,
185
186
  coverage,
@@ -297,15 +298,18 @@ const executeCompiledVersion = async ({
297
298
  }
298
299
 
299
300
  const generateCoverageForPage = (fileExecutionResultMap) => {
300
- const istanbulCoverages = []
301
+ let istanbulCoverageComposed = null
301
302
  Object.keys(fileExecutionResultMap).forEach((fileRelativeUrl) => {
302
303
  const istanbulCoverage = fileExecutionResultMap[fileRelativeUrl].coverage
303
- if (istanbulCoverage) {
304
- istanbulCoverages.push(istanbulCoverage)
305
- }
304
+ istanbulCoverageComposed = istanbulCoverageComposed
305
+ ? composeTwoFileByFileIstanbulCoverages(
306
+ istanbulCoverageComposed,
307
+ istanbulCoverage,
308
+ )
309
+ : istanbulCoverage
306
310
  })
307
- const istanbulCoverage = composeIstanbulCoverages(istanbulCoverages)
308
- return istanbulCoverage
311
+
312
+ return istanbulCoverageComposed
309
313
  }
310
314
 
311
315
  const evalException = (
@@ -71,7 +71,10 @@ export const createCompiledFileService = ({
71
71
 
72
72
  return (request) => {
73
73
  const { origin, ressource } = request
74
- const requestUrl = `${origin}${ressource}`
74
+ // we use "ressourceToPathname" to remove eventual query param from the url
75
+ // Without this a pattern like "**/*.js" would not match "file.js?t=1"
76
+ // This would result in file not being compiled when they should
77
+ const requestUrl = `${origin}${ressourceToPathname(ressource)}`
75
78
 
76
79
  const requestCompileInfo = serverUrlToCompileInfo(requestUrl, {
77
80
  outDirectoryRelativeUrl,
@@ -276,3 +279,12 @@ const babelPluginMapFromCompileId = (
276
279
 
277
280
  return babelPluginMapForGroup
278
281
  }
282
+
283
+ const ressourceToPathname = (ressource) => {
284
+ const searchSeparatorIndex = ressource.indexOf("?")
285
+ const pathname =
286
+ searchSeparatorIndex === -1
287
+ ? ressource
288
+ : ressource.slice(0, searchSeparatorIndex)
289
+ return pathname
290
+ }
@@ -3,6 +3,7 @@ import {
3
3
  normalizeStructuredMetaMap,
4
4
  urlToMeta,
5
5
  } from "@jsenv/filesystem"
6
+
6
7
  import { require } from "../../require.js"
7
8
 
8
9
  // https://github.com/istanbuljs/babel-plugin-istanbul/blob/321740f7b25d803f881466ea819d870f7ed6a254/src/index.js
@@ -1,146 +1,187 @@
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
+ import { Abort } from "@jsenv/abort"
3
+
4
+ import {
5
+ visitNodeV8Directory,
6
+ filterV8Coverage,
7
+ } from "../coverage_utils/v8_coverage_from_directory.js"
8
+ import { composeTwoV8Coverages } from "../coverage_utils/v8_coverage_composition.js"
9
+ import { composeTwoFileByFileIstanbulCoverages } from "../coverage_utils/istanbul_coverage_composition.js"
10
+ import { v8CoverageToIstanbul } from "../coverage_utils/v8_coverage_to_istanbul.js"
11
+ import { composeV8AndIstanbul } from "../coverage_utils/v8_and_istanbul.js"
12
+ import { normalizeFileByFileCoveragePaths } from "../coverage_utils/file_by_file_coverage.js"
13
+ import { getMissingFileByFileCoverage } from "../coverage_missing/missing_coverage.js"
6
14
 
7
15
  export const reportToCoverage = async (
8
16
  report,
9
17
  {
10
- multipleExecutionsOperation,
18
+ signal,
11
19
  logger,
12
20
  projectDirectoryUrl,
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(
29
+ // collect v8 and istanbul coverage from executions
30
+ let { v8Coverage, fileByFileIstanbulCoverage } = await getCoverageFromReport({
31
+ signal,
22
32
  report,
23
- {
24
- logger,
25
- projectDirectoryUrl,
26
- coverageV8MergeConflictIsExpected,
33
+ onMissing: ({ file, executionResult, executionName }) => {
34
+ // several reasons not to have coverage here:
35
+ // 1. the file we executed did not import an instrumented file.
36
+ // - a test file without import
37
+ // - a test file importing only file excluded from coverage
38
+ // - a coverDescription badly configured so that we don't realize
39
+ // a file should be covered
40
+
41
+ // 2. the file we wanted to executed timedout
42
+ // - infinite loop
43
+ // - too extensive operation
44
+ // - a badly configured or too low allocatedMs for that execution.
45
+
46
+ // 3. the file we wanted to execute contains syntax-error
47
+
48
+ // in any scenario we are fine because
49
+ // coverDescription will generate empty coverage for files
50
+ // that were suppose to be coverage but were not.
51
+ if (executionResult.status === "completed") {
52
+ logger.debug(
53
+ `No execution.coverageFileUrl from execution named "${executionName}" of ${file}`,
54
+ )
55
+ }
27
56
  },
28
- )
57
+ })
29
58
 
30
- if (!coverageIncludeMissing) {
31
- return istanbulCoverageFromExecution
59
+ if (!coverageForceIstanbul && process.env.NODE_V8_COVERAGE) {
60
+ await visitNodeV8Directory({
61
+ signal,
62
+ NODE_V8_COVERAGE: process.env.NODE_V8_COVERAGE,
63
+ onV8Coverage: (nodeV8Coverage) => {
64
+ const nodeV8CoverageLight = filterV8Coverage(nodeV8Coverage, {
65
+ coverageIgnorePredicate,
66
+ })
67
+ v8Coverage = v8Coverage
68
+ ? composeTwoV8Coverages(v8Coverage, nodeV8CoverageLight)
69
+ : nodeV8CoverageLight
70
+ },
71
+ })
32
72
  }
33
73
 
34
- const relativeFileUrlToCoverArray = await listRelativeFileUrlToCover({
35
- multipleExecutionsOperation,
36
- projectDirectoryUrl,
37
- coverageConfig,
38
- })
74
+ // try to merge v8 with istanbul, if any
75
+ let fileByFileCoverage
76
+ if (v8Coverage) {
77
+ let v8FileByFileCoverage = await v8CoverageToIstanbul(v8Coverage, {
78
+ signal,
79
+ })
39
80
 
40
- const relativeFileUrlMissingCoverageArray =
41
- relativeFileUrlToCoverArray.filter((relativeFileUrlToCover) =>
42
- Object.keys(istanbulCoverageFromExecution).every((key) => {
43
- return key !== `./${relativeFileUrlToCover}`
44
- }),
81
+ v8FileByFileCoverage = normalizeFileByFileCoveragePaths(
82
+ v8FileByFileCoverage,
83
+ projectDirectoryUrl,
45
84
  )
46
85
 
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
64
- },
65
- ),
66
- )
67
-
68
- return {
69
- ...istanbulCoverageFromExecution, // already normalized
70
- ...normalizeIstanbulCoverage(
71
- istanbulCoverageFromMissedFiles,
86
+ if (fileByFileIstanbulCoverage) {
87
+ fileByFileIstanbulCoverage = normalizeFileByFileCoveragePaths(
88
+ fileByFileIstanbulCoverage,
89
+ projectDirectoryUrl,
90
+ )
91
+ fileByFileCoverage = composeV8AndIstanbul(
92
+ v8FileByFileCoverage,
93
+ fileByFileIstanbulCoverage,
94
+ { coverageV8ConflictWarning },
95
+ )
96
+ } else {
97
+ fileByFileCoverage = v8FileByFileCoverage
98
+ }
99
+ }
100
+ // get istanbul only
101
+ else if (fileByFileIstanbulCoverage) {
102
+ fileByFileCoverage = normalizeFileByFileCoveragePaths(
103
+ fileByFileIstanbulCoverage,
72
104
  projectDirectoryUrl,
73
- ),
105
+ )
74
106
  }
75
- }
76
-
77
- const listRelativeFileUrlToCover = async ({
78
- multipleExecutionsOperation,
79
- projectDirectoryUrl,
80
- coverageConfig,
81
- }) => {
82
- const structuredMetaMapForCoverage = {
83
- cover: coverageConfig,
107
+ // no coverage found in execution (or zero file where executed)
108
+ else {
109
+ fileByFileCoverage = {}
84
110
  }
85
111
 
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
- }
95
-
96
- const executionReportToCoverage = async (
97
- report,
98
- { logger, projectDirectoryUrl, coverageV8MergeConflictIsExpected },
99
- ) => {
100
- const coverages = []
101
-
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
- }
135
-
136
- coverages.push(coverage)
112
+ // now add coverage for file not covered
113
+ if (coverageIncludeMissing) {
114
+ const missingFileByFileCoverage = await getMissingFileByFileCoverage({
115
+ signal,
116
+ projectDirectoryUrl,
117
+ coverageConfig,
118
+ fileByFileCoverage,
119
+ babelPluginMap,
137
120
  })
138
- })
121
+ Object.assign(fileByFileCoverage, missingFileByFileCoverage)
122
+ }
139
123
 
140
- const istanbulCoverage = await istanbulCoverageFromCoverages(coverages, {
141
- projectDirectoryUrl,
142
- coverageV8MergeConflictIsExpected,
143
- })
124
+ return fileByFileCoverage
125
+ }
144
126
 
145
- return istanbulCoverage
127
+ const getCoverageFromReport = async ({ signal, report, onMissing }) => {
128
+ const operation = Abort.startOperation()
129
+ operation.addAbortSignal(signal)
130
+
131
+ try {
132
+ let v8Coverage
133
+ let fileByFileIstanbulCoverage
134
+
135
+ // collect v8 and istanbul coverage from executions
136
+ await Object.keys(report).reduce(async (previous, file) => {
137
+ operation.throwIfAborted()
138
+ await previous
139
+
140
+ const executionResultForFile = report[file]
141
+ await Object.keys(executionResultForFile).reduce(
142
+ async (previous, executionName) => {
143
+ operation.throwIfAborted()
144
+ await previous
145
+
146
+ const executionResultForFileOnRuntime =
147
+ executionResultForFile[executionName]
148
+ const { coverageFileUrl } = executionResultForFileOnRuntime
149
+ if (!coverageFileUrl) {
150
+ onMissing({
151
+ executionName,
152
+ file,
153
+ executionResult: executionResultForFileOnRuntime,
154
+ })
155
+ return
156
+ }
157
+
158
+ const executionCoverage = await readFile(coverageFileUrl, {
159
+ as: "json",
160
+ })
161
+ if (isV8Coverage(executionCoverage)) {
162
+ v8Coverage = v8Coverage
163
+ ? composeTwoV8Coverages(v8Coverage, executionCoverage)
164
+ : executionCoverage
165
+ } else {
166
+ fileByFileIstanbulCoverage = fileByFileIstanbulCoverage
167
+ ? composeTwoFileByFileIstanbulCoverages(
168
+ fileByFileIstanbulCoverage,
169
+ executionCoverage,
170
+ )
171
+ : executionCoverage
172
+ }
173
+ },
174
+ Promise.resolve(),
175
+ )
176
+ }, Promise.resolve())
177
+
178
+ return {
179
+ v8Coverage,
180
+ fileByFileIstanbulCoverage,
181
+ }
182
+ } finally {
183
+ await operation.end()
184
+ }
146
185
  }
186
+
187
+ const isV8Coverage = (coverage) => Boolean(coverage.result)
@@ -0,0 +1,20 @@
1
+ import { collectFiles } from "@jsenv/filesystem"
2
+
3
+ export const listRelativeFileUrlToCover = async ({
4
+ signal,
5
+ projectDirectoryUrl,
6
+ coverageConfig,
7
+ }) => {
8
+ const structuredMetaMapForCoverage = {
9
+ cover: coverageConfig,
10
+ }
11
+
12
+ const matchingFileResultArray = await collectFiles({
13
+ signal,
14
+ directoryUrl: projectDirectoryUrl,
15
+ structuredMetaMap: structuredMetaMapForCoverage,
16
+ predicate: ({ cover }) => cover,
17
+ })
18
+
19
+ return matchingFileResultArray.map(({ relativeUrl }) => relativeUrl)
20
+ }
@@ -0,0 +1,46 @@
1
+ import { Abort } from "@jsenv/abort"
2
+
3
+ import { listRelativeFileUrlToCover } from "./list_files_not_covered.js"
4
+ import { relativeUrlToEmptyCoverage } from "./relativeUrlToEmptyCoverage.js"
5
+
6
+ export const getMissingFileByFileCoverage = async ({
7
+ signal,
8
+ projectDirectoryUrl,
9
+ coverageConfig,
10
+ fileByFileCoverage,
11
+ babelPluginMap,
12
+ }) => {
13
+ const relativeUrlsToCover = await listRelativeFileUrlToCover({
14
+ signal,
15
+ projectDirectoryUrl,
16
+ coverageConfig,
17
+ })
18
+
19
+ const relativeUrlsMissing = relativeUrlsToCover.filter((relativeUrlToCover) =>
20
+ Object.keys(fileByFileCoverage).every((key) => {
21
+ return key !== `./${relativeUrlToCover}`
22
+ }),
23
+ )
24
+
25
+ const operation = Abort.startOperation()
26
+ operation.addAbortSignal(signal)
27
+
28
+ const missingFileByFileCoverage = {}
29
+ await relativeUrlsMissing.reduce(async (previous, relativeUrlMissing) => {
30
+ operation.throwIfAborted()
31
+ await previous
32
+ await operation.withSignal(async (signal) => {
33
+ const emptyCoverage = await relativeUrlToEmptyCoverage(
34
+ relativeUrlMissing,
35
+ {
36
+ signal,
37
+ projectDirectoryUrl,
38
+ babelPluginMap,
39
+ },
40
+ )
41
+ missingFileByFileCoverage[`./${relativeUrlMissing}`] = emptyCoverage
42
+ })
43
+ }, Promise.resolve())
44
+
45
+ return missingFileByFileCoverage
46
+ }