@jsenv/core 27.1.0 → 27.2.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 (33) hide show
  1. package/dist/controllable_child_process.mjs +139 -0
  2. package/dist/controllable_worker_thread.mjs +103 -0
  3. package/dist/js/execute_using_dynamic_import.js +169 -0
  4. package/dist/js/v8_coverage.js +539 -0
  5. package/dist/main.js +575 -804
  6. package/package.json +8 -7
  7. package/src/build/build.js +9 -12
  8. package/src/build/build_urls_generator.js +1 -1
  9. package/src/build/inject_global_version_mappings.js +3 -2
  10. package/src/build/inject_service_worker_urls.js +1 -2
  11. package/src/execute/run.js +50 -68
  12. package/src/execute/runtimes/browsers/from_playwright.js +13 -8
  13. package/src/execute/runtimes/node/{controllable_file.mjs → controllable_child_process.mjs} +18 -50
  14. package/src/execute/runtimes/node/controllable_worker_thread.mjs +103 -0
  15. package/src/execute/runtimes/node/execute_using_dynamic_import.js +49 -0
  16. package/src/execute/runtimes/node/exit_codes.js +9 -0
  17. package/src/execute/runtimes/node/{node_process.js → node_child_process.js} +56 -50
  18. package/src/execute/runtimes/node/node_worker_thread.js +268 -25
  19. package/src/execute/runtimes/node/profiler_v8_coverage.js +56 -0
  20. package/src/main.js +3 -1
  21. package/src/omega/kitchen.js +19 -6
  22. package/src/omega/server/file_service.js +2 -2
  23. package/src/omega/url_graph/url_graph_load.js +0 -1
  24. package/src/omega/url_graph.js +1 -0
  25. package/src/plugins/bundling/js_module/bundle_js_module.js +2 -5
  26. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic.js +18 -15
  27. package/src/plugins/url_resolution/jsenv_plugin_url_resolution.js +2 -1
  28. package/src/test/coverage/report_to_coverage.js +16 -19
  29. package/src/test/coverage/v8_coverage.js +26 -0
  30. package/src/test/coverage/{v8_coverage_from_directory.js → v8_coverage_node_directory.js} +22 -26
  31. package/src/test/execute_plan.js +92 -91
  32. package/src/test/execute_test_plan.js +15 -13
  33. package/dist/js/controllable_file.mjs +0 -227
@@ -50,10 +50,10 @@ export const createFileService = ({
50
50
  reference = urlGraph.inferReference(request.ressource, parentUrl)
51
51
  }
52
52
  if (!reference) {
53
- const entryPoint = kitchen.prepareEntryPoint({
53
+ const entryPoint = kitchen.injectReference({
54
54
  trace: parentUrl || rootDirectoryUrl,
55
55
  parentUrl: parentUrl || rootDirectoryUrl,
56
- type: "entry_point",
56
+ type: "http_request",
57
57
  specifier: request.ressource,
58
58
  })
59
59
  reference = entryPoint[0]
@@ -56,7 +56,6 @@ export const loadUrlGraph = async ({
56
56
  type,
57
57
  specifier,
58
58
  })
59
- entryUrlInfo.data.isEntryPoint = true
60
59
  cook(entryUrlInfo, { reference: entryReference })
61
60
  return [entryReference, entryUrlInfo]
62
61
  },
@@ -207,6 +207,7 @@ const createUrlInfo = (url) => {
207
207
  originalUrl: undefined,
208
208
  generatedUrl: null,
209
209
  filename: "",
210
+ isEntryPoint: false,
210
211
  isInline: false,
211
212
  inlineUrlSite: null,
212
213
  shouldHandle: undefined,
@@ -94,7 +94,7 @@ const rollupPluginJsenv = ({
94
94
  let previousNonEntryPointModuleId
95
95
  jsModuleUrlInfos.forEach((jsModuleUrlInfo) => {
96
96
  const id = jsModuleUrlInfo.url
97
- if (jsModuleUrlInfo.data.isEntryPoint) {
97
+ if (jsModuleUrlInfo.isEntryPoint) {
98
98
  emitChunk({
99
99
  id,
100
100
  })
@@ -355,10 +355,7 @@ const willBeInsideJsDirectory = ({
355
355
  // generated by rollup
356
356
  return true
357
357
  }
358
- if (
359
- !jsModuleUrlInfo.data.isEntryPoint &&
360
- !jsModuleUrlInfo.data.isWebWorkerEntryPoint
361
- ) {
358
+ if (!jsModuleUrlInfo.isEntryPoint) {
362
359
  // not an entry point, jsenv will put it inside js/ directory
363
360
  return true
364
361
  }
@@ -101,24 +101,24 @@ const jsenvPluginAsJsClassicConversion = ({
101
101
  if (!originalUrlInfo) {
102
102
  return null
103
103
  }
104
- const isJsEntryPoint =
105
- // in general html files are entry points
106
- // but during build js can be sepcified as an entry point
107
- // (meaning there is no html file where we can inject systemjs)
108
- // in that case we need to inject systemjs in the js file
109
- originalUrlInfo.data.isEntryPoint ||
110
- // In thoose case we need to inject systemjs the worker js file
111
- originalUrlInfo.data.isWebWorkerEntryPoint
112
- // if it's an entry point without dependency (it does not use import)
113
- // then we can use UMD, otherwise we have to use systemjs
114
- // because it is imported by systemjs
115
104
  const jsClassicFormat =
116
- isJsEntryPoint && !originalUrlInfo.data.usesImport ? "umd" : "system"
105
+ // in general html file are entry points, but js can be entry point when:
106
+ // - passed in entryPoints to build
107
+ // - is used by web worker
108
+ // - the reference contains ?entry_point
109
+ // When js is entry point there can be no HTML to inject systemjs
110
+ // and systemjs must be injected into the js file
111
+ originalUrlInfo.isEntryPoint &&
112
+ // if it's an entry point without dependency (it does not use import)
113
+ // then we can use UMD, otherwise we have to use systemjs
114
+ // because it is imported by systemjs
115
+ !originalUrlInfo.data.usesImport
116
+ ? "umd"
117
+ : "system"
117
118
  const { content, sourcemap } = await convertJsModuleToJsClassic({
118
119
  systemJsInjection,
119
120
  systemJsClientFileUrl,
120
121
  urlInfo: originalUrlInfo,
121
- isJsEntryPoint,
122
122
  jsClassicFormat,
123
123
  })
124
124
  urlInfo.data.jsClassicFormat = jsClassicFormat
@@ -160,7 +160,6 @@ const convertJsModuleToJsClassic = async ({
160
160
  systemJsInjection,
161
161
  systemJsClientFileUrl,
162
162
  urlInfo,
163
- isJsEntryPoint,
164
163
  jsClassicFormat,
165
164
  }) => {
166
165
  const { code, map } = await applyBabelPlugins({
@@ -193,7 +192,11 @@ const convertJsModuleToJsClassic = async ({
193
192
  })
194
193
  let sourcemap = urlInfo.sourcemap
195
194
  sourcemap = await composeTwoSourcemaps(sourcemap, map)
196
- if (systemJsInjection && jsClassicFormat === "system" && isJsEntryPoint) {
195
+ if (
196
+ systemJsInjection &&
197
+ jsClassicFormat === "system" &&
198
+ urlInfo.isEntryPoint
199
+ ) {
197
200
  const magicSource = createMagicSource(code)
198
201
  const systemjsCode = readFileSync(systemJsClientFileUrl, { as: "string" })
199
202
  magicSource.prepend(`${systemjsCode}\n\n`)
@@ -9,7 +9,8 @@ export const jsenvPluginUrlResolution = () => {
9
9
  name: "jsenv:url_resolution",
10
10
  appliesDuring: "*",
11
11
  resolveUrl: {
12
- "entry_point": urlResolver,
12
+ "http_request": urlResolver, // during dev
13
+ "entry_point": urlResolver, // during build
13
14
  "link_href": urlResolver,
14
15
  "script_src": urlResolver,
15
16
  "a_href": urlResolver,
@@ -1,10 +1,8 @@
1
- import { readFile } from "@jsenv/filesystem"
1
+ import { readFileSync } from "node:fs"
2
2
  import { Abort } from "@jsenv/abort"
3
3
 
4
- import {
5
- visitNodeV8Directory,
6
- filterV8Coverage,
7
- } from "./v8_coverage_from_directory.js"
4
+ import { filterV8Coverage } from "./v8_coverage.js"
5
+ import { readNodeV8CoverageDirectory } from "./v8_coverage_node_directory.js"
8
6
  import { composeTwoV8Coverages } from "./v8_coverage_composition.js"
9
7
  import { composeTwoFileByFileIstanbulCoverages } from "./istanbul_coverage_composition.js"
10
8
  import { v8CoverageToIstanbul } from "./v8_coverage_to_istanbul.js"
@@ -20,8 +18,7 @@ export const reportToCoverage = async (
20
18
  rootDirectoryUrl,
21
19
  coverageConfig,
22
20
  coverageIncludeMissing,
23
- urlShouldBeCovered,
24
- coverageForceIstanbul,
21
+ coverageMethodForNodeJs,
25
22
  coverageV8ConflictWarning,
26
23
  },
27
24
  ) => {
@@ -49,24 +46,24 @@ export const reportToCoverage = async (
49
46
  // that were suppose to be coverage but were not.
50
47
  if (
51
48
  executionResult.status === "completed" &&
52
- executionResult.runtimeName !== "node" &&
53
- !process.env.NODE_V8_COVERAGE
49
+ executionResult.type === "node" &&
50
+ coverageMethodForNodeJs !== "NODE_V8_COVERAGE"
54
51
  ) {
55
52
  logger.warn(
56
- `No execution.coverageFileUrl from execution named "${executionName}" of ${file}`,
53
+ `No "coverageFileUrl" from execution named "${executionName}" of ${file}`,
57
54
  )
58
55
  }
59
56
  },
60
57
  })
61
58
 
62
- if (!coverageForceIstanbul && process.env.NODE_V8_COVERAGE) {
63
- await visitNodeV8Directory({
59
+ if (coverageMethodForNodeJs === "NODE_V8_COVERAGE") {
60
+ await readNodeV8CoverageDirectory({
64
61
  logger,
65
62
  signal,
66
- NODE_V8_COVERAGE: process.env.NODE_V8_COVERAGE,
67
- onV8Coverage: (nodeV8Coverage) => {
68
- const nodeV8CoverageLight = filterV8Coverage(nodeV8Coverage, {
69
- urlShouldBeCovered,
63
+ onV8Coverage: async (nodeV8Coverage) => {
64
+ const nodeV8CoverageLight = await filterV8Coverage(nodeV8Coverage, {
65
+ rootDirectoryUrl,
66
+ coverageConfig,
70
67
  })
71
68
  v8Coverage = v8Coverage
72
69
  ? composeTwoV8Coverages(v8Coverage, nodeV8CoverageLight)
@@ -164,9 +161,9 @@ const getCoverageFromReport = async ({ signal, report, onMissing }) => {
164
161
  return
165
162
  }
166
163
 
167
- const executionCoverage = await readFile(coverageFileUrl, {
168
- as: "json",
169
- })
164
+ const executionCoverage = JSON.parse(
165
+ String(readFileSync(new URL(coverageFileUrl))),
166
+ )
170
167
  if (isV8Coverage(executionCoverage)) {
171
168
  v8Coverage = v8Coverage
172
169
  ? composeTwoV8Coverages(v8Coverage, executionCoverage)
@@ -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
+ }
@@ -1,27 +1,30 @@
1
- import {
2
- assertAndNormalizeDirectoryUrl,
3
- readDirectory,
4
- readFile,
5
- } from "@jsenv/filesystem"
6
- import { resolveUrl } from "@jsenv/urls"
1
+ import { readFileSync, readdirSync } from "node:fs"
2
+ import { assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
7
3
  import { createDetailedMessage } from "@jsenv/log"
8
4
  import { Abort } from "@jsenv/abort"
9
5
 
10
- export const visitNodeV8Directory = async ({
6
+ export const readNodeV8CoverageDirectory = async ({
11
7
  logger,
12
8
  signal,
13
- NODE_V8_COVERAGE,
14
9
  onV8Coverage,
15
10
  maxMsWaitingForNodeToWriteCoverageFile = 2000,
16
11
  }) => {
12
+ const NODE_V8_COVERAGE = process.env.NODE_V8_COVERAGE
17
13
  const operation = Abort.startOperation()
18
14
  operation.addAbortSignal(signal)
19
15
 
16
+ let timeSpentTrying = 0
20
17
  const tryReadDirectory = async () => {
21
- const dirContent = await readDirectory(NODE_V8_COVERAGE)
18
+ const dirContent = readdirSync(NODE_V8_COVERAGE)
22
19
  if (dirContent.length > 0) {
23
20
  return dirContent
24
21
  }
22
+ if (timeSpentTrying < maxMsWaitingForNodeToWriteCoverageFile) {
23
+ await new Promise((resolve) => setTimeout(resolve, 200))
24
+ timeSpentTrying += 200
25
+ logger.debug("retry to read coverage directory")
26
+ return tryReadDirectory()
27
+ }
25
28
  logger.warn(`v8 coverage directory is empty at ${NODE_V8_COVERAGE}`)
26
29
  return dirContent
27
30
  }
@@ -32,17 +35,19 @@ export const visitNodeV8Directory = async ({
32
35
 
33
36
  const coverageDirectoryUrl =
34
37
  assertAndNormalizeDirectoryUrl(NODE_V8_COVERAGE)
38
+
35
39
  await dirContent.reduce(async (previous, dirEntry) => {
36
40
  operation.throwIfAborted()
37
41
  await previous
38
42
 
39
- const dirEntryUrl = resolveUrl(dirEntry, coverageDirectoryUrl)
40
- const tryReadJsonFile = async (timeSpentTrying = 0) => {
41
- const fileContent = await readFile(dirEntryUrl, { as: "string" })
43
+ const dirEntryUrl = new URL(dirEntry, coverageDirectoryUrl)
44
+ const tryReadJsonFile = async () => {
45
+ const fileContent = String(readFileSync(dirEntryUrl))
42
46
  if (fileContent === "") {
43
- if (timeSpentTrying < 400) {
47
+ if (timeSpentTrying < maxMsWaitingForNodeToWriteCoverageFile) {
44
48
  await new Promise((resolve) => setTimeout(resolve, 200))
45
- return tryReadJsonFile(timeSpentTrying + 200)
49
+ timeSpentTrying += 200
50
+ return tryReadJsonFile()
46
51
  }
47
52
  console.warn(`Coverage JSON file is empty at ${dirEntryUrl}`)
48
53
  return null
@@ -54,7 +59,8 @@ export const visitNodeV8Directory = async ({
54
59
  } catch (e) {
55
60
  if (timeSpentTrying < maxMsWaitingForNodeToWriteCoverageFile) {
56
61
  await new Promise((resolve) => setTimeout(resolve, 200))
57
- return tryReadJsonFile(timeSpentTrying + 200)
62
+ timeSpentTrying += 200
63
+ return tryReadJsonFile()
58
64
  }
59
65
  console.warn(
60
66
  createDetailedMessage(`Error while reading coverage file`, {
@@ -68,20 +74,10 @@ export const visitNodeV8Directory = async ({
68
74
 
69
75
  const fileContent = await tryReadJsonFile()
70
76
  if (fileContent) {
71
- onV8Coverage(fileContent)
77
+ await onV8Coverage(fileContent)
72
78
  }
73
79
  }, Promise.resolve())
74
80
  } finally {
75
81
  await operation.end()
76
82
  }
77
83
  }
78
-
79
- export const filterV8Coverage = (v8Coverage, { urlShouldBeCovered }) => {
80
- const v8CoverageFiltered = {
81
- ...v8Coverage,
82
- result: v8Coverage.result.filter((fileReport) =>
83
- urlShouldBeCovered(fileReport.url),
84
- ),
85
- }
86
- return v8CoverageFiltered
87
- }
@@ -1,10 +1,9 @@
1
1
  import { existsSync } from "node:fs"
2
2
  import { memoryUsage } from "node:process"
3
+ import { takeCoverage } from "node:v8"
3
4
  import wrapAnsi from "wrap-ansi"
4
5
  import stripAnsi from "strip-ansi"
5
- import cuid from "cuid"
6
6
 
7
- import { URL_META } from "@jsenv/url-meta"
8
7
  import { urlToFileSystemPath } from "@jsenv/urls"
9
8
  import {
10
9
  createDetailedMessage,
@@ -13,11 +12,7 @@ import {
13
12
  startSpinner,
14
13
  } from "@jsenv/log"
15
14
  import { Abort, raceProcessTeardownEvents } from "@jsenv/abort"
16
- import {
17
- writeDirectory,
18
- ensureEmptyDirectory,
19
- writeFileSync,
20
- } from "@jsenv/filesystem"
15
+ import { ensureEmptyDirectory, writeFileSync } from "@jsenv/filesystem"
21
16
 
22
17
  import { babelPluginInstrument } from "./coverage/babel_plugin_instrument.js"
23
18
  import { reportToCoverage } from "./coverage/report_to_coverage.js"
@@ -54,10 +49,11 @@ export const executePlan = async (
54
49
  gcBetweenExecutions,
55
50
  cooldownBetweenExecutions,
56
51
 
57
- coverage,
52
+ coverageEnabled,
58
53
  coverageConfig,
59
54
  coverageIncludeMissing,
60
- coverageForceIstanbul,
55
+ coverageMethodForBrowsers,
56
+ coverageMethodForNodeJs,
61
57
  coverageV8ConflictWarning,
62
58
  coverageTempDirectoryRelativeUrl,
63
59
 
@@ -79,9 +75,13 @@ export const executePlan = async (
79
75
  afterExecutionCallback = () => {},
80
76
  } = {},
81
77
  ) => {
78
+ const executePlanReturnValue = {}
79
+ const report = {}
80
+ const callbacks = []
82
81
  const stopAfterAllSignal = { notify: () => {} }
83
82
 
84
83
  let someNeedsServer = false
84
+ let someNodeRuntime = false
85
85
  const runtimes = {}
86
86
  Object.keys(plan).forEach((filePattern) => {
87
87
  const filePlan = plan[filePattern]
@@ -93,6 +93,9 @@ export const executePlan = async (
93
93
  if (runtime.needsServer) {
94
94
  someNeedsServer = true
95
95
  }
96
+ if (runtime.type === "node") {
97
+ someNodeRuntime = true
98
+ }
96
99
  }
97
100
  })
98
101
  })
@@ -122,10 +125,76 @@ export const executePlan = async (
122
125
  }
123
126
 
124
127
  try {
128
+ const coverageTempDirectoryUrl = new URL(
129
+ coverageTempDirectoryRelativeUrl,
130
+ rootDirectoryUrl,
131
+ ).href
132
+ if (
133
+ someNodeRuntime &&
134
+ coverageEnabled &&
135
+ coverageMethodForNodeJs === "NODE_V8_COVERAGE"
136
+ ) {
137
+ if (process.env.NODE_V8_COVERAGE) {
138
+ // when runned multiple times, we don't want to keep previous files in this directory
139
+ await ensureEmptyDirectory(process.env.NODE_V8_COVERAGE)
140
+ } else {
141
+ coverageMethodForNodeJs = "Profiler"
142
+ logger.warn(
143
+ createDetailedMessage(
144
+ `process.env.NODE_V8_COVERAGE is required to generate coverage for Node.js subprocesses`,
145
+ {
146
+ "suggestion": `Preprend NODE_V8_COVERAGE=.coverage/node to the command executing this process`,
147
+ "suggestion 2": `use coverageMethodForNodeJs: "Profiler". But it means coverage for child_process and worker_thread cannot be collected`,
148
+ },
149
+ ),
150
+ )
151
+ }
152
+ }
153
+
154
+ if (gcBetweenExecutions) {
155
+ ensureGlobalGc()
156
+ }
157
+
158
+ if (coverageEnabled) {
159
+ // when runned multiple times, we don't want to keep previous files in this directory
160
+ await ensureEmptyDirectory(coverageTempDirectoryUrl)
161
+ callbacks.push(async () => {
162
+ if (multipleExecutionsOperation.signal.aborted) {
163
+ // don't try to do the coverage stuff
164
+ return
165
+ }
166
+ try {
167
+ if (coverageMethodForNodeJs === "NODE_V8_COVERAGE") {
168
+ takeCoverage()
169
+ // conceptually we don't need coverage anymore so it would be
170
+ // good to call v8.stopCoverage()
171
+ // but it logs a strange message about "result is not an object"
172
+ }
173
+ const planCoverage = await reportToCoverage(report, {
174
+ signal: multipleExecutionsOperation.signal,
175
+ logger,
176
+ rootDirectoryUrl,
177
+ coverageConfig,
178
+ coverageIncludeMissing,
179
+ coverageMethodForBrowsers,
180
+ coverageV8ConflictWarning,
181
+ })
182
+ executePlanReturnValue.planCoverage = planCoverage
183
+ } catch (e) {
184
+ if (Abort.isAbortError(e)) {
185
+ return
186
+ }
187
+ throw e
188
+ }
189
+ })
190
+ }
191
+
125
192
  let runtimeParams = {
126
193
  rootDirectoryUrl,
127
- collectCoverage: coverage,
128
- coverageForceIstanbul,
194
+ coverageEnabled,
195
+ coverageConfig,
196
+ coverageMethodForBrowsers,
197
+ coverageMethodForNodeJs,
129
198
  stopAfterAllSignal,
130
199
  }
131
200
  if (someNeedsServer) {
@@ -154,7 +223,7 @@ export const executePlan = async (
154
223
  ...transpilation,
155
224
  getCustomBabelPlugins: ({ clientRuntimeCompat }) => {
156
225
  if (
157
- coverage &&
226
+ coverageEnabled &&
158
227
  Object.keys(clientRuntimeCompat)[0] !== "chrome"
159
228
  ) {
160
229
  return {
@@ -221,76 +290,8 @@ export const executePlan = async (
221
290
  process.exitCode !== 1
222
291
 
223
292
  const startMs = Date.now()
224
- const report = {}
225
293
  let rawOutput = ""
226
294
 
227
- let transformReturnValue = (value) => value
228
- if (gcBetweenExecutions) {
229
- ensureGlobalGc()
230
- }
231
-
232
- const coverageTempDirectoryUrl = new URL(
233
- coverageTempDirectoryRelativeUrl,
234
- rootDirectoryUrl,
235
- ).href
236
-
237
- if (coverage) {
238
- const associations = URL_META.resolveAssociations(
239
- { cover: coverageConfig },
240
- rootDirectoryUrl,
241
- )
242
- const urlShouldBeCovered = (url) => {
243
- const { cover } = URL_META.applyAssociations({
244
- url: new URL(url, rootDirectoryUrl).href,
245
- associations,
246
- })
247
- return cover
248
- }
249
- runtimeParams.urlShouldBeCovered = urlShouldBeCovered
250
-
251
- // in case runned multiple times, we don't want to keep writing lot of files in this directory
252
- if (!process.env.NODE_V8_COVERAGE) {
253
- await ensureEmptyDirectory(coverageTempDirectoryUrl)
254
- }
255
- if (runtimes.node) {
256
- // v8 coverage is written in a directoy and auto propagate to subprocesses
257
- // through process.env.NODE_V8_COVERAGE.
258
- if (!coverageForceIstanbul && !process.env.NODE_V8_COVERAGE) {
259
- const v8CoverageDirectory = new URL(
260
- `./node_v8/${cuid()}`,
261
- coverageTempDirectoryUrl,
262
- ).href
263
- await writeDirectory(v8CoverageDirectory, { allowUseless: true })
264
- process.env.NODE_V8_COVERAGE =
265
- urlToFileSystemPath(v8CoverageDirectory)
266
- }
267
- }
268
- transformReturnValue = async (value) => {
269
- if (multipleExecutionsOperation.signal.aborted) {
270
- // don't try to do the coverage stuff
271
- return value
272
- }
273
- try {
274
- value.coverage = await reportToCoverage(value.report, {
275
- signal: multipleExecutionsOperation.signal,
276
- logger,
277
- rootDirectoryUrl,
278
- coverageConfig,
279
- coverageIncludeMissing,
280
- coverageForceIstanbul,
281
- urlShouldBeCovered,
282
- coverageV8ConflictWarning,
283
- })
284
- } catch (e) {
285
- if (Abort.isAbortError(e)) {
286
- return value
287
- }
288
- throw e
289
- }
290
- return value
291
- }
292
- }
293
-
294
295
  logger.info("")
295
296
  let executionLog = createLog({ newLine: "" })
296
297
  const counters = {
@@ -309,6 +310,7 @@ export const executePlan = async (
309
310
  start: async (paramsFromStep) => {
310
311
  const executionIndex = executionSteps.indexOf(paramsFromStep)
311
312
  const { executionName, fileRelativeUrl, runtime } = paramsFromStep
313
+ const runtimeType = runtime.type
312
314
  const runtimeName = runtime.name
313
315
  const runtimeVersion = runtime.version
314
316
  const executionParams = {
@@ -324,6 +326,7 @@ export const executePlan = async (
324
326
  }
325
327
  const beforeExecutionInfo = {
326
328
  fileRelativeUrl,
329
+ runtimeType,
327
330
  runtimeName,
328
331
  runtimeVersion,
329
332
  executionIndex,
@@ -366,7 +369,7 @@ export const executePlan = async (
366
369
  keepRunning,
367
370
  mirrorConsole: false, // file are executed in parallel, log would be a mess to read
368
371
  collectConsole: executionParams.collectConsole,
369
- collectCoverage: coverage,
372
+ coverageEnabled,
370
373
  coverageTempDirectoryUrl,
371
374
  runtime: executionParams.runtime,
372
375
  runtimeParams: {
@@ -483,16 +486,14 @@ export const executePlan = async (
483
486
  writeFileSync(logFileUrl, rawOutput)
484
487
  logger.info(`-> ${urlToFileSystemPath(logFileUrl)}`)
485
488
  }
486
- const result = await transformReturnValue({
487
- summary,
488
- report,
489
- })
490
- return {
491
- aborted: multipleExecutionsOperation.signal.aborted,
492
- planSummary: result.summary,
493
- planReport: result.report,
494
- planCoverage: result.coverage,
495
- }
489
+ executePlanReturnValue.aborted = multipleExecutionsOperation.signal.aborted
490
+ executePlanReturnValue.planSummary = summary
491
+ executePlanReturnValue.planReport = report
492
+ await callbacks.reduce(async (previous, callback) => {
493
+ await previous
494
+ await callback()
495
+ }, Promise.resolve())
496
+ return executePlanReturnValue
496
497
  } finally {
497
498
  await multipleExecutionsOperation.end()
498
499
  }
@@ -28,7 +28,7 @@ import { executePlan } from "./execute_plan.js"
28
28
  * @param {boolean} [testPlanParameters.failFast=false] Fails immediatly when a test execution fails
29
29
  * @param {number} [testPlanParameters.cooldownBetweenExecutions=0] Millisecond to wait between each execution
30
30
  * @param {boolean} [testPlanParameters.logMemoryHeapUsage=false] Add memory heap usage during logs
31
- * @param {boolean} [testPlanParameters.coverage=false] Controls if coverage is collected during files executions
31
+ * @param {boolean} [testPlanParameters.coverageEnabled=false] Controls if coverage is collected during files executions
32
32
  * @param {boolean} [testPlanParameters.coverageV8ConflictWarning=true] Warn when coverage from 2 executions cannot be merged
33
33
  * @return {Object} An object containing the result of all file executions
34
34
  */
@@ -60,23 +60,24 @@ export const executeTestPlan = async ({
60
60
  cooldownBetweenExecutions = 0,
61
61
  gcBetweenExecutions = logMemoryHeapUsage,
62
62
 
63
- coverage = process.argv.includes("--cover") ||
63
+ coverageEnabled = process.argv.includes("--cover") ||
64
64
  process.argv.includes("--coverage"),
65
- coverageTempDirectoryRelativeUrl = "./.coverage/tmp/",
66
65
  coverageConfig = {
67
66
  "./src/": true,
68
67
  },
69
68
  coverageIncludeMissing = true,
70
69
  coverageAndExecutionAllowed = false,
71
- coverageForceIstanbul = false,
70
+ coverageMethodForNodeJs = "NODE_V8_COVERAGE", // "Profiler" also accepted
71
+ coverageMethodForBrowsers = "playwright_api", // "istanbul" also accepted
72
72
  coverageV8ConflictWarning = true,
73
- coverageReportTextLog = true,
74
- coverageReportJsonFile = process.env.CI ? null : "./.coverage/coverage.json",
75
- coverageReportHtmlDirectory = process.env.CI ? "./.coverage/" : null,
73
+ coverageTempDirectoryRelativeUrl = "./.coverage/tmp/",
76
74
  // skip empty means empty files won't appear in the coverage reports (json and html)
77
75
  coverageReportSkipEmpty = false,
78
76
  // skip full means file with 100% coverage won't appear in coverage reports (json and html)
79
77
  coverageReportSkipFull = false,
78
+ coverageReportTextLog = true,
79
+ coverageReportJsonFile = process.env.CI ? null : "./.coverage/coverage.json",
80
+ coverageReportHtmlDirectory = process.env.CI ? "./.coverage/" : null,
80
81
 
81
82
  sourcemaps = "inline",
82
83
  plugins = [],
@@ -95,7 +96,7 @@ export const executeTestPlan = async ({
95
96
  if (typeof testPlan !== "object") {
96
97
  throw new Error(`testPlan must be an object, got ${testPlan}`)
97
98
  }
98
- if (coverage) {
99
+ if (coverageEnabled) {
99
100
  if (typeof coverageConfig !== "object") {
100
101
  throw new TypeError(
101
102
  `coverageConfig must be an object, got ${coverageConfig}`,
@@ -156,10 +157,11 @@ export const executeTestPlan = async ({
156
157
  cooldownBetweenExecutions,
157
158
  gcBetweenExecutions,
158
159
 
159
- coverage,
160
+ coverageEnabled,
160
161
  coverageConfig,
161
162
  coverageIncludeMissing,
162
- coverageForceIstanbul,
163
+ coverageMethodForBrowsers,
164
+ coverageMethodForNodeJs,
163
165
  coverageV8ConflictWarning,
164
166
  coverageTempDirectoryRelativeUrl,
165
167
 
@@ -189,7 +191,7 @@ export const executeTestPlan = async ({
189
191
  // keep this one first because it does ensureEmptyDirectory
190
192
  // and in case coverage json file gets written in the same directory
191
193
  // it must be done before
192
- if (coverage && coverageReportHtmlDirectory) {
194
+ if (coverageEnabled && coverageReportHtmlDirectory) {
193
195
  const coverageHtmlDirectoryUrl = resolveDirectoryUrl(
194
196
  coverageReportHtmlDirectory,
195
197
  rootDirectoryUrl,
@@ -216,7 +218,7 @@ export const executeTestPlan = async ({
216
218
  }),
217
219
  )
218
220
  }
219
- if (coverage && coverageReportJsonFile) {
221
+ if (coverageEnabled && coverageReportJsonFile) {
220
222
  const coverageJsonFileUrl = new URL(
221
223
  coverageReportJsonFile,
222
224
  rootDirectoryUrl,
@@ -229,7 +231,7 @@ export const executeTestPlan = async ({
229
231
  }),
230
232
  )
231
233
  }
232
- if (coverage && coverageReportTextLog) {
234
+ if (coverageEnabled && coverageReportTextLog) {
233
235
  promises.push(
234
236
  generateCoverageTextLog(result.planCoverage, {
235
237
  coverageReportSkipEmpty,