@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
@@ -1,30 +1,32 @@
1
1
  import { resolveUrl, urlToFileSystemPath, readFile } from "@jsenv/filesystem"
2
+ import { Abort } from "@jsenv/abort"
2
3
 
3
4
  import {
4
5
  babelPluginsFromBabelPluginMap,
5
6
  getMinimalBabelPluginMap,
6
7
  } from "@jsenv/core/src/internal/compiling/babel_plugins.js"
7
- import { babelPluginInstrument } from "./babel_plugin_instrument.js"
8
+ import { babelPluginInstrument } from "../coverage/babel_plugin_instrument.js"
8
9
  import { createEmptyCoverage } from "./createEmptyCoverage.js"
9
10
 
10
11
  export const relativeUrlToEmptyCoverage = async (
11
12
  relativeUrl,
12
- { multipleExecutionsOperation, projectDirectoryUrl, babelPluginMap },
13
+ { signal, projectDirectoryUrl, babelPluginMap },
13
14
  ) => {
14
- const { transformAsync } = await import("@babel/core")
15
-
16
- const fileUrl = resolveUrl(relativeUrl, projectDirectoryUrl)
17
- multipleExecutionsOperation.throwIfAborted()
18
- const source = await readFile(fileUrl)
15
+ const operation = Abort.startOperation()
16
+ operation.addAbortSignal(signal)
19
17
 
20
18
  try {
19
+ const { transformAsync } = await import("@babel/core")
20
+ const fileUrl = resolveUrl(relativeUrl, projectDirectoryUrl)
21
+ const source = await readFile(fileUrl)
22
+
21
23
  babelPluginMap = {
22
24
  ...getMinimalBabelPluginMap(),
23
25
  ...babelPluginMap,
24
26
  "transform-instrument": [babelPluginInstrument, { projectDirectoryUrl }],
25
27
  }
26
28
 
27
- multipleExecutionsOperation.throwIfAborted()
29
+ operation.throwIfAborted()
28
30
  const { metadata } = await transformAsync(source, {
29
31
  filename: urlToFileSystemPath(fileUrl),
30
32
  filenameRelative: relativeUrl,
@@ -55,5 +57,7 @@ export const relativeUrlToEmptyCoverage = async (
55
57
  return createEmptyCoverage(relativeUrl)
56
58
  }
57
59
  throw e
60
+ } finally {
61
+ await operation.end()
58
62
  }
59
63
  }
@@ -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,77 @@
1
+ import {
2
+ assertAndNormalizeDirectoryUrl,
3
+ readDirectory,
4
+ readFile,
5
+ resolveUrl,
6
+ } from "@jsenv/filesystem"
7
+ import { createDetailedMessage } from "@jsenv/logger"
8
+ import { Abort } from "@jsenv/abort"
9
+
10
+ export const visitNodeV8Directory = async ({
11
+ signal,
12
+ NODE_V8_COVERAGE,
13
+ onV8Coverage,
14
+ }) => {
15
+ const operation = Abort.startOperation()
16
+ operation.addAbortSignal(signal)
17
+
18
+ const tryReadDirectory = async () => {
19
+ const dirContent = await readDirectory(NODE_V8_COVERAGE)
20
+ if (dirContent.length > 0) {
21
+ return dirContent
22
+ }
23
+ console.warn(`v8 coverage directory is empty at ${NODE_V8_COVERAGE}`)
24
+ return dirContent
25
+ }
26
+
27
+ try {
28
+ operation.throwIfAborted()
29
+ const dirContent = await tryReadDirectory()
30
+
31
+ const coverageDirectoryUrl =
32
+ assertAndNormalizeDirectoryUrl(NODE_V8_COVERAGE)
33
+ await dirContent.reduce(async (previous, dirEntry) => {
34
+ operation.throwIfAborted()
35
+ await previous
36
+
37
+ const dirEntryUrl = resolveUrl(dirEntry, coverageDirectoryUrl)
38
+ const tryReadJsonFile = async () => {
39
+ const fileContent = await readFile(dirEntryUrl, { as: "string" })
40
+ if (fileContent === "") {
41
+ console.warn(`Coverage JSON file is empty at ${dirEntryUrl}`)
42
+ return null
43
+ }
44
+
45
+ try {
46
+ const fileAsJson = JSON.parse(fileContent)
47
+ return fileAsJson
48
+ } catch (e) {
49
+ console.warn(
50
+ createDetailedMessage(`Error while reading coverage file`, {
51
+ "error stack": e.stack,
52
+ "file": dirEntryUrl,
53
+ }),
54
+ )
55
+ return null
56
+ }
57
+ }
58
+
59
+ const fileContent = await tryReadJsonFile()
60
+ if (fileContent) {
61
+ onV8Coverage(fileContent)
62
+ }
63
+ }, Promise.resolve())
64
+ } finally {
65
+ await operation.end()
66
+ }
67
+ }
68
+
69
+ export const filterV8Coverage = (v8Coverage, { coverageIgnorePredicate }) => {
70
+ const v8CoverageFiltered = {
71
+ ...v8Coverage,
72
+ result: v8Coverage.result.filter((fileReport) => {
73
+ return !coverageIgnorePredicate(fileReport.url)
74
+ }),
75
+ }
76
+ return v8CoverageFiltered
77
+ }
@@ -1,14 +1,23 @@
1
1
  import { urlToFileSystemPath } from "@jsenv/filesystem"
2
+ import { Abort } from "@jsenv/abort"
2
3
 
3
4
  import { require } from "@jsenv/core/src/internal/require.js"
4
5
 
5
- import { composeIstanbulCoverages } from "./composeIstanbulCoverages.js"
6
+ import { composeTwoFileByFileIstanbulCoverages } from "./istanbul_coverage_composition.js"
7
+
8
+ export const v8CoverageToIstanbul = async (v8Coverage, { signal }) => {
9
+ const operation = Abort.startOperation()
10
+ operation.addAbortSignal(signal)
11
+
12
+ try {
13
+ const v8ToIstanbul = require("v8-to-istanbul")
14
+ const sourcemapCache = v8Coverage["source-map-cache"]
15
+ let istanbulCoverageComposed = null
16
+
17
+ await v8Coverage.result.reduce(async (previous, fileV8Coverage) => {
18
+ operation.throwIfAborted()
19
+ await previous
6
20
 
7
- export const istanbulCoverageFromV8Coverage = async (v8Coverage) => {
8
- const v8ToIstanbul = require("v8-to-istanbul")
9
- const sourcemapCache = v8Coverage["source-map-cache"]
10
- const istanbulCoverages = await Promise.all(
11
- v8Coverage.result.map(async (fileV8Coverage) => {
12
21
  const { source } = fileV8Coverage
13
22
  let sources
14
23
  // when v8 coverage comes from playwright (chromium) v8Coverage.source is set
@@ -32,23 +41,35 @@ export const istanbulCoverageFromV8Coverage = async (v8Coverage) => {
32
41
 
33
42
  converter.applyCoverage(fileV8Coverage.functions)
34
43
  const istanbulCoverage = converter.toIstanbul()
35
- return istanbulCoverage
36
- }),
37
- )
38
44
 
39
- const istanbulCoverageComposed = composeIstanbulCoverages(istanbulCoverages)
40
- return markCoverageAsConverted(istanbulCoverageComposed)
45
+ istanbulCoverageComposed = istanbulCoverageComposed
46
+ ? composeTwoFileByFileIstanbulCoverages(
47
+ istanbulCoverageComposed,
48
+ istanbulCoverage,
49
+ )
50
+ : istanbulCoverage
51
+ }, Promise.resolve())
52
+
53
+ if (!istanbulCoverageComposed) {
54
+ return {}
55
+ }
56
+ istanbulCoverageComposed = markAsConvertedFromV8(istanbulCoverageComposed)
57
+ return istanbulCoverageComposed
58
+ } finally {
59
+ await operation.end()
60
+ }
41
61
  }
42
62
 
43
- const markCoverageAsConverted = (istanbulCoverage) => {
44
- const istanbulCoverageMarked = {}
45
- Object.keys(istanbulCoverage).forEach((key) => {
46
- istanbulCoverageMarked[key] = {
47
- ...istanbulCoverage[key],
63
+ const markAsConvertedFromV8 = (fileByFileCoverage) => {
64
+ const fileByFileMarked = {}
65
+ Object.keys(fileByFileCoverage).forEach((key) => {
66
+ const fileCoverage = fileByFileCoverage[key]
67
+ fileByFileMarked[key] = {
68
+ ...fileCoverage,
48
69
  fromV8: true,
49
70
  }
50
71
  })
51
- return istanbulCoverageMarked
72
+ return fileByFileMarked
52
73
  }
53
74
 
54
75
  const sourcesFromSourceMapCache = (url, sourceMapCache) => {
@@ -5,7 +5,8 @@ import { EXECUTION_COLORS } from "./execution_colors.js"
5
5
 
6
6
  export const createSummaryLog = (summary) => `
7
7
  -------------- summary -----------------
8
- ${createSummaryMessage(summary)}${createTotalDurationMessage(summary)}
8
+ ${createSummaryMessage(summary)}
9
+ total duration: ${msAsDuration(summary.duration)}
9
10
  ----------------------------------------
10
11
  `
11
12
 
@@ -21,7 +22,9 @@ const createSummaryMessage = ({
21
22
  return `no execution`
22
23
  }
23
24
 
24
- return `${executionCount} execution: ${createSummaryDetails({
25
+ const executionLabel =
26
+ executionCount === 1 ? `1 execution` : `${executionCount} executions`
27
+ return `${executionLabel}: ${createSummaryDetails({
25
28
  executionCount,
26
29
  abortedCount,
27
30
  timedoutCount,
@@ -112,10 +115,3 @@ const createMixedDetails = ({
112
115
 
113
116
  return `${parts.join(", ")}`
114
117
  }
115
-
116
- const createTotalDurationMessage = ({ startMs, endMs }) => {
117
- if (!endMs) return ""
118
-
119
- return `
120
- total duration: ${msAsDuration(endMs - startMs)}`
121
- }