@jsenv/core 27.0.3 → 27.2.1
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.
- package/dist/controllable_child_process.mjs +139 -0
- package/dist/controllable_worker_thread.mjs +103 -0
- package/dist/js/execute_using_dynamic_import.js +169 -0
- package/dist/js/v8_coverage.js +539 -0
- package/dist/main.js +683 -818
- package/package.json +9 -8
- package/src/build/build.js +9 -12
- package/src/build/build_urls_generator.js +1 -1
- package/src/build/inject_global_version_mappings.js +3 -2
- package/src/build/inject_service_worker_urls.js +1 -2
- package/src/execute/run.js +50 -68
- package/src/execute/runtimes/browsers/chromium.js +1 -1
- package/src/execute/runtimes/browsers/firefox.js +1 -1
- package/src/execute/runtimes/browsers/from_playwright.js +13 -8
- package/src/execute/runtimes/browsers/webkit.js +1 -1
- package/src/execute/runtimes/node/{controllable_file.mjs → controllable_child_process.mjs} +18 -50
- package/src/execute/runtimes/node/controllable_worker_thread.mjs +103 -0
- package/src/execute/runtimes/node/execute_using_dynamic_import.js +49 -0
- package/src/execute/runtimes/node/exit_codes.js +9 -0
- package/src/execute/runtimes/node/{node_process.js → node_child_process.js} +56 -50
- package/src/execute/runtimes/node/node_worker_thread.js +268 -25
- package/src/execute/runtimes/node/profiler_v8_coverage.js +56 -0
- package/src/main.js +3 -1
- package/src/omega/kitchen.js +19 -6
- package/src/omega/server/file_service.js +2 -2
- package/src/omega/url_graph/url_graph_load.js +0 -1
- package/src/omega/url_graph.js +1 -0
- package/src/plugins/bundling/js_module/bundle_js_module.js +2 -5
- package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic.js +18 -15
- package/src/plugins/url_resolution/jsenv_plugin_url_resolution.js +2 -1
- package/src/test/coverage/report_to_coverage.js +16 -19
- package/src/test/coverage/v8_coverage.js +26 -0
- package/src/test/coverage/{v8_coverage_from_directory.js → v8_coverage_node_directory.js} +22 -26
- package/src/test/execute_plan.js +98 -91
- package/src/test/execute_test_plan.js +19 -13
- package/src/test/logs_file_execution.js +90 -13
- package/dist/js/controllable_file.mjs +0 -227
package/src/test/execute_plan.js
CHANGED
|
@@ -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"
|
|
@@ -37,6 +32,8 @@ export const executePlan = async (
|
|
|
37
32
|
signal,
|
|
38
33
|
handleSIGINT,
|
|
39
34
|
logger,
|
|
35
|
+
logRuntime,
|
|
36
|
+
logEachDuration,
|
|
40
37
|
logSummary,
|
|
41
38
|
logTimeUsage,
|
|
42
39
|
logMemoryHeapUsage,
|
|
@@ -52,10 +49,11 @@ export const executePlan = async (
|
|
|
52
49
|
gcBetweenExecutions,
|
|
53
50
|
cooldownBetweenExecutions,
|
|
54
51
|
|
|
55
|
-
|
|
52
|
+
coverageEnabled,
|
|
56
53
|
coverageConfig,
|
|
57
54
|
coverageIncludeMissing,
|
|
58
|
-
|
|
55
|
+
coverageMethodForBrowsers,
|
|
56
|
+
coverageMethodForNodeJs,
|
|
59
57
|
coverageV8ConflictWarning,
|
|
60
58
|
coverageTempDirectoryRelativeUrl,
|
|
61
59
|
|
|
@@ -77,9 +75,13 @@ export const executePlan = async (
|
|
|
77
75
|
afterExecutionCallback = () => {},
|
|
78
76
|
} = {},
|
|
79
77
|
) => {
|
|
78
|
+
const executePlanReturnValue = {}
|
|
79
|
+
const report = {}
|
|
80
|
+
const callbacks = []
|
|
80
81
|
const stopAfterAllSignal = { notify: () => {} }
|
|
81
82
|
|
|
82
83
|
let someNeedsServer = false
|
|
84
|
+
let someNodeRuntime = false
|
|
83
85
|
const runtimes = {}
|
|
84
86
|
Object.keys(plan).forEach((filePattern) => {
|
|
85
87
|
const filePlan = plan[filePattern]
|
|
@@ -91,6 +93,9 @@ export const executePlan = async (
|
|
|
91
93
|
if (runtime.needsServer) {
|
|
92
94
|
someNeedsServer = true
|
|
93
95
|
}
|
|
96
|
+
if (runtime.type === "node") {
|
|
97
|
+
someNodeRuntime = true
|
|
98
|
+
}
|
|
94
99
|
}
|
|
95
100
|
})
|
|
96
101
|
})
|
|
@@ -120,10 +125,76 @@ export const executePlan = async (
|
|
|
120
125
|
}
|
|
121
126
|
|
|
122
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
|
+
|
|
123
192
|
let runtimeParams = {
|
|
124
193
|
rootDirectoryUrl,
|
|
125
|
-
|
|
126
|
-
|
|
194
|
+
coverageEnabled,
|
|
195
|
+
coverageConfig,
|
|
196
|
+
coverageMethodForBrowsers,
|
|
197
|
+
coverageMethodForNodeJs,
|
|
127
198
|
stopAfterAllSignal,
|
|
128
199
|
}
|
|
129
200
|
if (someNeedsServer) {
|
|
@@ -152,7 +223,7 @@ export const executePlan = async (
|
|
|
152
223
|
...transpilation,
|
|
153
224
|
getCustomBabelPlugins: ({ clientRuntimeCompat }) => {
|
|
154
225
|
if (
|
|
155
|
-
|
|
226
|
+
coverageEnabled &&
|
|
156
227
|
Object.keys(clientRuntimeCompat)[0] !== "chrome"
|
|
157
228
|
) {
|
|
158
229
|
return {
|
|
@@ -219,76 +290,8 @@ export const executePlan = async (
|
|
|
219
290
|
process.exitCode !== 1
|
|
220
291
|
|
|
221
292
|
const startMs = Date.now()
|
|
222
|
-
const report = {}
|
|
223
293
|
let rawOutput = ""
|
|
224
294
|
|
|
225
|
-
let transformReturnValue = (value) => value
|
|
226
|
-
if (gcBetweenExecutions) {
|
|
227
|
-
ensureGlobalGc()
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const coverageTempDirectoryUrl = new URL(
|
|
231
|
-
coverageTempDirectoryRelativeUrl,
|
|
232
|
-
rootDirectoryUrl,
|
|
233
|
-
).href
|
|
234
|
-
|
|
235
|
-
if (coverage) {
|
|
236
|
-
const associations = URL_META.resolveAssociations(
|
|
237
|
-
{ cover: coverageConfig },
|
|
238
|
-
rootDirectoryUrl,
|
|
239
|
-
)
|
|
240
|
-
const urlShouldBeCovered = (url) => {
|
|
241
|
-
const { cover } = URL_META.applyAssociations({
|
|
242
|
-
url: new URL(url, rootDirectoryUrl).href,
|
|
243
|
-
associations,
|
|
244
|
-
})
|
|
245
|
-
return cover
|
|
246
|
-
}
|
|
247
|
-
runtimeParams.urlShouldBeCovered = urlShouldBeCovered
|
|
248
|
-
|
|
249
|
-
// in case runned multiple times, we don't want to keep writing lot of files in this directory
|
|
250
|
-
if (!process.env.NODE_V8_COVERAGE) {
|
|
251
|
-
await ensureEmptyDirectory(coverageTempDirectoryUrl)
|
|
252
|
-
}
|
|
253
|
-
if (runtimes.node) {
|
|
254
|
-
// v8 coverage is written in a directoy and auto propagate to subprocesses
|
|
255
|
-
// through process.env.NODE_V8_COVERAGE.
|
|
256
|
-
if (!coverageForceIstanbul && !process.env.NODE_V8_COVERAGE) {
|
|
257
|
-
const v8CoverageDirectory = new URL(
|
|
258
|
-
`./node_v8/${cuid()}`,
|
|
259
|
-
coverageTempDirectoryUrl,
|
|
260
|
-
).href
|
|
261
|
-
await writeDirectory(v8CoverageDirectory, { allowUseless: true })
|
|
262
|
-
process.env.NODE_V8_COVERAGE =
|
|
263
|
-
urlToFileSystemPath(v8CoverageDirectory)
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
transformReturnValue = async (value) => {
|
|
267
|
-
if (multipleExecutionsOperation.signal.aborted) {
|
|
268
|
-
// don't try to do the coverage stuff
|
|
269
|
-
return value
|
|
270
|
-
}
|
|
271
|
-
try {
|
|
272
|
-
value.coverage = await reportToCoverage(value.report, {
|
|
273
|
-
signal: multipleExecutionsOperation.signal,
|
|
274
|
-
logger,
|
|
275
|
-
rootDirectoryUrl,
|
|
276
|
-
coverageConfig,
|
|
277
|
-
coverageIncludeMissing,
|
|
278
|
-
coverageForceIstanbul,
|
|
279
|
-
urlShouldBeCovered,
|
|
280
|
-
coverageV8ConflictWarning,
|
|
281
|
-
})
|
|
282
|
-
} catch (e) {
|
|
283
|
-
if (Abort.isAbortError(e)) {
|
|
284
|
-
return value
|
|
285
|
-
}
|
|
286
|
-
throw e
|
|
287
|
-
}
|
|
288
|
-
return value
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
295
|
logger.info("")
|
|
293
296
|
let executionLog = createLog({ newLine: "" })
|
|
294
297
|
const counters = {
|
|
@@ -307,6 +310,7 @@ export const executePlan = async (
|
|
|
307
310
|
start: async (paramsFromStep) => {
|
|
308
311
|
const executionIndex = executionSteps.indexOf(paramsFromStep)
|
|
309
312
|
const { executionName, fileRelativeUrl, runtime } = paramsFromStep
|
|
313
|
+
const runtimeType = runtime.type
|
|
310
314
|
const runtimeName = runtime.name
|
|
311
315
|
const runtimeVersion = runtime.version
|
|
312
316
|
const executionParams = {
|
|
@@ -322,6 +326,7 @@ export const executePlan = async (
|
|
|
322
326
|
}
|
|
323
327
|
const beforeExecutionInfo = {
|
|
324
328
|
fileRelativeUrl,
|
|
329
|
+
runtimeType,
|
|
325
330
|
runtimeName,
|
|
326
331
|
runtimeVersion,
|
|
327
332
|
executionIndex,
|
|
@@ -338,6 +343,8 @@ export const executePlan = async (
|
|
|
338
343
|
render: () => {
|
|
339
344
|
return createExecutionLog(beforeExecutionInfo, {
|
|
340
345
|
counters,
|
|
346
|
+
logRuntime,
|
|
347
|
+
logEachDuration,
|
|
341
348
|
...(logTimeUsage
|
|
342
349
|
? {
|
|
343
350
|
timeEllapsed: Date.now() - startMs,
|
|
@@ -362,7 +369,7 @@ export const executePlan = async (
|
|
|
362
369
|
keepRunning,
|
|
363
370
|
mirrorConsole: false, // file are executed in parallel, log would be a mess to read
|
|
364
371
|
collectConsole: executionParams.collectConsole,
|
|
365
|
-
|
|
372
|
+
coverageEnabled,
|
|
366
373
|
coverageTempDirectoryUrl,
|
|
367
374
|
runtime: executionParams.runtime,
|
|
368
375
|
runtimeParams: {
|
|
@@ -411,6 +418,8 @@ export const executePlan = async (
|
|
|
411
418
|
let log = createExecutionLog(afterExecutionInfo, {
|
|
412
419
|
completedExecutionLogAbbreviation,
|
|
413
420
|
counters,
|
|
421
|
+
logRuntime,
|
|
422
|
+
logEachDuration,
|
|
414
423
|
...(logTimeUsage
|
|
415
424
|
? {
|
|
416
425
|
timeEllapsed: Date.now() - startMs,
|
|
@@ -477,16 +486,14 @@ export const executePlan = async (
|
|
|
477
486
|
writeFileSync(logFileUrl, rawOutput)
|
|
478
487
|
logger.info(`-> ${urlToFileSystemPath(logFileUrl)}`)
|
|
479
488
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
planCoverage: result.coverage,
|
|
489
|
-
}
|
|
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
|
|
490
497
|
} finally {
|
|
491
498
|
await multipleExecutionsOperation.end()
|
|
492
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.
|
|
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
|
*/
|
|
@@ -36,6 +36,8 @@ export const executeTestPlan = async ({
|
|
|
36
36
|
signal = new AbortController().signal,
|
|
37
37
|
handleSIGINT = true,
|
|
38
38
|
logLevel = "info",
|
|
39
|
+
logRuntime = true,
|
|
40
|
+
logEachDuration = true,
|
|
39
41
|
logSummary = true,
|
|
40
42
|
logTimeUsage = false,
|
|
41
43
|
logMemoryHeapUsage = false,
|
|
@@ -58,23 +60,24 @@ export const executeTestPlan = async ({
|
|
|
58
60
|
cooldownBetweenExecutions = 0,
|
|
59
61
|
gcBetweenExecutions = logMemoryHeapUsage,
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
coverageEnabled = process.argv.includes("--cover") ||
|
|
62
64
|
process.argv.includes("--coverage"),
|
|
63
|
-
coverageTempDirectoryRelativeUrl = "./.coverage/tmp/",
|
|
64
65
|
coverageConfig = {
|
|
65
66
|
"./src/": true,
|
|
66
67
|
},
|
|
67
68
|
coverageIncludeMissing = true,
|
|
68
69
|
coverageAndExecutionAllowed = false,
|
|
69
|
-
|
|
70
|
+
coverageMethodForNodeJs = "NODE_V8_COVERAGE", // "Profiler" also accepted
|
|
71
|
+
coverageMethodForBrowsers = "playwright_api", // "istanbul" also accepted
|
|
70
72
|
coverageV8ConflictWarning = true,
|
|
71
|
-
|
|
72
|
-
coverageReportJsonFile = process.env.CI ? null : "./.coverage/coverage.json",
|
|
73
|
-
coverageReportHtmlDirectory = process.env.CI ? "./.coverage/" : null,
|
|
73
|
+
coverageTempDirectoryRelativeUrl = "./.coverage/tmp/",
|
|
74
74
|
// skip empty means empty files won't appear in the coverage reports (json and html)
|
|
75
75
|
coverageReportSkipEmpty = false,
|
|
76
76
|
// skip full means file with 100% coverage won't appear in coverage reports (json and html)
|
|
77
77
|
coverageReportSkipFull = false,
|
|
78
|
+
coverageReportTextLog = true,
|
|
79
|
+
coverageReportJsonFile = process.env.CI ? null : "./.coverage/coverage.json",
|
|
80
|
+
coverageReportHtmlDirectory = process.env.CI ? "./.coverage/" : null,
|
|
78
81
|
|
|
79
82
|
sourcemaps = "inline",
|
|
80
83
|
plugins = [],
|
|
@@ -93,7 +96,7 @@ export const executeTestPlan = async ({
|
|
|
93
96
|
if (typeof testPlan !== "object") {
|
|
94
97
|
throw new Error(`testPlan must be an object, got ${testPlan}`)
|
|
95
98
|
}
|
|
96
|
-
if (
|
|
99
|
+
if (coverageEnabled) {
|
|
97
100
|
if (typeof coverageConfig !== "object") {
|
|
98
101
|
throw new TypeError(
|
|
99
102
|
`coverageConfig must be an object, got ${coverageConfig}`,
|
|
@@ -138,6 +141,8 @@ export const executeTestPlan = async ({
|
|
|
138
141
|
logger,
|
|
139
142
|
logLevel,
|
|
140
143
|
logSummary,
|
|
144
|
+
logRuntime,
|
|
145
|
+
logEachDuration,
|
|
141
146
|
logTimeUsage,
|
|
142
147
|
logMemoryHeapUsage,
|
|
143
148
|
logFileRelativeUrl,
|
|
@@ -152,10 +157,11 @@ export const executeTestPlan = async ({
|
|
|
152
157
|
cooldownBetweenExecutions,
|
|
153
158
|
gcBetweenExecutions,
|
|
154
159
|
|
|
155
|
-
|
|
160
|
+
coverageEnabled,
|
|
156
161
|
coverageConfig,
|
|
157
162
|
coverageIncludeMissing,
|
|
158
|
-
|
|
163
|
+
coverageMethodForBrowsers,
|
|
164
|
+
coverageMethodForNodeJs,
|
|
159
165
|
coverageV8ConflictWarning,
|
|
160
166
|
coverageTempDirectoryRelativeUrl,
|
|
161
167
|
|
|
@@ -185,7 +191,7 @@ export const executeTestPlan = async ({
|
|
|
185
191
|
// keep this one first because it does ensureEmptyDirectory
|
|
186
192
|
// and in case coverage json file gets written in the same directory
|
|
187
193
|
// it must be done before
|
|
188
|
-
if (
|
|
194
|
+
if (coverageEnabled && coverageReportHtmlDirectory) {
|
|
189
195
|
const coverageHtmlDirectoryUrl = resolveDirectoryUrl(
|
|
190
196
|
coverageReportHtmlDirectory,
|
|
191
197
|
rootDirectoryUrl,
|
|
@@ -212,7 +218,7 @@ export const executeTestPlan = async ({
|
|
|
212
218
|
}),
|
|
213
219
|
)
|
|
214
220
|
}
|
|
215
|
-
if (
|
|
221
|
+
if (coverageEnabled && coverageReportJsonFile) {
|
|
216
222
|
const coverageJsonFileUrl = new URL(
|
|
217
223
|
coverageReportJsonFile,
|
|
218
224
|
rootDirectoryUrl,
|
|
@@ -225,7 +231,7 @@ export const executeTestPlan = async ({
|
|
|
225
231
|
}),
|
|
226
232
|
)
|
|
227
233
|
}
|
|
228
|
-
if (
|
|
234
|
+
if (coverageEnabled && coverageReportTextLog) {
|
|
229
235
|
promises.push(
|
|
230
236
|
generateCoverageTextLog(result.planCoverage, {
|
|
231
237
|
coverageReportSkipEmpty,
|
|
@@ -19,7 +19,14 @@ export const createExecutionLog = (
|
|
|
19
19
|
startMs,
|
|
20
20
|
endMs,
|
|
21
21
|
},
|
|
22
|
-
{
|
|
22
|
+
{
|
|
23
|
+
completedExecutionLogAbbreviation,
|
|
24
|
+
counters,
|
|
25
|
+
logRuntime,
|
|
26
|
+
logEachDuration,
|
|
27
|
+
timeEllapsed,
|
|
28
|
+
memoryHeap,
|
|
29
|
+
},
|
|
23
30
|
) => {
|
|
24
31
|
const { status } = executionResult
|
|
25
32
|
const descriptionFormatter = descriptionFormatters[status]
|
|
@@ -43,11 +50,15 @@ export const createExecutionLog = (
|
|
|
43
50
|
label: `${description}${summary}`,
|
|
44
51
|
details: {
|
|
45
52
|
file: fileRelativeUrl,
|
|
46
|
-
runtime: `${runtimeName}/${runtimeVersion}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
...(logRuntime ? { runtime: `${runtimeName}/${runtimeVersion}` } : {}),
|
|
54
|
+
...(logEachDuration
|
|
55
|
+
? {
|
|
56
|
+
duration:
|
|
57
|
+
status === "executing"
|
|
58
|
+
? msAsEllapsedTime(Date.now() - startMs)
|
|
59
|
+
: msAsDuration(endMs - startMs),
|
|
60
|
+
}
|
|
61
|
+
: {}),
|
|
51
62
|
...(error ? { error: error.stack || error.message || error } : {}),
|
|
52
63
|
},
|
|
53
64
|
consoleOutput,
|
|
@@ -203,18 +214,84 @@ const descriptionFormatters = {
|
|
|
203
214
|
}
|
|
204
215
|
|
|
205
216
|
const formatConsoleCalls = (consoleCalls) => {
|
|
206
|
-
|
|
207
|
-
return `${previous}${text}`
|
|
208
|
-
}, "")
|
|
209
|
-
const consoleOutputTrimmed = consoleOutput.trim()
|
|
210
|
-
if (consoleOutputTrimmed === "") {
|
|
217
|
+
if (consoleCalls.length === 0) {
|
|
211
218
|
return ""
|
|
212
219
|
}
|
|
213
|
-
|
|
214
|
-
|
|
220
|
+
|
|
221
|
+
const repartition = {
|
|
222
|
+
debug: 0,
|
|
223
|
+
info: 0,
|
|
224
|
+
warning: 0,
|
|
225
|
+
error: 0,
|
|
226
|
+
log: 0,
|
|
227
|
+
}
|
|
228
|
+
let consoleOutput = ``
|
|
229
|
+
consoleCalls.forEach((consoleCall) => {
|
|
230
|
+
repartition[consoleCall.type]++
|
|
231
|
+
const text = consoleCall.text
|
|
232
|
+
const textFormatted = prefixFirstAndIndentRemainingLines({
|
|
233
|
+
prefix: CONSOLE_ICONS[consoleCall.type],
|
|
234
|
+
text,
|
|
235
|
+
trimLastLine: consoleCall === consoleCalls[consoleCalls.length - 1],
|
|
236
|
+
})
|
|
237
|
+
consoleOutput += textFormatted
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
return `${ANSI.color(
|
|
241
|
+
`-------- ${formatConsoleSummary(repartition)} --------`,
|
|
242
|
+
ANSI.GREY,
|
|
243
|
+
)}
|
|
244
|
+
${consoleOutput}
|
|
215
245
|
${ANSI.color(`-------------------------`, ANSI.GREY)}`
|
|
216
246
|
}
|
|
217
247
|
|
|
248
|
+
const CONSOLE_ICONS = {
|
|
249
|
+
debug: UNICODE.DEBUG,
|
|
250
|
+
info: UNICODE.INFO,
|
|
251
|
+
warning: UNICODE.WARNING,
|
|
252
|
+
error: UNICODE.FAILURE,
|
|
253
|
+
log: " ",
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const formatConsoleSummary = (repartition) => {
|
|
257
|
+
const { debug, info, warning, error } = repartition
|
|
258
|
+
const parts = []
|
|
259
|
+
if (error) {
|
|
260
|
+
parts.push(`${CONSOLE_ICONS.error} ${error}`)
|
|
261
|
+
}
|
|
262
|
+
if (warning) {
|
|
263
|
+
parts.push(`${CONSOLE_ICONS.warning} ${warning}`)
|
|
264
|
+
}
|
|
265
|
+
if (info) {
|
|
266
|
+
parts.push(`${CONSOLE_ICONS.info} ${info}`)
|
|
267
|
+
}
|
|
268
|
+
if (debug) {
|
|
269
|
+
parts.push(`${CONSOLE_ICONS.debug} ${debug}`)
|
|
270
|
+
}
|
|
271
|
+
if (parts.length === 0) {
|
|
272
|
+
return `console`
|
|
273
|
+
}
|
|
274
|
+
return `console (${parts.join(" ")})`
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const prefixFirstAndIndentRemainingLines = ({ prefix, text, trimLastLine }) => {
|
|
278
|
+
const lines = text.split(/\r?\n/)
|
|
279
|
+
const firstLine = lines.shift()
|
|
280
|
+
let result = `${prefix} ${firstLine}`
|
|
281
|
+
let i = 0
|
|
282
|
+
const indentation = ` `
|
|
283
|
+
while (i < lines.length) {
|
|
284
|
+
const line = lines[i].trim()
|
|
285
|
+
i++
|
|
286
|
+
result += line.length
|
|
287
|
+
? `\n${indentation}${line}`
|
|
288
|
+
: trimLastLine && i === lines.length
|
|
289
|
+
? ""
|
|
290
|
+
: `\n`
|
|
291
|
+
}
|
|
292
|
+
return result
|
|
293
|
+
}
|
|
294
|
+
|
|
218
295
|
const formatExecution = ({ label, details = {}, consoleOutput }) => {
|
|
219
296
|
let message = ``
|
|
220
297
|
message += label
|