@jsenv/core 23.4.0 → 23.5.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/{LICENSE → license} +0 -0
- package/package.json +6 -7
- package/readme.md +3 -3
- package/src/execute.js +0 -6
- package/src/executeTestPlan.js +3 -2
- package/src/internal/compiling/createCompiledFileService.js +13 -1
- package/src/internal/executing/coverage/reportToCoverage.js +97 -83
- package/src/internal/executing/{coverage_empty → coverage_missing}/createEmptyCoverage.js +0 -0
- package/src/internal/executing/{coverage_empty → coverage_missing}/list_files_not_covered.js +2 -2
- package/src/internal/executing/coverage_missing/missing_coverage.js +46 -0
- package/src/internal/executing/{coverage_empty → coverage_missing}/relativeUrlToEmptyCoverage.js +11 -7
- package/src/internal/executing/coverage_utils/v8_coverage_from_directory.js +44 -26
- package/src/internal/executing/coverage_utils/v8_coverage_to_istanbul.js +48 -38
- package/src/internal/executing/createSummaryLog.js +2 -8
- package/src/internal/executing/executeConcurrently.js +119 -93
- package/src/internal/executing/executePlan.js +3 -3
- package/src/internal/executing/executionLogs.js +66 -37
- package/src/internal/executing/execution_colors.js +2 -1
- package/src/internal/executing/launchAndExecute.js +16 -34
- package/src/internal/logs/msAsDuration.js +4 -3
package/{LICENSE → license}
RENAMED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/core",
|
|
3
|
-
"version": "23.
|
|
3
|
+
"version": "23.5.1",
|
|
4
4
|
"description": "Tool to develop, test and build js projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
"node": ">=14.9.0"
|
|
12
12
|
},
|
|
13
13
|
"publishConfig": {
|
|
14
|
-
"access": "public"
|
|
15
|
-
"registry": "https://registry.npmjs.org"
|
|
14
|
+
"access": "public"
|
|
16
15
|
},
|
|
17
16
|
"type": "module",
|
|
18
17
|
"exports": {
|
|
@@ -63,12 +62,12 @@
|
|
|
63
62
|
"@babel/plugin-syntax-numeric-separator": "7.10.4",
|
|
64
63
|
"@babel/plugin-transform-modules-systemjs": "7.15.4",
|
|
65
64
|
"@c88/v8-coverage": "0.1.1",
|
|
66
|
-
"@jsenv/abort": "4.
|
|
67
|
-
"@jsenv/filesystem": "2.5.
|
|
65
|
+
"@jsenv/abort": "4.1.1",
|
|
66
|
+
"@jsenv/filesystem": "2.5.1",
|
|
68
67
|
"@jsenv/importmap": "1.1.0",
|
|
69
|
-
"@jsenv/log": "1.
|
|
68
|
+
"@jsenv/log": "1.4.0",
|
|
70
69
|
"@jsenv/logger": "4.0.1",
|
|
71
|
-
"@jsenv/server": "10.0.
|
|
70
|
+
"@jsenv/server": "10.0.2",
|
|
72
71
|
"@jsenv/uneval": "1.6.0",
|
|
73
72
|
"@jsenv/workers": "1.2.0",
|
|
74
73
|
"@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.
|
|
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.
|
|
107
|
+
duration: 2.4 seconds
|
|
108
108
|
|
|
109
109
|
-------------- summary -----------------
|
|
110
110
|
2 executions: all completed
|
|
111
|
-
total duration: 3.
|
|
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,
|
package/src/executeTestPlan.js
CHANGED
|
@@ -33,13 +33,13 @@ 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") ||
|
|
@@ -144,10 +144,10 @@ 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,
|
|
@@ -225,6 +225,7 @@ export const executeTestPlan = async ({
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
return {
|
|
228
|
+
testPlanAborted: result.aborted,
|
|
228
229
|
testPlanSummary: result.planSummary,
|
|
229
230
|
testPlanReport: result.planReport,
|
|
230
231
|
testPlanCoverage: planCoverage,
|
|
@@ -71,7 +71,10 @@ export const createCompiledFileService = ({
|
|
|
71
71
|
|
|
72
72
|
return (request) => {
|
|
73
73
|
const { origin, ressource } = request
|
|
74
|
-
|
|
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
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFile } from "@jsenv/filesystem"
|
|
2
|
+
import { Abort } from "@jsenv/abort"
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
visitNodeV8Directory,
|
|
@@ -9,13 +10,12 @@ import { composeTwoFileByFileIstanbulCoverages } from "../coverage_utils/istanbu
|
|
|
9
10
|
import { v8CoverageToIstanbul } from "../coverage_utils/v8_coverage_to_istanbul.js"
|
|
10
11
|
import { composeV8AndIstanbul } from "../coverage_utils/v8_and_istanbul.js"
|
|
11
12
|
import { normalizeFileByFileCoveragePaths } from "../coverage_utils/file_by_file_coverage.js"
|
|
12
|
-
import {
|
|
13
|
-
import { relativeUrlToEmptyCoverage } from "../coverage_empty/relativeUrlToEmptyCoverage.js"
|
|
13
|
+
import { getMissingFileByFileCoverage } from "../coverage_missing/missing_coverage.js"
|
|
14
14
|
|
|
15
15
|
export const reportToCoverage = async (
|
|
16
16
|
report,
|
|
17
17
|
{
|
|
18
|
-
|
|
18
|
+
signal,
|
|
19
19
|
logger,
|
|
20
20
|
projectDirectoryUrl,
|
|
21
21
|
babelPluginMap,
|
|
@@ -26,70 +26,39 @@ export const reportToCoverage = async (
|
|
|
26
26
|
coverageV8ConflictWarning,
|
|
27
27
|
},
|
|
28
28
|
) => {
|
|
29
|
-
let v8Coverage
|
|
30
|
-
let fileByFileIstanbulCoverage
|
|
31
|
-
|
|
32
29
|
// collect v8 and istanbul coverage from executions
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// that were suppose to be coverage but were not.
|
|
62
|
-
if (status === "completed") {
|
|
63
|
-
logger.debug(
|
|
64
|
-
`No execution.coverageFileUrl from execution named "${executionName}" of ${file}`,
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const executionCoverage = await readFile(coverageFileUrl, {
|
|
71
|
-
as: "json",
|
|
72
|
-
})
|
|
73
|
-
if (isV8Coverage(executionCoverage)) {
|
|
74
|
-
v8Coverage = v8Coverage
|
|
75
|
-
? composeTwoV8Coverages(v8Coverage, executionCoverage)
|
|
76
|
-
: executionCoverage
|
|
77
|
-
} else {
|
|
78
|
-
fileByFileIstanbulCoverage = fileByFileIstanbulCoverage
|
|
79
|
-
? composeTwoFileByFileIstanbulCoverages(
|
|
80
|
-
fileByFileIstanbulCoverage,
|
|
81
|
-
executionCoverage,
|
|
82
|
-
)
|
|
83
|
-
: executionCoverage
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
Promise.resolve(),
|
|
87
|
-
)
|
|
88
|
-
}, Promise.resolve())
|
|
30
|
+
let { v8Coverage, fileByFileIstanbulCoverage } = await getCoverageFromReport({
|
|
31
|
+
signal,
|
|
32
|
+
report,
|
|
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
|
+
}
|
|
56
|
+
},
|
|
57
|
+
})
|
|
89
58
|
|
|
90
59
|
if (!coverageForceIstanbul && process.env.NODE_V8_COVERAGE) {
|
|
91
60
|
await visitNodeV8Directory({
|
|
92
|
-
signal
|
|
61
|
+
signal,
|
|
93
62
|
NODE_V8_COVERAGE: process.env.NODE_V8_COVERAGE,
|
|
94
63
|
onV8Coverage: (nodeV8Coverage) => {
|
|
95
64
|
const nodeV8CoverageLight = filterV8Coverage(nodeV8Coverage, {
|
|
@@ -105,7 +74,9 @@ export const reportToCoverage = async (
|
|
|
105
74
|
// try to merge v8 with istanbul, if any
|
|
106
75
|
let fileByFileCoverage
|
|
107
76
|
if (v8Coverage) {
|
|
108
|
-
let v8FileByFileCoverage = await v8CoverageToIstanbul(v8Coverage
|
|
77
|
+
let v8FileByFileCoverage = await v8CoverageToIstanbul(v8Coverage, {
|
|
78
|
+
signal,
|
|
79
|
+
})
|
|
109
80
|
|
|
110
81
|
v8FileByFileCoverage = normalizeFileByFileCoveragePaths(
|
|
111
82
|
v8FileByFileCoverage,
|
|
@@ -140,34 +111,77 @@ export const reportToCoverage = async (
|
|
|
140
111
|
|
|
141
112
|
// now add coverage for file not covered
|
|
142
113
|
if (coverageIncludeMissing) {
|
|
143
|
-
const
|
|
144
|
-
|
|
114
|
+
const missingFileByFileCoverage = await getMissingFileByFileCoverage({
|
|
115
|
+
signal,
|
|
145
116
|
projectDirectoryUrl,
|
|
146
117
|
coverageConfig,
|
|
118
|
+
fileByFileCoverage,
|
|
119
|
+
babelPluginMap,
|
|
147
120
|
})
|
|
121
|
+
Object.assign(fileByFileCoverage, missingFileByFileCoverage)
|
|
122
|
+
}
|
|
148
123
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
124
|
+
return fileByFileCoverage
|
|
125
|
+
}
|
|
126
|
+
|
|
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
|
+
}
|
|
155
157
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
+
}
|
|
163
173
|
},
|
|
174
|
+
Promise.resolve(),
|
|
164
175
|
)
|
|
165
|
-
fileByFileCoverage[`./${relativeUrlMissing}`] = emptyCoverage
|
|
166
|
-
return emptyCoverage
|
|
167
176
|
}, Promise.resolve())
|
|
168
|
-
}
|
|
169
177
|
|
|
170
|
-
|
|
178
|
+
return {
|
|
179
|
+
v8Coverage,
|
|
180
|
+
fileByFileIstanbulCoverage,
|
|
181
|
+
}
|
|
182
|
+
} finally {
|
|
183
|
+
await operation.end()
|
|
184
|
+
}
|
|
171
185
|
}
|
|
172
186
|
|
|
173
187
|
const isV8Coverage = (coverage) => Boolean(coverage.result)
|
|
File without changes
|
package/src/internal/executing/{coverage_empty → coverage_missing}/list_files_not_covered.js
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { collectFiles } from "@jsenv/filesystem"
|
|
2
2
|
|
|
3
3
|
export const listRelativeFileUrlToCover = async ({
|
|
4
|
-
|
|
4
|
+
signal,
|
|
5
5
|
projectDirectoryUrl,
|
|
6
6
|
coverageConfig,
|
|
7
7
|
}) => {
|
|
@@ -10,7 +10,7 @@ export const listRelativeFileUrlToCover = async ({
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
const matchingFileResultArray = await collectFiles({
|
|
13
|
-
signal
|
|
13
|
+
signal,
|
|
14
14
|
directoryUrl: projectDirectoryUrl,
|
|
15
15
|
structuredMetaMap: structuredMetaMapForCoverage,
|
|
16
16
|
predicate: ({ cover }) => cover,
|
|
@@ -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
|
+
}
|
package/src/internal/executing/{coverage_empty → coverage_missing}/relativeUrlToEmptyCoverage.js
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveUrl, urlToFileSystemPath, readFile } from "@jsenv/filesystem"
|
|
2
|
+
import { Abort } from "@jsenv/abort"
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
babelPluginsFromBabelPluginMap,
|
|
@@ -9,22 +10,23 @@ import { createEmptyCoverage } from "./createEmptyCoverage.js"
|
|
|
9
10
|
|
|
10
11
|
export const relativeUrlToEmptyCoverage = async (
|
|
11
12
|
relativeUrl,
|
|
12
|
-
{
|
|
13
|
+
{ signal, projectDirectoryUrl, babelPluginMap },
|
|
13
14
|
) => {
|
|
14
|
-
const
|
|
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
|
-
|
|
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
|
}
|
|
@@ -5,12 +5,16 @@ import {
|
|
|
5
5
|
resolveUrl,
|
|
6
6
|
} from "@jsenv/filesystem"
|
|
7
7
|
import { createDetailedMessage } from "@jsenv/logger"
|
|
8
|
+
import { Abort } from "@jsenv/abort"
|
|
8
9
|
|
|
9
10
|
export const visitNodeV8Directory = async ({
|
|
10
|
-
|
|
11
|
+
signal,
|
|
11
12
|
NODE_V8_COVERAGE,
|
|
12
13
|
onV8Coverage,
|
|
13
14
|
}) => {
|
|
15
|
+
const operation = Abort.startOperation()
|
|
16
|
+
operation.addAbortSignal(signal)
|
|
17
|
+
|
|
14
18
|
const tryReadDirectory = async () => {
|
|
15
19
|
const dirContent = await readDirectory(NODE_V8_COVERAGE)
|
|
16
20
|
if (dirContent.length > 0) {
|
|
@@ -19,33 +23,47 @@ export const visitNodeV8Directory = async ({
|
|
|
19
23
|
console.warn(`v8 coverage directory is empty at ${NODE_V8_COVERAGE}`)
|
|
20
24
|
return dirContent
|
|
21
25
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
+
}
|
|
41
57
|
}
|
|
42
|
-
}
|
|
43
58
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
59
|
+
const fileContent = await tryReadJsonFile()
|
|
60
|
+
if (fileContent) {
|
|
61
|
+
onV8Coverage(fileContent)
|
|
62
|
+
}
|
|
63
|
+
}, Promise.resolve())
|
|
64
|
+
} finally {
|
|
65
|
+
await operation.end()
|
|
66
|
+
}
|
|
49
67
|
}
|
|
50
68
|
|
|
51
69
|
export const filterV8Coverage = (v8Coverage, { coverageIgnorePredicate }) => {
|
|
@@ -1,53 +1,63 @@
|
|
|
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
6
|
import { composeTwoFileByFileIstanbulCoverages } from "./istanbul_coverage_composition.js"
|
|
6
7
|
|
|
7
|
-
export const v8CoverageToIstanbul = async (v8Coverage) => {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
let istanbulCoverageComposed = null
|
|
11
|
-
await v8Coverage.result.reduce(async (previous, fileV8Coverage) => {
|
|
12
|
-
await previous
|
|
8
|
+
export const v8CoverageToIstanbul = async (v8Coverage, { signal }) => {
|
|
9
|
+
const operation = Abort.startOperation()
|
|
10
|
+
operation.addAbortSignal(signal)
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
sources = { source }
|
|
19
|
-
}
|
|
20
|
-
// when v8 coverage comes from Node.js, the source can be read from sourcemapCache
|
|
21
|
-
else if (sourcemapCache) {
|
|
22
|
-
sources = sourcesFromSourceMapCache(fileV8Coverage.url, sourcemapCache)
|
|
23
|
-
}
|
|
24
|
-
const path = urlToFileSystemPath(fileV8Coverage.url)
|
|
12
|
+
try {
|
|
13
|
+
const v8ToIstanbul = require("v8-to-istanbul")
|
|
14
|
+
const sourcemapCache = v8Coverage["source-map-cache"]
|
|
15
|
+
let istanbulCoverageComposed = null
|
|
25
16
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// https://github.com/istanbuljs/v8-to-istanbul/blob/2b54bc97c5edf8a37b39a171ec29134ba9bfd532/lib/v8-to-istanbul.js#L27
|
|
30
|
-
undefined,
|
|
31
|
-
sources,
|
|
32
|
-
)
|
|
33
|
-
await converter.load()
|
|
17
|
+
await v8Coverage.result.reduce(async (previous, fileV8Coverage) => {
|
|
18
|
+
operation.throwIfAborted()
|
|
19
|
+
await previous
|
|
34
20
|
|
|
35
|
-
|
|
36
|
-
|
|
21
|
+
const { source } = fileV8Coverage
|
|
22
|
+
let sources
|
|
23
|
+
// when v8 coverage comes from playwright (chromium) v8Coverage.source is set
|
|
24
|
+
if (typeof source === "string") {
|
|
25
|
+
sources = { source }
|
|
26
|
+
}
|
|
27
|
+
// when v8 coverage comes from Node.js, the source can be read from sourcemapCache
|
|
28
|
+
else if (sourcemapCache) {
|
|
29
|
+
sources = sourcesFromSourceMapCache(fileV8Coverage.url, sourcemapCache)
|
|
30
|
+
}
|
|
31
|
+
const path = urlToFileSystemPath(fileV8Coverage.url)
|
|
37
32
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
33
|
+
const converter = v8ToIstanbul(
|
|
34
|
+
path,
|
|
35
|
+
// wrapperLength is undefined we don't need it
|
|
36
|
+
// https://github.com/istanbuljs/v8-to-istanbul/blob/2b54bc97c5edf8a37b39a171ec29134ba9bfd532/lib/v8-to-istanbul.js#L27
|
|
37
|
+
undefined,
|
|
38
|
+
sources,
|
|
39
|
+
)
|
|
40
|
+
await converter.load()
|
|
45
41
|
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
converter.applyCoverage(fileV8Coverage.functions)
|
|
43
|
+
const istanbulCoverage = converter.toIstanbul()
|
|
44
|
+
|
|
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()
|
|
48
60
|
}
|
|
49
|
-
istanbulCoverageComposed = markAsConvertedFromV8(istanbulCoverageComposed)
|
|
50
|
-
return istanbulCoverageComposed
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
const markAsConvertedFromV8 = (fileByFileCoverage) => {
|
|
@@ -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)}
|
|
8
|
+
${createSummaryMessage(summary)}
|
|
9
|
+
total duration: ${msAsDuration(summary.duration)}
|
|
9
10
|
----------------------------------------
|
|
10
11
|
`
|
|
11
12
|
|
|
@@ -114,10 +115,3 @@ const createMixedDetails = ({
|
|
|
114
115
|
|
|
115
116
|
return `${parts.join(", ")}`
|
|
116
117
|
}
|
|
117
|
-
|
|
118
|
-
const createTotalDurationMessage = ({ startMs, endMs }) => {
|
|
119
|
-
if (!endMs) return ""
|
|
120
|
-
|
|
121
|
-
return `
|
|
122
|
-
total duration: ${msAsDuration(endMs - startMs)}`
|
|
123
|
-
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync } from "node:fs"
|
|
2
2
|
import wrapAnsi from "wrap-ansi"
|
|
3
3
|
import cuid from "cuid"
|
|
4
|
-
import { loggerToLevels
|
|
4
|
+
import { loggerToLevels } from "@jsenv/logger"
|
|
5
|
+
import { createLog, startSpinner } from "@jsenv/log"
|
|
5
6
|
import {
|
|
6
7
|
urlToFileSystemPath,
|
|
7
8
|
resolveUrl,
|
|
@@ -10,12 +11,11 @@ import {
|
|
|
10
11
|
normalizeStructuredMetaMap,
|
|
11
12
|
urlToMeta,
|
|
12
13
|
} from "@jsenv/filesystem"
|
|
13
|
-
import { createLog } from "@jsenv/log"
|
|
14
14
|
import { Abort } from "@jsenv/abort"
|
|
15
15
|
|
|
16
16
|
import { launchAndExecute } from "../executing/launchAndExecute.js"
|
|
17
17
|
import { reportToCoverage } from "./coverage/reportToCoverage.js"
|
|
18
|
-
import {
|
|
18
|
+
import { formatExecuting, formatExecutionResult } from "./executionLogs.js"
|
|
19
19
|
import { createSummaryLog } from "./createSummaryLog.js"
|
|
20
20
|
|
|
21
21
|
export const executeConcurrently = async (
|
|
@@ -33,10 +33,10 @@ export const executeConcurrently = async (
|
|
|
33
33
|
babelPluginMap,
|
|
34
34
|
|
|
35
35
|
defaultMsAllocatedPerExecution = 30000,
|
|
36
|
+
cooldownBetweenExecutions = 0,
|
|
36
37
|
maxExecutionsInParallel = 1,
|
|
37
38
|
completedExecutionLogMerging,
|
|
38
39
|
completedExecutionLogAbbreviation,
|
|
39
|
-
measureGlobalDuration = true,
|
|
40
40
|
|
|
41
41
|
coverage,
|
|
42
42
|
coverageConfig,
|
|
@@ -46,21 +46,21 @@ export const executeConcurrently = async (
|
|
|
46
46
|
coverageTempDirectoryRelativeUrl,
|
|
47
47
|
runtimeSupport,
|
|
48
48
|
|
|
49
|
-
mainFileNotFoundCallback = ({ fileRelativeUrl }) => {
|
|
50
|
-
logger.error(
|
|
51
|
-
new Error(
|
|
52
|
-
createDetailedMessage(`an execution main file does not exists.`, {
|
|
53
|
-
["file relative path"]: fileRelativeUrl,
|
|
54
|
-
}),
|
|
55
|
-
),
|
|
56
|
-
)
|
|
57
|
-
},
|
|
58
49
|
beforeExecutionCallback = () => {},
|
|
59
50
|
afterExecutionCallback = () => {},
|
|
60
51
|
|
|
61
52
|
logSummary,
|
|
62
53
|
},
|
|
63
54
|
) => {
|
|
55
|
+
if (completedExecutionLogMerging && !process.stdout.isTTY) {
|
|
56
|
+
completedExecutionLogMerging = false
|
|
57
|
+
logger.debug(
|
|
58
|
+
`Force completedExecutionLogMerging to false because process.stdout.isTTY is false`,
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
const executionLogsEnabled = loggerToLevels(logger).info
|
|
62
|
+
const executionSpinner = executionLogsEnabled && process.stdout.isTTY
|
|
63
|
+
|
|
64
64
|
const startMs = Date.now()
|
|
65
65
|
|
|
66
66
|
const report = {}
|
|
@@ -113,7 +113,7 @@ export const executeConcurrently = async (
|
|
|
113
113
|
|
|
114
114
|
try {
|
|
115
115
|
value.coverage = await reportToCoverage(value.report, {
|
|
116
|
-
multipleExecutionsOperation,
|
|
116
|
+
signal: multipleExecutionsOperation.signal,
|
|
117
117
|
logger,
|
|
118
118
|
projectDirectoryUrl,
|
|
119
119
|
babelPluginMap,
|
|
@@ -133,8 +133,7 @@ export const executeConcurrently = async (
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
let
|
|
137
|
-
let previousExecutionLog
|
|
136
|
+
let executionLog = createLog({ newLine: "around" })
|
|
138
137
|
let abortedCount = 0
|
|
139
138
|
let timedoutCount = 0
|
|
140
139
|
let erroredCount = 0
|
|
@@ -142,18 +141,19 @@ export const executeConcurrently = async (
|
|
|
142
141
|
const executionsDone = await executeInParallel({
|
|
143
142
|
multipleExecutionsOperation,
|
|
144
143
|
maxExecutionsInParallel,
|
|
144
|
+
cooldownBetweenExecutions,
|
|
145
145
|
executionSteps,
|
|
146
146
|
start: async (paramsFromStep) => {
|
|
147
147
|
const executionIndex = executionSteps.indexOf(paramsFromStep)
|
|
148
|
-
const { executionName, fileRelativeUrl } = paramsFromStep
|
|
148
|
+
const { executionName, fileRelativeUrl, runtime } = paramsFromStep
|
|
149
|
+
const runtimeName = runtime.name
|
|
150
|
+
const runtimeVersion = runtime.version
|
|
151
|
+
|
|
149
152
|
const executionParams = {
|
|
150
153
|
// the params below can be overriden by executionDefaultParams
|
|
151
154
|
measurePerformance: false,
|
|
152
155
|
collectPerformance: false,
|
|
153
|
-
measureDuration: true,
|
|
154
156
|
captureConsole: true,
|
|
155
|
-
collectRuntimeName: true,
|
|
156
|
-
collectRuntimeVersion: true,
|
|
157
157
|
// stopAfterExecute: true to ensure runtime is stopped once executed
|
|
158
158
|
// because we have what we wants: execution is completed and
|
|
159
159
|
// we have associated coverage and capturedConsole
|
|
@@ -163,6 +163,7 @@ export const executeConcurrently = async (
|
|
|
163
163
|
stopAfterExecuteReason: "execution-done",
|
|
164
164
|
allocatedMs: defaultMsAllocatedPerExecution,
|
|
165
165
|
...paramsFromStep,
|
|
166
|
+
runtime,
|
|
166
167
|
// mirrorConsole: false because file will be executed in parallel
|
|
167
168
|
// so log would be a mess to read
|
|
168
169
|
mirrorConsole: false,
|
|
@@ -170,48 +171,69 @@ export const executeConcurrently = async (
|
|
|
170
171
|
|
|
171
172
|
const beforeExecutionInfo = {
|
|
172
173
|
fileRelativeUrl,
|
|
174
|
+
runtimeName,
|
|
175
|
+
runtimeVersion,
|
|
173
176
|
executionIndex,
|
|
174
177
|
executionParams,
|
|
175
178
|
}
|
|
176
179
|
|
|
180
|
+
let spinner
|
|
181
|
+
if (executionSpinner) {
|
|
182
|
+
spinner = startSpinner({
|
|
183
|
+
log: executionLog,
|
|
184
|
+
text: formatExecuting(beforeExecutionInfo, {
|
|
185
|
+
executionCount,
|
|
186
|
+
abortedCount,
|
|
187
|
+
timedoutCount,
|
|
188
|
+
erroredCount,
|
|
189
|
+
completedCount,
|
|
190
|
+
}),
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
beforeExecutionCallback(beforeExecutionInfo)
|
|
194
|
+
|
|
177
195
|
const filePath = urlToFileSystemPath(
|
|
178
196
|
`${projectDirectoryUrl}${fileRelativeUrl}`,
|
|
179
197
|
)
|
|
180
|
-
|
|
181
|
-
if (
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
beforeExecutionCallback(beforeExecutionInfo)
|
|
198
|
+
let executionResult
|
|
199
|
+
if (existsSync(filePath)) {
|
|
200
|
+
executionResult = await launchAndExecute({
|
|
201
|
+
signal: multipleExecutionsOperation.signal,
|
|
202
|
+
launchAndExecuteLogLevel,
|
|
187
203
|
|
|
188
|
-
|
|
189
|
-
// et c'est bien, on veut le gérer, si tous les suivants sont aborted
|
|
190
|
-
// on le gere en dehors de cette boucle
|
|
191
|
-
const executionResult = await launchAndExecute({
|
|
192
|
-
signal: multipleExecutionsOperation.signal,
|
|
193
|
-
launchAndExecuteLogLevel,
|
|
194
|
-
|
|
195
|
-
...executionParams,
|
|
196
|
-
collectCoverage: coverage,
|
|
197
|
-
coverageTempDirectoryUrl,
|
|
198
|
-
runtimeParams: {
|
|
199
|
-
projectDirectoryUrl,
|
|
200
|
-
compileServerOrigin,
|
|
201
|
-
outDirectoryRelativeUrl,
|
|
204
|
+
...executionParams,
|
|
202
205
|
collectCoverage: coverage,
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
206
|
+
coverageTempDirectoryUrl,
|
|
207
|
+
runtimeParams: {
|
|
208
|
+
projectDirectoryUrl,
|
|
209
|
+
compileServerOrigin,
|
|
210
|
+
outDirectoryRelativeUrl,
|
|
211
|
+
collectCoverage: coverage,
|
|
212
|
+
coverageIgnorePredicate,
|
|
213
|
+
coverageForceIstanbul,
|
|
214
|
+
...executionParams.runtimeParams,
|
|
215
|
+
},
|
|
216
|
+
executeParams: {
|
|
217
|
+
fileRelativeUrl,
|
|
218
|
+
...executionParams.executeParams,
|
|
219
|
+
},
|
|
220
|
+
coverageV8ConflictWarning,
|
|
221
|
+
})
|
|
222
|
+
} else {
|
|
223
|
+
executionResult = {
|
|
224
|
+
status: "errored",
|
|
225
|
+
error: new Error(
|
|
226
|
+
`No file at ${fileRelativeUrl} for execution "${executionName}"`,
|
|
227
|
+
),
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (fileRelativeUrl in report === false) {
|
|
231
|
+
report[fileRelativeUrl] = {}
|
|
232
|
+
}
|
|
233
|
+
report[fileRelativeUrl][executionName] = executionResult
|
|
213
234
|
const afterExecutionInfo = {
|
|
214
235
|
...beforeExecutionInfo,
|
|
236
|
+
endMs: Date.now(),
|
|
215
237
|
executionResult,
|
|
216
238
|
}
|
|
217
239
|
afterExecutionCallback(afterExecutionInfo)
|
|
@@ -226,8 +248,8 @@ export const executeConcurrently = async (
|
|
|
226
248
|
completedCount++
|
|
227
249
|
}
|
|
228
250
|
|
|
229
|
-
if (
|
|
230
|
-
let log =
|
|
251
|
+
if (executionLogsEnabled) {
|
|
252
|
+
let log = formatExecutionResult(afterExecutionInfo, {
|
|
231
253
|
completedExecutionLogAbbreviation,
|
|
232
254
|
executionCount,
|
|
233
255
|
abortedCount,
|
|
@@ -242,31 +264,21 @@ export const executeConcurrently = async (
|
|
|
242
264
|
wordWrap: false,
|
|
243
265
|
})
|
|
244
266
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
previousExecutionLog.write(log)
|
|
267
|
+
// replace spinner with this execution result
|
|
268
|
+
if (spinner) spinner.stop()
|
|
269
|
+
executionLog.write(log)
|
|
270
|
+
|
|
271
|
+
const canOverwriteLog = canOverwriteLogGetter({
|
|
272
|
+
completedExecutionLogMerging,
|
|
273
|
+
executionResult,
|
|
274
|
+
})
|
|
275
|
+
if (canOverwriteLog) {
|
|
276
|
+
// nothing to do, we reuse the current executionLog object
|
|
256
277
|
} else {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
previousExecutionLog = createLog()
|
|
261
|
-
previousExecutionLog.write(log)
|
|
278
|
+
executionLog.destroy()
|
|
279
|
+
executionLog = createLog({ newLine: "around" })
|
|
262
280
|
}
|
|
263
281
|
}
|
|
264
|
-
|
|
265
|
-
if (fileRelativeUrl in report === false) {
|
|
266
|
-
report[fileRelativeUrl] = {}
|
|
267
|
-
}
|
|
268
|
-
report[fileRelativeUrl][executionName] = executionResult
|
|
269
|
-
previousExecutionResult = executionResult
|
|
270
282
|
},
|
|
271
283
|
})
|
|
272
284
|
|
|
@@ -281,7 +293,7 @@ export const executeConcurrently = async (
|
|
|
281
293
|
executionsDone.length -
|
|
282
294
|
// we substract abortedCount because they are not pushed into executionsDone
|
|
283
295
|
summaryCounts.abortedCount,
|
|
284
|
-
|
|
296
|
+
duration: Date.now() - startMs,
|
|
285
297
|
}
|
|
286
298
|
if (logSummary) {
|
|
287
299
|
logger.info(createSummaryLog(summary))
|
|
@@ -293,11 +305,36 @@ export const executeConcurrently = async (
|
|
|
293
305
|
})
|
|
294
306
|
}
|
|
295
307
|
|
|
308
|
+
const canOverwriteLogGetter = ({
|
|
309
|
+
completedExecutionLogMerging,
|
|
310
|
+
executionResult,
|
|
311
|
+
}) => {
|
|
312
|
+
if (!completedExecutionLogMerging) {
|
|
313
|
+
return false
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (executionResult.status === "aborted") {
|
|
317
|
+
return true
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (executionResult.status !== "completed") {
|
|
321
|
+
return false
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const { consoleCalls = [] } = executionResult
|
|
325
|
+
if (consoleCalls.length > 0) {
|
|
326
|
+
return false
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return true
|
|
330
|
+
}
|
|
331
|
+
|
|
296
332
|
const executeInParallel = async ({
|
|
297
333
|
multipleExecutionsOperation,
|
|
298
334
|
executionSteps,
|
|
299
335
|
start,
|
|
300
336
|
maxExecutionsInParallel = 1,
|
|
337
|
+
cooldownBetweenExecutions,
|
|
301
338
|
}) => {
|
|
302
339
|
const executionResults = []
|
|
303
340
|
let progressionIndex = 0
|
|
@@ -333,6 +370,11 @@ const executeInParallel = async ({
|
|
|
333
370
|
if (!multipleExecutionsOperation.signal.aborted) {
|
|
334
371
|
executionResults[index] = output
|
|
335
372
|
}
|
|
373
|
+
if (cooldownBetweenExecutions) {
|
|
374
|
+
await new Promise((resolve) =>
|
|
375
|
+
setTimeout(resolve, cooldownBetweenExecutions),
|
|
376
|
+
)
|
|
377
|
+
}
|
|
336
378
|
}
|
|
337
379
|
|
|
338
380
|
await nextChunk()
|
|
@@ -340,22 +382,6 @@ const executeInParallel = async ({
|
|
|
340
382
|
return executionResults
|
|
341
383
|
}
|
|
342
384
|
|
|
343
|
-
const pathLeadsToFile = (path) => {
|
|
344
|
-
return new Promise((resolve, reject) => {
|
|
345
|
-
stat(path, (error, stats) => {
|
|
346
|
-
if (error) {
|
|
347
|
-
if (error.code === "ENOENT") {
|
|
348
|
-
resolve(false)
|
|
349
|
-
} else {
|
|
350
|
-
reject(error)
|
|
351
|
-
}
|
|
352
|
-
} else {
|
|
353
|
-
resolve(stats.isFile())
|
|
354
|
-
}
|
|
355
|
-
})
|
|
356
|
-
})
|
|
357
|
-
}
|
|
358
|
-
|
|
359
385
|
const reportToSummary = (report) => {
|
|
360
386
|
const fileNames = Object.keys(report)
|
|
361
387
|
|
|
@@ -25,10 +25,10 @@ export const executePlan = async (
|
|
|
25
25
|
|
|
26
26
|
defaultMsAllocatedPerExecution,
|
|
27
27
|
maxExecutionsInParallel,
|
|
28
|
+
cooldownBetweenExecutions,
|
|
28
29
|
completedExecutionLogMerging,
|
|
29
30
|
completedExecutionLogAbbreviation,
|
|
30
31
|
logSummary,
|
|
31
|
-
measureGlobalDuration,
|
|
32
32
|
|
|
33
33
|
coverage,
|
|
34
34
|
coverageConfig,
|
|
@@ -82,7 +82,6 @@ export const executePlan = async (
|
|
|
82
82
|
SIGINT: true,
|
|
83
83
|
},
|
|
84
84
|
() => {
|
|
85
|
-
logger.info("Aborting execution (SIGINT)")
|
|
86
85
|
abort()
|
|
87
86
|
},
|
|
88
87
|
)
|
|
@@ -148,10 +147,10 @@ export const executePlan = async (
|
|
|
148
147
|
|
|
149
148
|
defaultMsAllocatedPerExecution,
|
|
150
149
|
maxExecutionsInParallel,
|
|
150
|
+
cooldownBetweenExecutions,
|
|
151
151
|
completedExecutionLogMerging,
|
|
152
152
|
completedExecutionLogAbbreviation,
|
|
153
153
|
logSummary,
|
|
154
|
-
measureGlobalDuration,
|
|
155
154
|
|
|
156
155
|
coverage,
|
|
157
156
|
coverageConfig,
|
|
@@ -163,6 +162,7 @@ export const executePlan = async (
|
|
|
163
162
|
})
|
|
164
163
|
|
|
165
164
|
return {
|
|
165
|
+
aborted: multipleExecutionsOperation.signal.aborted,
|
|
166
166
|
planSummary: result.summary,
|
|
167
167
|
planReport: result.report,
|
|
168
168
|
planCoverage: result.coverage,
|
|
@@ -4,8 +4,40 @@ import { msAsDuration } from "../logs/msAsDuration.js"
|
|
|
4
4
|
import { EXECUTION_COLORS } from "./execution_colors.js"
|
|
5
5
|
import { createSummaryDetails } from "./createSummaryLog.js"
|
|
6
6
|
|
|
7
|
-
export const
|
|
8
|
-
{ executionIndex
|
|
7
|
+
export const formatExecuting = (
|
|
8
|
+
{ executionIndex },
|
|
9
|
+
{ executionCount, abortedCount, timedoutCount, erroredCount, completedCount },
|
|
10
|
+
) => {
|
|
11
|
+
const executionNumber = executionIndex + 1
|
|
12
|
+
const description = ANSI.color(
|
|
13
|
+
`executing ${executionNumber} of ${executionCount}`,
|
|
14
|
+
EXECUTION_COLORS.executing,
|
|
15
|
+
)
|
|
16
|
+
const summary =
|
|
17
|
+
executionIndex === 0
|
|
18
|
+
? ""
|
|
19
|
+
: `(${createSummaryDetails({
|
|
20
|
+
executionCount: executionIndex,
|
|
21
|
+
abortedCount,
|
|
22
|
+
timedoutCount,
|
|
23
|
+
erroredCount,
|
|
24
|
+
completedCount,
|
|
25
|
+
})})`
|
|
26
|
+
|
|
27
|
+
return formatExecution({
|
|
28
|
+
label: `${description} ${summary}`,
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const formatExecutionResult = (
|
|
33
|
+
{
|
|
34
|
+
executionIndex,
|
|
35
|
+
fileRelativeUrl,
|
|
36
|
+
runtimeName,
|
|
37
|
+
runtimeVersion,
|
|
38
|
+
executionParams,
|
|
39
|
+
executionResult,
|
|
40
|
+
},
|
|
9
41
|
{
|
|
10
42
|
completedExecutionLogAbbreviation,
|
|
11
43
|
executionCount,
|
|
@@ -24,6 +56,7 @@ export const createExecutionResultLog = (
|
|
|
24
56
|
executionCount,
|
|
25
57
|
allocatedMs,
|
|
26
58
|
})
|
|
59
|
+
|
|
27
60
|
const summary = `(${createSummaryDetails({
|
|
28
61
|
executionCount: executionNumber,
|
|
29
62
|
abortedCount,
|
|
@@ -33,21 +66,22 @@ export const createExecutionResultLog = (
|
|
|
33
66
|
})})`
|
|
34
67
|
|
|
35
68
|
if (completedExecutionLogAbbreviation && status === "completed") {
|
|
36
|
-
return `
|
|
37
|
-
${description} ${summary}`
|
|
69
|
+
return `${description} ${summary}`
|
|
38
70
|
}
|
|
39
71
|
|
|
40
|
-
const {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
file:
|
|
47
|
-
runtime:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
72
|
+
const { consoleCalls = [], error, duration } = executionResult
|
|
73
|
+
const console = formatConsoleCalls(consoleCalls)
|
|
74
|
+
|
|
75
|
+
return formatExecution({
|
|
76
|
+
label: `${description} ${summary}`,
|
|
77
|
+
details: {
|
|
78
|
+
file: fileRelativeUrl,
|
|
79
|
+
runtime: `${runtimeName}/${runtimeVersion}`,
|
|
80
|
+
duration: msAsDuration(duration),
|
|
81
|
+
...(error ? { error: error.stack } : {}),
|
|
82
|
+
},
|
|
83
|
+
console,
|
|
84
|
+
})
|
|
51
85
|
}
|
|
52
86
|
|
|
53
87
|
const descriptionFormatters = {
|
|
@@ -83,38 +117,33 @@ const descriptionFormatters = {
|
|
|
83
117
|
},
|
|
84
118
|
}
|
|
85
119
|
|
|
86
|
-
const
|
|
87
|
-
if (!endMs) return ""
|
|
88
|
-
|
|
89
|
-
return `
|
|
90
|
-
duration: ${msAsDuration(endMs - startMs)}`
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const appendConsole = (consoleCalls) => {
|
|
94
|
-
if (!consoleCalls || consoleCalls.length === 0) return ""
|
|
95
|
-
|
|
120
|
+
const formatConsoleCalls = (consoleCalls) => {
|
|
96
121
|
const consoleOutput = consoleCalls.reduce((previous, { text }) => {
|
|
97
122
|
return `${previous}${text}`
|
|
98
123
|
}, "")
|
|
99
124
|
|
|
100
125
|
const consoleOutputTrimmed = consoleOutput.trim()
|
|
101
|
-
if (consoleOutputTrimmed === "")
|
|
126
|
+
if (consoleOutputTrimmed === "") {
|
|
127
|
+
return ""
|
|
128
|
+
}
|
|
102
129
|
|
|
103
|
-
return
|
|
104
|
-
${ANSI.color(`-------- console --------`, ANSI.GREY)}
|
|
130
|
+
return `${ANSI.color(`-------- console --------`, ANSI.GREY)}
|
|
105
131
|
${consoleOutputTrimmed}
|
|
106
132
|
${ANSI.color(`-------------------------`, ANSI.GREY)}`
|
|
107
133
|
}
|
|
108
134
|
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
135
|
+
const formatExecution = ({ label, details = {}, console }) => {
|
|
136
|
+
let message = ``
|
|
137
|
+
|
|
138
|
+
message += label
|
|
139
|
+
Object.keys(details).forEach((key) => {
|
|
140
|
+
message += `
|
|
141
|
+
${key}: ${details[key]}`
|
|
142
|
+
})
|
|
143
|
+
if (console) {
|
|
144
|
+
message += `
|
|
145
|
+
${console}`
|
|
112
146
|
}
|
|
113
147
|
|
|
114
|
-
return
|
|
115
|
-
error: ${error.stack}`
|
|
148
|
+
return message
|
|
116
149
|
}
|
|
117
|
-
|
|
118
|
-
// export const createShortExecutionResultLog = () => {
|
|
119
|
-
// return `Execution completed (2/9) - (all completed)`
|
|
120
|
-
// }
|
|
@@ -14,11 +14,8 @@ export const launchAndExecute = async ({
|
|
|
14
14
|
executeParams,
|
|
15
15
|
|
|
16
16
|
allocatedMs,
|
|
17
|
-
measureDuration = false,
|
|
18
17
|
mirrorConsole = false,
|
|
19
18
|
captureConsole = false, // rename collectConsole ?
|
|
20
|
-
collectRuntimeName = false,
|
|
21
|
-
collectRuntimeVersion = false,
|
|
22
19
|
inheritCoverage = false,
|
|
23
20
|
collectCoverage = false,
|
|
24
21
|
coverageTempDirectoryUrl,
|
|
@@ -102,25 +99,14 @@ export const launchAndExecute = async ({
|
|
|
102
99
|
)
|
|
103
100
|
}
|
|
104
101
|
|
|
105
|
-
|
|
106
|
-
executionResultTransformer
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (collectRuntimeVersion) {
|
|
116
|
-
executionResultTransformer = composeTransformer(
|
|
117
|
-
executionResultTransformer,
|
|
118
|
-
(executionResult) => {
|
|
119
|
-
executionResult.runtimeVersion = runtime.version
|
|
120
|
-
return executionResult
|
|
121
|
-
},
|
|
122
|
-
)
|
|
123
|
-
}
|
|
102
|
+
executionResultTransformer = composeTransformer(
|
|
103
|
+
executionResultTransformer,
|
|
104
|
+
(executionResult) => {
|
|
105
|
+
executionResult.runtimeName = runtime.name
|
|
106
|
+
executionResult.runtimeVersion = runtime.version
|
|
107
|
+
return executionResult
|
|
108
|
+
},
|
|
109
|
+
)
|
|
124
110
|
|
|
125
111
|
if (
|
|
126
112
|
inheritCoverage &&
|
|
@@ -204,18 +190,14 @@ export const launchAndExecute = async ({
|
|
|
204
190
|
)
|
|
205
191
|
}
|
|
206
192
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
executionResultTransformer
|
|
210
|
-
|
|
211
|
-
(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
return executionResult
|
|
216
|
-
},
|
|
217
|
-
)
|
|
218
|
-
}
|
|
193
|
+
const startMs = Date.now()
|
|
194
|
+
executionResultTransformer = composeTransformer(
|
|
195
|
+
executionResultTransformer,
|
|
196
|
+
(executionResult) => {
|
|
197
|
+
executionResult.duration = Date.now() - startMs
|
|
198
|
+
return executionResult
|
|
199
|
+
},
|
|
200
|
+
)
|
|
219
201
|
|
|
220
202
|
try {
|
|
221
203
|
const runtimeLabel = `${runtime.name}/${runtime.version}`
|
|
@@ -2,10 +2,11 @@ import { require } from "../require.js"
|
|
|
2
2
|
|
|
3
3
|
const humanizeDuration = require("humanize-duration")
|
|
4
4
|
|
|
5
|
-
export const msAsDuration = (
|
|
6
|
-
return humanizeDuration(
|
|
5
|
+
export const msAsDuration = (ms) => {
|
|
6
|
+
return humanizeDuration(ms, {
|
|
7
7
|
largest: 2,
|
|
8
|
-
maxDecimalPoints:
|
|
8
|
+
maxDecimalPoints: ms < 0.1 ? 3 : ms < 1000 ? 2 : ms < 60000 ? 1 : 0,
|
|
9
|
+
delimiter: " and ",
|
|
9
10
|
// units: ["s"]
|
|
10
11
|
})
|
|
11
12
|
}
|