velocious 1.0.666 → 1.0.668
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/README.md +40 -11
- package/build/background-jobs/adapter-client.js +22 -0
- package/build/background-jobs/adapter.js +15 -0
- package/build/background-jobs/client.js +147 -1
- package/build/background-jobs/job-semantics.js +30 -0
- package/build/background-jobs/job.js +26 -1
- package/build/background-jobs/local-adapter.js +36 -8
- package/build/background-jobs/local-store.js +410 -39
- package/build/background-jobs/main.js +69 -2
- package/build/background-jobs/platform-job.js +26 -1
- package/build/background-jobs/runtime.js +58 -0
- package/build/background-jobs/store.js +374 -6
- package/build/background-jobs/types.js +27 -2
- package/build/background-jobs/web/controller.js +1 -0
- package/build/configuration.js +30 -6
- package/build/database/drivers/mssql/column.js +2 -2
- package/build/database/drivers/mssql/table.js +19 -5
- package/build/database/pool/async-tracked-multi-connection.js +2 -1
- package/build/database/query/alter-table-base.js +8 -1
- package/build/database/record/attachments/store.js +2 -2
- package/build/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +2 -48
- package/build/environment-handlers/node/cli/commands/test.js +17 -69
- package/build/frontend-model-controller.js +22 -2
- package/build/frontend-models/websocket-channel.js +18 -0
- package/build/http-server/client/websocket-session.js +41 -4
- package/build/http-server/websocket-channel.js +14 -0
- package/build/http-server/websocket-event-log-store.js +85 -12
- package/build/http-server/websocket-events-host.js +17 -7
- package/build/src/background-jobs/adapter-client.d.ts +17 -0
- package/build/src/background-jobs/adapter-client.d.ts.map +1 -1
- package/build/src/background-jobs/adapter-client.js +19 -1
- package/build/src/background-jobs/adapter.d.ts +15 -0
- package/build/src/background-jobs/adapter.d.ts.map +1 -1
- package/build/src/background-jobs/adapter.js +14 -1
- package/build/src/background-jobs/client.d.ts +17 -0
- package/build/src/background-jobs/client.d.ts.map +1 -1
- package/build/src/background-jobs/client.js +129 -1
- package/build/src/background-jobs/job-semantics.d.ts +18 -0
- package/build/src/background-jobs/job-semantics.d.ts.map +1 -1
- package/build/src/background-jobs/job-semantics.js +28 -1
- package/build/src/background-jobs/job.d.ts +15 -0
- package/build/src/background-jobs/job.d.ts.map +1 -1
- package/build/src/background-jobs/job.js +21 -2
- package/build/src/background-jobs/local-adapter.d.ts +23 -8
- package/build/src/background-jobs/local-adapter.d.ts.map +1 -1
- package/build/src/background-jobs/local-adapter.js +35 -9
- package/build/src/background-jobs/local-store.d.ts +120 -5
- package/build/src/background-jobs/local-store.d.ts.map +1 -1
- package/build/src/background-jobs/local-store.js +366 -40
- package/build/src/background-jobs/main.d.ts +24 -2
- package/build/src/background-jobs/main.d.ts.map +1 -1
- package/build/src/background-jobs/main.js +68 -3
- package/build/src/background-jobs/platform-job.d.ts +15 -0
- package/build/src/background-jobs/platform-job.d.ts.map +1 -1
- package/build/src/background-jobs/platform-job.js +19 -2
- package/build/src/background-jobs/runtime.d.ts +39 -0
- package/build/src/background-jobs/runtime.d.ts.map +1 -1
- package/build/src/background-jobs/runtime.js +49 -1
- package/build/src/background-jobs/store.d.ts +100 -1
- package/build/src/background-jobs/store.d.ts.map +1 -1
- package/build/src/background-jobs/store.js +334 -8
- package/build/src/background-jobs/types.d.ts +97 -4
- package/build/src/background-jobs/types.d.ts.map +1 -1
- package/build/src/background-jobs/types.js +28 -3
- package/build/src/background-jobs/web/controller.d.ts.map +1 -1
- package/build/src/background-jobs/web/controller.js +2 -1
- package/build/src/configuration.d.ts +18 -1
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +28 -6
- package/build/src/database/drivers/mssql/column.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/column.js +2 -2
- package/build/src/database/drivers/mssql/table.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/table.js +19 -5
- package/build/src/database/pool/async-tracked-multi-connection.d.ts.map +1 -1
- package/build/src/database/pool/async-tracked-multi-connection.js +3 -2
- package/build/src/database/query/alter-table-base.d.ts.map +1 -1
- package/build/src/database/query/alter-table-base.js +9 -2
- package/build/src/database/record/attachments/store.js +3 -3
- package/build/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +3 -47
- package/build/src/environment-handlers/node/cli/commands/test.d.ts +2 -28
- package/build/src/environment-handlers/node/cli/commands/test.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/test.js +10 -61
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +18 -3
- package/build/src/frontend-models/websocket-channel.d.ts +9 -0
- package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
- package/build/src/frontend-models/websocket-channel.js +16 -1
- package/build/src/http-server/client/websocket-session.d.ts +25 -2
- package/build/src/http-server/client/websocket-session.d.ts.map +1 -1
- package/build/src/http-server/client/websocket-session.js +40 -5
- package/build/src/http-server/websocket-channel.d.ts +11 -0
- package/build/src/http-server/websocket-channel.d.ts.map +1 -1
- package/build/src/http-server/websocket-channel.js +14 -1
- package/build/src/http-server/websocket-event-log-store.d.ts +60 -41
- package/build/src/http-server/websocket-event-log-store.d.ts.map +1 -1
- package/build/src/http-server/websocket-event-log-store.js +79 -12
- package/build/src/http-server/websocket-events-host.d.ts +10 -3
- package/build/src/http-server/websocket-events-host.d.ts.map +1 -1
- package/build/src/http-server/websocket-events-host.js +17 -8
- package/build/src/testing/test-files-finder.d.ts +17 -117
- package/build/src/testing/test-files-finder.d.ts.map +1 -1
- package/build/src/testing/test-files-finder.js +29 -294
- package/build/src/testing/test-filter-parser.d.ts +32 -22
- package/build/src/testing/test-filter-parser.d.ts.map +1 -1
- package/build/src/testing/test-filter-parser.js +25 -217
- package/build/src/testing/test-profile-activity.d.ts +1 -6
- package/build/src/testing/test-profile-activity.d.ts.map +1 -1
- package/build/src/testing/test-profile-activity.js +2 -12
- package/build/src/testing/test-profile-output.d.ts +1 -40
- package/build/src/testing/test-profile-output.d.ts.map +1 -1
- package/build/src/testing/test-profile-output.js +2 -217
- package/build/src/testing/test-profiler.d.ts +15 -659
- package/build/src/testing/test-profiler.d.ts.map +1 -1
- package/build/src/testing/test-profiler.js +35 -833
- package/build/src/testing/test-runner.d.ts +10 -1
- package/build/src/testing/test-runner.d.ts.map +1 -1
- package/build/src/testing/test-runner.js +26 -4
- package/build/src/testing/test-suite-splitter.d.ts +1 -128
- package/build/src/testing/test-suite-splitter.d.ts.map +1 -1
- package/build/src/testing/test-suite-splitter.js +2 -259
- package/build/src/testing/timing-manifest.d.ts +5 -93
- package/build/src/testing/timing-manifest.d.ts.map +1 -1
- package/build/src/testing/timing-manifest.js +4 -283
- package/build/src/testing/velocious-attempt-executor.d.ts.map +1 -1
- package/build/src/testing/velocious-attempt-executor.js +8 -2
- package/build/testing/test-files-finder.js +32 -343
- package/build/testing/test-filter-parser.js +29 -248
- package/build/testing/test-profile-activity.js +1 -12
- package/build/testing/test-profile-output.js +8 -253
- package/build/testing/test-profiler.js +38 -895
- package/build/testing/test-runner.js +33 -9
- package/build/testing/test-suite-splitter.js +1 -301
- package/build/testing/timing-manifest.js +10 -344
- package/build/testing/velocious-attempt-executor.js +7 -1
- package/package.json +3 -3
- package/scripts/test-browser.js +30 -6
- package/scripts/verify-docker-dev-environment.js +13 -1
- package/src/background-jobs/adapter-client.js +22 -0
- package/src/background-jobs/adapter.js +15 -0
- package/src/background-jobs/client.js +147 -1
- package/src/background-jobs/job-semantics.js +30 -0
- package/src/background-jobs/job.js +26 -1
- package/src/background-jobs/local-adapter.js +36 -8
- package/src/background-jobs/local-store.js +410 -39
- package/src/background-jobs/main.js +69 -2
- package/src/background-jobs/platform-job.js +26 -1
- package/src/background-jobs/runtime.js +58 -0
- package/src/background-jobs/store.js +374 -6
- package/src/background-jobs/types.js +27 -2
- package/src/background-jobs/web/controller.js +1 -0
- package/src/configuration.js +30 -6
- package/src/database/drivers/mssql/column.js +2 -2
- package/src/database/drivers/mssql/table.js +19 -5
- package/src/database/pool/async-tracked-multi-connection.js +2 -1
- package/src/database/query/alter-table-base.js +8 -1
- package/src/database/record/attachments/store.js +2 -2
- package/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +2 -48
- package/src/environment-handlers/node/cli/commands/test.js +17 -69
- package/src/frontend-model-controller.js +22 -2
- package/src/frontend-models/websocket-channel.js +18 -0
- package/src/http-server/client/websocket-session.js +41 -4
- package/src/http-server/websocket-channel.js +14 -0
- package/src/http-server/websocket-event-log-store.js +85 -12
- package/src/http-server/websocket-events-host.js +17 -7
- package/src/testing/test-files-finder.js +32 -343
- package/src/testing/test-filter-parser.js +29 -248
- package/src/testing/test-profile-activity.js +1 -12
- package/src/testing/test-profile-output.js +8 -253
- package/src/testing/test-profiler.js +38 -895
- package/src/testing/test-runner.js +33 -9
- package/src/testing/test-suite-splitter.js +1 -301
- package/src/testing/timing-manifest.js +10 -344
- package/src/testing/velocious-attempt-executor.js +7 -1
|
@@ -1,269 +1,50 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
extractTestCliArguments,
|
|
5
|
+
normalizeExamplePatterns
|
|
6
|
+
} from "@velocious/testing/node"
|
|
7
|
+
|
|
1
8
|
/**
|
|
2
|
-
* ParseFiltersResult type.
|
|
3
9
|
* @typedef {object} ParseFiltersResult
|
|
4
10
|
* @property {string[]} includeTags - Tags to include.
|
|
5
11
|
* @property {string[]} excludeTags - Tags to exclude.
|
|
6
12
|
* @property {string[]} examplePatterns - Example name patterns.
|
|
7
|
-
* @property {string[]} filteredProcessArgs - Remaining process args with
|
|
13
|
+
* @property {string[]} filteredProcessArgs - Remaining process args with package-owned flags removed.
|
|
8
14
|
* @property {number | undefined} groups - Total number of groups for test splitting.
|
|
9
15
|
* @property {number | undefined} groupNumber - Which group to run (1-indexed).
|
|
10
16
|
* @property {boolean} profile - Whether test profiling is enabled.
|
|
11
17
|
* @property {string | undefined} profileJsonPath - Rich profile output path.
|
|
12
18
|
* @property {string | undefined} timingManifestPath - JSON timing manifest path.
|
|
13
19
|
* @property {string | undefined} timingManifestOutputPath - Timing manifest output path.
|
|
20
|
+
* @property {number | undefined} retries - Default retry count.
|
|
21
|
+
* @property {string[]} setupFiles - Setup files imported before test files.
|
|
22
|
+
* @property {number | undefined} timeoutMs - Default lifecycle timeout.
|
|
14
23
|
*/
|
|
15
|
-
// @ts-check
|
|
16
|
-
|
|
17
|
-
const INCLUDE_TAG_FLAGS = new Set(["--tag", "--include-tag", "-t"])
|
|
18
|
-
const EXCLUDE_TAG_FLAGS = new Set(["--exclude-tag", "--skip-tag", "-x"])
|
|
19
|
-
const EXAMPLE_FLAGS = new Set(["--example", "--name", "-e"])
|
|
20
|
-
const GROUPS_FLAGS = new Set(["--groups"])
|
|
21
|
-
const GROUP_NUMBER_FLAGS = new Set(["--group-number"])
|
|
22
|
-
const TIMING_MANIFEST_FLAGS = new Set(["--timing-manifest"])
|
|
23
|
-
const PROFILE_JSON_FLAGS = new Set(["--profile-json"])
|
|
24
|
-
const TIMING_MANIFEST_OUTPUT_FLAGS = new Set(["--timing-manifest-output"])
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Runs split tags.
|
|
28
|
-
* @param {string | undefined} value - Tag argument value.
|
|
29
|
-
* @returns {string[]} - Tags list.
|
|
30
|
-
*/
|
|
31
|
-
function splitTags(value) {
|
|
32
|
-
if (!value) return []
|
|
33
|
-
|
|
34
|
-
return value
|
|
35
|
-
.split(",")
|
|
36
|
-
.map((tag) => tag.trim())
|
|
37
|
-
.filter(Boolean)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Runs escape reg exp.
|
|
42
|
-
* @param {string} value - Value.
|
|
43
|
-
* @returns {string} - Escaped value for regex.
|
|
44
|
-
*/
|
|
45
|
-
function escapeRegExp(value) {
|
|
46
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Runs the normalizeExamplePatterns helper.
|
|
51
|
-
* @param {string[]} patterns - Patterns.
|
|
52
|
-
* @returns {RegExp[]} - Normalized patterns.
|
|
53
|
-
*/
|
|
54
|
-
export function normalizeExamplePatterns(patterns) {
|
|
55
|
-
const normalized = []
|
|
56
|
-
|
|
57
|
-
for (const pattern of patterns) {
|
|
58
|
-
const regexMatch = pattern.match(/^\/(.+)\/([gimsuy]*)$/)
|
|
59
24
|
|
|
60
|
-
|
|
61
|
-
normalized.push(new RegExp(regexMatch[1], regexMatch[2]))
|
|
62
|
-
} else {
|
|
63
|
-
normalized.push(new RegExp(escapeRegExp(pattern)))
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return normalized
|
|
68
|
-
}
|
|
25
|
+
export { normalizeExamplePatterns }
|
|
69
26
|
|
|
70
27
|
/**
|
|
71
|
-
*
|
|
72
|
-
* @param {string[]} processArgs -
|
|
73
|
-
* @returns {ParseFiltersResult} -
|
|
28
|
+
* Preserves the Velocious parser result while delegating package-owned option parsing.
|
|
29
|
+
* @param {string[]} processArgs - Raw process arguments including the command name.
|
|
30
|
+
* @returns {ParseFiltersResult} - Package options and downstream process arguments.
|
|
74
31
|
*/
|
|
75
32
|
export function parseFilters(processArgs) {
|
|
76
|
-
const
|
|
77
|
-
const excludeTags = []
|
|
78
|
-
const filteredProcessArgs = processArgs.length > 0 ? [processArgs[0]] : []
|
|
79
|
-
const examplePatterns = []
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Defines groups.
|
|
83
|
-
* @type {number | undefined} */
|
|
84
|
-
let groups
|
|
85
|
-
/**
|
|
86
|
-
* Defines groupNumber.
|
|
87
|
-
* @type {number | undefined} */
|
|
88
|
-
let groupNumber
|
|
89
|
-
let profile = false
|
|
90
|
-
/** @type {string | undefined} */
|
|
91
|
-
let profileJsonPath
|
|
92
|
-
/** @type {string | undefined} */
|
|
93
|
-
let timingManifestPath
|
|
94
|
-
/** @type {string | undefined} */
|
|
95
|
-
let timingManifestOutputPath
|
|
96
|
-
|
|
97
|
-
let inRestArgs = false
|
|
98
|
-
|
|
99
|
-
for (let i = 1; i < processArgs.length; i++) {
|
|
100
|
-
const arg = processArgs[i]
|
|
101
|
-
|
|
102
|
-
if (arg === "--") {
|
|
103
|
-
inRestArgs = true
|
|
104
|
-
filteredProcessArgs.push(arg)
|
|
105
|
-
continue
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (!inRestArgs) {
|
|
109
|
-
if (INCLUDE_TAG_FLAGS.has(arg)) {
|
|
110
|
-
const nextValue = processArgs[i + 1]
|
|
111
|
-
|
|
112
|
-
if (nextValue && !nextValue.startsWith("-")) {
|
|
113
|
-
includeTags.push(...splitTags(nextValue))
|
|
114
|
-
i++
|
|
115
|
-
}
|
|
116
|
-
continue
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (EXCLUDE_TAG_FLAGS.has(arg)) {
|
|
120
|
-
const nextValue = processArgs[i + 1]
|
|
121
|
-
|
|
122
|
-
if (nextValue && !nextValue.startsWith("-")) {
|
|
123
|
-
excludeTags.push(...splitTags(nextValue))
|
|
124
|
-
i++
|
|
125
|
-
}
|
|
126
|
-
continue
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (arg.startsWith("--tag=")) {
|
|
130
|
-
includeTags.push(...splitTags(arg.slice("--tag=".length)))
|
|
131
|
-
continue
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (arg.startsWith("--include-tag=")) {
|
|
135
|
-
includeTags.push(...splitTags(arg.slice("--include-tag=".length)))
|
|
136
|
-
continue
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (arg.startsWith("--exclude-tag=")) {
|
|
140
|
-
excludeTags.push(...splitTags(arg.slice("--exclude-tag=".length)))
|
|
141
|
-
continue
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (arg.startsWith("--skip-tag=")) {
|
|
145
|
-
excludeTags.push(...splitTags(arg.slice("--skip-tag=".length)))
|
|
146
|
-
continue
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (EXAMPLE_FLAGS.has(arg)) {
|
|
150
|
-
const nextValue = processArgs[i + 1]
|
|
151
|
-
|
|
152
|
-
if (nextValue && !nextValue.startsWith("-")) {
|
|
153
|
-
examplePatterns.push(nextValue)
|
|
154
|
-
i++
|
|
155
|
-
}
|
|
156
|
-
continue
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (arg.startsWith("--example=")) {
|
|
160
|
-
examplePatterns.push(arg.slice("--example=".length))
|
|
161
|
-
continue
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (arg.startsWith("--name=")) {
|
|
165
|
-
examplePatterns.push(arg.slice("--name=".length))
|
|
166
|
-
continue
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (GROUPS_FLAGS.has(arg)) {
|
|
170
|
-
const nextValue = processArgs[i + 1]
|
|
171
|
-
|
|
172
|
-
if (nextValue && !nextValue.startsWith("-")) {
|
|
173
|
-
groups = parseInt(nextValue, 10)
|
|
174
|
-
i++
|
|
175
|
-
}
|
|
176
|
-
continue
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (arg.startsWith("--groups=")) {
|
|
180
|
-
groups = parseInt(arg.slice("--groups=".length), 10)
|
|
181
|
-
continue
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (GROUP_NUMBER_FLAGS.has(arg)) {
|
|
185
|
-
const nextValue = processArgs[i + 1]
|
|
186
|
-
|
|
187
|
-
if (nextValue && !nextValue.startsWith("-")) {
|
|
188
|
-
groupNumber = parseInt(nextValue, 10)
|
|
189
|
-
i++
|
|
190
|
-
}
|
|
191
|
-
continue
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (arg.startsWith("--group-number=")) {
|
|
195
|
-
groupNumber = parseInt(arg.slice("--group-number=".length), 10)
|
|
196
|
-
continue
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (TIMING_MANIFEST_FLAGS.has(arg)) {
|
|
200
|
-
const nextValue = processArgs[i + 1]
|
|
201
|
-
|
|
202
|
-
if (!nextValue || nextValue.startsWith("-")) throw new Error("--timing-manifest requires a path")
|
|
203
|
-
timingManifestPath = nextValue
|
|
204
|
-
i++
|
|
205
|
-
continue
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (arg.startsWith("--timing-manifest=")) {
|
|
209
|
-
timingManifestPath = arg.slice("--timing-manifest=".length)
|
|
210
|
-
if (!timingManifestPath) throw new Error("--timing-manifest requires a path")
|
|
211
|
-
continue
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (arg === "--profile") {
|
|
215
|
-
profile = true
|
|
216
|
-
continue
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (PROFILE_JSON_FLAGS.has(arg)) {
|
|
220
|
-
const nextValue = processArgs[i + 1]
|
|
221
|
-
|
|
222
|
-
if (!nextValue || nextValue.startsWith("-")) throw new Error("--profile-json requires a path")
|
|
223
|
-
profileJsonPath = nextValue
|
|
224
|
-
profile = true
|
|
225
|
-
i++
|
|
226
|
-
continue
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (arg.startsWith("--profile-json=")) {
|
|
230
|
-
profileJsonPath = arg.slice("--profile-json=".length)
|
|
231
|
-
if (!profileJsonPath) throw new Error("--profile-json requires a path")
|
|
232
|
-
profile = true
|
|
233
|
-
continue
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (TIMING_MANIFEST_OUTPUT_FLAGS.has(arg)) {
|
|
237
|
-
const nextValue = processArgs[i + 1]
|
|
238
|
-
|
|
239
|
-
if (!nextValue || nextValue.startsWith("-")) throw new Error("--timing-manifest-output requires a path")
|
|
240
|
-
timingManifestOutputPath = nextValue
|
|
241
|
-
profile = true
|
|
242
|
-
i++
|
|
243
|
-
continue
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (arg.startsWith("--timing-manifest-output=")) {
|
|
247
|
-
timingManifestOutputPath = arg.slice("--timing-manifest-output=".length)
|
|
248
|
-
if (!timingManifestOutputPath) throw new Error("--timing-manifest-output requires a path")
|
|
249
|
-
profile = true
|
|
250
|
-
continue
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
filteredProcessArgs.push(arg)
|
|
255
|
-
}
|
|
33
|
+
const {options, remainingArguments} = extractTestCliArguments(processArgs)
|
|
256
34
|
|
|
257
35
|
return {
|
|
258
|
-
includeTags:
|
|
259
|
-
excludeTags:
|
|
260
|
-
examplePatterns,
|
|
261
|
-
filteredProcessArgs,
|
|
262
|
-
groups,
|
|
263
|
-
groupNumber,
|
|
264
|
-
profile,
|
|
265
|
-
profileJsonPath,
|
|
266
|
-
timingManifestPath,
|
|
267
|
-
timingManifestOutputPath
|
|
36
|
+
includeTags: options.includeTags,
|
|
37
|
+
excludeTags: options.excludeTags,
|
|
38
|
+
examplePatterns: options.examples,
|
|
39
|
+
filteredProcessArgs: remainingArguments,
|
|
40
|
+
groups: options.groups,
|
|
41
|
+
groupNumber: options.groupNumber,
|
|
42
|
+
profile: options.profile === true,
|
|
43
|
+
profileJsonPath: options.profileJsonPath,
|
|
44
|
+
timingManifestPath: options.timingManifestPath,
|
|
45
|
+
timingManifestOutputPath: options.timingManifestOutputPath,
|
|
46
|
+
retries: options.retries,
|
|
47
|
+
setupFiles: options.setupFiles,
|
|
48
|
+
timeoutMs: options.timeoutMs
|
|
268
49
|
}
|
|
269
50
|
}
|
|
@@ -1,14 +1,3 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
* Validates a low-cardinality activity label suitable for profile output.
|
|
5
|
-
* @param {string} name - Activity name.
|
|
6
|
-
* @returns {string} - Validated name.
|
|
7
|
-
*/
|
|
8
|
-
export function validateTestActivityName(name) {
|
|
9
|
-
if (typeof name !== "string" || !/^[a-z][a-z0-9]*(?:[._:-][a-z0-9]+)*$/.test(name) || name.length > 64) {
|
|
10
|
-
throw new Error("Test profile activity name must be a lowercase identifier of at most 64 characters")
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return name
|
|
14
|
-
}
|
|
3
|
+
export { validateTestActivityName } from "@velocious/testing/profiling"
|
|
@@ -1,255 +1,10 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"checkoutname",
|
|
12
|
-
"credential",
|
|
13
|
-
"credentials",
|
|
14
|
-
"databasename",
|
|
15
|
-
"error",
|
|
16
|
-
"host",
|
|
17
|
-
"password",
|
|
18
|
-
"reusekey",
|
|
19
|
-
"sql",
|
|
20
|
-
"stack",
|
|
21
|
-
"tenant",
|
|
22
|
-
"username"
|
|
23
|
-
])
|
|
24
|
-
const PHASE_ORDER = [
|
|
25
|
-
"discovery",
|
|
26
|
-
"imports",
|
|
27
|
-
"testing config/global setup",
|
|
28
|
-
"beforeAll",
|
|
29
|
-
"beforeEach",
|
|
30
|
-
"test body",
|
|
31
|
-
"afterEach",
|
|
32
|
-
"afterAll",
|
|
33
|
-
"custom",
|
|
34
|
-
"runner overhead",
|
|
35
|
-
"total"
|
|
36
|
-
]
|
|
37
|
-
let atomicWriteSequence = 0
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Recursively rejects fields that are forbidden from rich profile output.
|
|
41
|
-
* @param {ReturnType<typeof JSON.parse>} value - Profile value.
|
|
42
|
-
* @param {string} [pathName] - Diagnostic field path.
|
|
43
|
-
* @returns {void}
|
|
44
|
-
*/
|
|
45
|
-
function assertProfilePrivacy(value, pathName = "profile") {
|
|
46
|
-
if (!value || typeof value !== "object") return
|
|
47
|
-
|
|
48
|
-
if (Array.isArray(value)) {
|
|
49
|
-
for (let index = 0; index < value.length; index++) {
|
|
50
|
-
assertProfilePrivacy(value[index], `${pathName}[${index}]`)
|
|
51
|
-
}
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
for (const [key, childValue] of Object.entries(value)) {
|
|
56
|
-
if (FORBIDDEN_PROFILE_FIELDS.has(key.toLowerCase())) {
|
|
57
|
-
throw new Error(`Forbidden profile field: ${pathName}.${key}`)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
assertProfilePrivacy(childValue, `${pathName}.${key}`)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Recursively rounds duration fields and rejects invalid duration values.
|
|
66
|
-
* @param {ReturnType<typeof JSON.parse>} value - Profile value.
|
|
67
|
-
* @param {string | undefined} [parentKey] - Parent field name.
|
|
68
|
-
* @returns {void}
|
|
69
|
-
*/
|
|
70
|
-
function normalizeDurations(value, parentKey) {
|
|
71
|
-
if (!value || typeof value !== "object") return
|
|
72
|
-
|
|
73
|
-
if (Array.isArray(value)) {
|
|
74
|
-
for (const childValue of value) normalizeDurations(childValue, parentKey)
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
for (const [key, childValue] of Object.entries(value)) {
|
|
79
|
-
const durationField = key.endsWith("Ms") || parentKey === "cpuMs"
|
|
80
|
-
|
|
81
|
-
if (durationField && typeof childValue === "number") {
|
|
82
|
-
if (typeof childValue !== "number" || !Number.isFinite(childValue) || childValue < 0) {
|
|
83
|
-
throw new Error(`Invalid test profile duration field: ${key}`)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
value[key] = roundProfileDuration(childValue)
|
|
87
|
-
continue
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (key.endsWith("Ms") && key !== "cpuMs") {
|
|
91
|
-
throw new Error(`Invalid test profile duration field: ${key}`)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
normalizeDurations(childValue, key)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Returns a normalized JSON-safe rich profile.
|
|
100
|
-
* @param {ReturnType<typeof JSON.parse>} profile - Rich profile document.
|
|
101
|
-
* @returns {ReturnType<typeof JSON.parse>} - Normalized document.
|
|
102
|
-
*/
|
|
103
|
-
function normalizedProfile(profile) {
|
|
104
|
-
if (profile?.schema !== "velocious.test-profile" || profile?.schemaVersion !== 1) {
|
|
105
|
-
throw new Error("Invalid Velocious test profile schema")
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
assertProfilePrivacy(profile)
|
|
109
|
-
const normalized = JSON.parse(JSON.stringify(profile))
|
|
110
|
-
|
|
111
|
-
normalizeDurations(normalized)
|
|
112
|
-
return normalized
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Writes text through a same-directory temporary file and atomic rename.
|
|
117
|
-
* @param {string} outputPath - Final output path.
|
|
118
|
-
* @param {string} content - Complete output content.
|
|
119
|
-
* @returns {Promise<void>} - Resolves after rename.
|
|
120
|
-
*/
|
|
121
|
-
async function atomicWrite(outputPath, content) {
|
|
122
|
-
const directory = path.dirname(outputPath)
|
|
123
|
-
const sequence = ++atomicWriteSequence
|
|
124
|
-
const temporaryPath = path.join(directory, `.${path.basename(outputPath)}.${process.pid}.${sequence}.tmp`)
|
|
125
|
-
let temporaryFileCreated = false
|
|
126
|
-
|
|
127
|
-
await fs.mkdir(directory, {recursive: true})
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
await fs.writeFile(temporaryPath, content, {encoding: "utf8", flag: "wx"})
|
|
131
|
-
temporaryFileCreated = true
|
|
132
|
-
await fs.rename(temporaryPath, outputPath)
|
|
133
|
-
} catch (error) {
|
|
134
|
-
if (temporaryFileCreated) {
|
|
135
|
-
try {
|
|
136
|
-
await fs.unlink(temporaryPath)
|
|
137
|
-
} catch (cleanupError) {
|
|
138
|
-
if (!(cleanupError instanceof Error) || !("code" in cleanupError) || cleanupError.code !== "ENOENT") {
|
|
139
|
-
throw new AggregateError(
|
|
140
|
-
[error, cleanupError],
|
|
141
|
-
`Failed to write and clean up test profile output: ${outputPath}`,
|
|
142
|
-
{cause: cleanupError}
|
|
143
|
-
)
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
throw error
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Creates a sorted plain timing manifest.
|
|
154
|
-
* @param {ReturnType<typeof JSON.parse>} profile - Rich profile document.
|
|
155
|
-
* @returns {Record<string, number>} - Sorted file-duration map.
|
|
156
|
-
*/
|
|
157
|
-
export function timingManifestFromProfile(profile) {
|
|
158
|
-
/** @type {Record<string, number>} */
|
|
159
|
-
const manifest = {}
|
|
160
|
-
const validatedManifest = validateTimingManifest(profile.timingManifest || {}, {source: "Test profile timing manifest"})
|
|
161
|
-
|
|
162
|
-
for (const [filePath, durationMs] of Object.entries(validatedManifest)) {
|
|
163
|
-
manifest[filePath] = roundProfileDuration(durationMs)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return manifest
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Atomically writes a canonical splitter-compatible timing manifest.
|
|
171
|
-
* @param {object} args - Output arguments.
|
|
172
|
-
* @param {string} args.outputPath - Final output path.
|
|
173
|
-
* @param {Record<string, number>} args.timingManifest - Validated or candidate timing manifest.
|
|
174
|
-
* @returns {Promise<void>} - Resolves after atomic replacement.
|
|
175
|
-
*/
|
|
176
|
-
export async function writeTimingManifest({outputPath, timingManifest}) {
|
|
177
|
-
const normalized = validateTimingManifest(timingManifest)
|
|
178
|
-
/** @type {Record<string, number>} */
|
|
179
|
-
const rounded = {}
|
|
180
|
-
|
|
181
|
-
for (const [filePath, durationMs] of Object.entries(normalized)) {
|
|
182
|
-
rounded[filePath] = roundProfileDuration(durationMs)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
await atomicWrite(outputPath, `${JSON.stringify(rounded, null, 2)}\n`)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Atomically writes requested test profile outputs.
|
|
190
|
-
* @param {object} args - Output options.
|
|
191
|
-
* @param {ReturnType<typeof JSON.parse>} args.profile - Rich profile document.
|
|
192
|
-
* @param {string} [args.profileJsonPath] - Rich JSON path.
|
|
193
|
-
* @param {string} [args.timingManifestOutputPath] - Plain timing manifest path.
|
|
194
|
-
* @returns {Promise<void>} - Resolves after all requested writes.
|
|
195
|
-
*/
|
|
196
|
-
export async function writeTestProfileOutputs({profile, profileJsonPath, timingManifestOutputPath}) {
|
|
197
|
-
const normalized = normalizedProfile(profile)
|
|
198
|
-
const timingManifest = timingManifestFromProfile(normalized)
|
|
199
|
-
|
|
200
|
-
normalized.timingManifest = timingManifest
|
|
201
|
-
|
|
202
|
-
if (profileJsonPath) {
|
|
203
|
-
await atomicWrite(profileJsonPath, `${JSON.stringify(normalized, null, 2)}\n`)
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (timingManifestOutputPath) {
|
|
207
|
-
await writeTimingManifest({outputPath: timingManifestOutputPath, timingManifest})
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Formats a compact Benchmark-style console summary.
|
|
213
|
-
* @param {ReturnType<typeof JSON.parse>} profile - Rich profile document.
|
|
214
|
-
* @param {{profileJsonPath?: string, timingManifestOutputPath?: string}} [outputs] - Written output paths.
|
|
215
|
-
* @returns {string} - Console summary.
|
|
216
|
-
*/
|
|
217
|
-
export function formatTestProfileSummary(profile, outputs = {}) {
|
|
218
|
-
const lines = [
|
|
219
|
-
"Test profile",
|
|
220
|
-
"Phase".padEnd(31) + "Count".padStart(5) + "Real ms".padStart(13) + "CPU ms".padStart(13)
|
|
221
|
-
]
|
|
222
|
-
|
|
223
|
-
for (const phase of PHASE_ORDER) {
|
|
224
|
-
const aggregate = profile.phases?.[phase]
|
|
225
|
-
|
|
226
|
-
if (!aggregate) continue
|
|
227
|
-
|
|
228
|
-
lines.push(
|
|
229
|
-
phase.padEnd(31) +
|
|
230
|
-
String(aggregate.count).padStart(5) +
|
|
231
|
-
aggregate.totalMs.toFixed(3).padStart(13) +
|
|
232
|
-
aggregate.cpuMs.total.toFixed(3).padStart(13)
|
|
233
|
-
)
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (profile.pools?.length > 0) {
|
|
237
|
-
lines.push("Pools")
|
|
238
|
-
|
|
239
|
-
for (const pool of profile.pools) {
|
|
240
|
-
lines.push(
|
|
241
|
-
`Pool ${pool.identifier}: created=${pool.connectionCreation.count}` +
|
|
242
|
-
` failed=${pool.connectionCreation.failedCount}` +
|
|
243
|
-
` wait=${pool.checkoutWait.count}/${pool.checkoutWait.totalMs.toFixed(3)}ms` +
|
|
244
|
-
` timeouts=${pool.checkoutTimeoutCount}` +
|
|
245
|
-
` reaped=${pool.idleReap.disposalCount}` +
|
|
246
|
-
` peak=${pool.peakLiveConnections}`
|
|
247
|
-
)
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (outputs.profileJsonPath) lines.push(`Rich JSON: ${outputs.profileJsonPath}`)
|
|
252
|
-
if (outputs.timingManifestOutputPath) lines.push(`Timing manifest: ${outputs.timingManifestOutputPath}`)
|
|
253
|
-
|
|
254
|
-
return lines.join("\n")
|
|
255
|
-
}
|
|
3
|
+
export {
|
|
4
|
+
formatTestProfileSummary,
|
|
5
|
+
loadTimingManifest,
|
|
6
|
+
resolveTestProfileOptions,
|
|
7
|
+
timingManifestFromProfile,
|
|
8
|
+
writeTestProfileOutputs,
|
|
9
|
+
writeTimingManifest
|
|
10
|
+
} from "@velocious/testing/node"
|