as-test 0.5.1 → 0.5.3
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/CHANGELOG.md +101 -0
- package/README.md +268 -3
- package/as-test.config.schema.json +171 -2
- package/assembly/coverage.ts +20 -0
- package/assembly/index.ts +196 -12
- package/assembly/src/expectation.ts +15 -29
- package/assembly/src/log.ts +13 -1
- package/assembly/src/suite.ts +53 -9
- package/assembly/src/tests.ts +25 -5
- package/assembly/util/helpers.ts +0 -1
- package/assembly/util/json.ts +78 -0
- package/bin/build.js +118 -33
- package/bin/index.js +524 -35
- package/bin/init.js +35 -10
- package/bin/reporters/default.js +26 -9
- package/bin/reporters/tap.js +294 -0
- package/bin/run.js +368 -44
- package/bin/types.js +18 -0
- package/bin/util.js +229 -1
- package/package.json +40 -50
- package/transform/lib/coverage.js +135 -124
- package/transform/lib/index.js +57 -23
- package/transform/lib/log.js +2 -39
- package/transform/lib/mock.js +42 -22
- package/transform/lib/builder.js.map +0 -1
- package/transform/lib/coverage.js.map +0 -1
- package/transform/lib/index.js.map +0 -1
- package/transform/lib/linker.js.map +0 -1
- package/transform/lib/location.js.map +0 -1
- package/transform/lib/log.js.map +0 -1
- package/transform/lib/mock.js.map +0 -1
- package/transform/lib/range.js.map +0 -1
- package/transform/lib/types.js.map +0 -1
- package/transform/lib/util.js.map +0 -1
- package/transform/lib/visitor.js.map +0 -1
package/bin/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import chalk from "chalk";
|
|
|
3
3
|
import { build } from "./build.js";
|
|
4
4
|
import { createRunReporter, run } from "./run.js";
|
|
5
5
|
import { init } from "./init.js";
|
|
6
|
-
import { getCliVersion, loadConfig } from "./util.js";
|
|
6
|
+
import { getCliVersion, loadConfig, resolveModeNames } from "./util.js";
|
|
7
7
|
import * as path from "path";
|
|
8
8
|
import { glob } from "glob";
|
|
9
9
|
const _args = process.argv.slice(2);
|
|
@@ -12,6 +12,7 @@ const args = [];
|
|
|
12
12
|
const COMMANDS = ["run", "build", "test", "init"];
|
|
13
13
|
const version = getCliVersion();
|
|
14
14
|
const configPath = resolveConfigPath(_args);
|
|
15
|
+
const selectedModes = resolveModeNames(_args);
|
|
15
16
|
for (const arg of _args) {
|
|
16
17
|
if (arg.startsWith("-"))
|
|
17
18
|
flags.push(arg);
|
|
@@ -27,36 +28,54 @@ if (!args.length) {
|
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
else if (COMMANDS.includes(args[0])) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
31
|
+
try {
|
|
32
|
+
const command = args.shift();
|
|
33
|
+
const commandArgs = resolveCommandArgs(_args, command ?? "");
|
|
34
|
+
const featureToggles = resolveFeatureToggles(_args, command ?? "");
|
|
35
|
+
const buildFeatureToggles = {
|
|
36
|
+
tryAs: featureToggles.tryAs,
|
|
37
|
+
coverage: featureToggles.coverage,
|
|
38
|
+
};
|
|
39
|
+
const runFlags = {
|
|
40
|
+
snapshot: !flags.includes("--no-snapshot"),
|
|
41
|
+
updateSnapshots: flags.includes("--update-snapshots"),
|
|
42
|
+
clean: flags.includes("--clean"),
|
|
43
|
+
showCoverage: flags.includes("--show-coverage"),
|
|
44
|
+
verbose: flags.includes("--verbose"),
|
|
45
|
+
coverage: featureToggles.coverage,
|
|
46
|
+
};
|
|
47
|
+
if (command === "build") {
|
|
48
|
+
const modeTargets = resolveExecutionModes(configPath, selectedModes);
|
|
49
|
+
runBuildModes(configPath, commandArgs, modeTargets, buildFeatureToggles).catch((error) => {
|
|
50
|
+
printCliError(error);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else if (command === "run") {
|
|
55
|
+
const modeTargets = resolveExecutionModes(configPath, selectedModes);
|
|
56
|
+
runRuntimeModes(runFlags, configPath, commandArgs, modeTargets).catch((error) => {
|
|
57
|
+
printCliError(error);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
else if (command === "test") {
|
|
62
|
+
const modeTargets = resolveExecutionModes(configPath, selectedModes);
|
|
63
|
+
runTestModes(runFlags, configPath, commandArgs, modeTargets, buildFeatureToggles).catch((error) => {
|
|
64
|
+
printCliError(error);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
else if (command === "init") {
|
|
69
|
+
const commandTokens = resolveCommandTokens(_args, command ?? "");
|
|
70
|
+
init(commandTokens).catch((error) => {
|
|
71
|
+
printCliError(error);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
53
75
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
printCliError(error);
|
|
58
|
-
process.exit(1);
|
|
59
|
-
});
|
|
76
|
+
catch (error) {
|
|
77
|
+
printCliError(error);
|
|
78
|
+
process.exit(1);
|
|
60
79
|
}
|
|
61
80
|
}
|
|
62
81
|
else {
|
|
@@ -109,6 +128,10 @@ function info() {
|
|
|
109
128
|
"Initialize an empty testing template");
|
|
110
129
|
console.log("");
|
|
111
130
|
console.log(chalk.bold("Flags:"));
|
|
131
|
+
console.log(" " +
|
|
132
|
+
chalk.bold.blue("--mode <name[,name...]>") +
|
|
133
|
+
" " +
|
|
134
|
+
"Run one or multiple named config modes");
|
|
112
135
|
console.log(" " +
|
|
113
136
|
chalk.bold.blue("--config <path>") +
|
|
114
137
|
" " +
|
|
@@ -129,10 +152,22 @@ function info() {
|
|
|
129
152
|
chalk.bold.blue("--show-coverage") +
|
|
130
153
|
" " +
|
|
131
154
|
"Print all coverage points with line:column refs");
|
|
155
|
+
console.log(" " +
|
|
156
|
+
chalk.bold.blue("--enable <feature>") +
|
|
157
|
+
" " +
|
|
158
|
+
"Enable as-test feature (coverage|try-as)");
|
|
159
|
+
console.log(" " +
|
|
160
|
+
chalk.bold.blue("--disable <feature>") +
|
|
161
|
+
" " +
|
|
162
|
+
"Disable as-test feature (coverage|try-as)");
|
|
132
163
|
console.log(" " +
|
|
133
164
|
chalk.bold.blue("--verbose") +
|
|
134
165
|
" " +
|
|
135
166
|
"Print each suite start/end line");
|
|
167
|
+
console.log(" " +
|
|
168
|
+
chalk.bold.blue("--reporter <name|path>") +
|
|
169
|
+
" " +
|
|
170
|
+
"Use built-in reporter (default|tap) or custom module path");
|
|
136
171
|
console.log("");
|
|
137
172
|
console.log(chalk.dim("If your using this, consider dropping a star, it would help a lot!") + "\n");
|
|
138
173
|
console.log("View the repo: " +
|
|
@@ -176,9 +211,33 @@ function resolveCommandArgs(rawArgs, command) {
|
|
|
176
211
|
i++;
|
|
177
212
|
continue;
|
|
178
213
|
}
|
|
214
|
+
if (arg == "--mode") {
|
|
215
|
+
i++;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
179
218
|
if (arg.startsWith("--config=")) {
|
|
180
219
|
continue;
|
|
181
220
|
}
|
|
221
|
+
if (arg.startsWith("--mode=")) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (arg == "--reporter") {
|
|
225
|
+
i++;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (arg.startsWith("--reporter=")) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (arg == "--tap") {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (arg == "--enable" || arg == "--disable") {
|
|
235
|
+
i++;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (arg.startsWith("--enable=") || arg.startsWith("--disable=")) {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
182
241
|
if (arg.startsWith("-")) {
|
|
183
242
|
continue;
|
|
184
243
|
}
|
|
@@ -186,6 +245,50 @@ function resolveCommandArgs(rawArgs, command) {
|
|
|
186
245
|
}
|
|
187
246
|
return values;
|
|
188
247
|
}
|
|
248
|
+
function resolveFeatureToggles(rawArgs, command) {
|
|
249
|
+
if (command !== "build" && command !== "run" && command !== "test")
|
|
250
|
+
return {};
|
|
251
|
+
const out = {};
|
|
252
|
+
let seenCommand = false;
|
|
253
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
254
|
+
const arg = rawArgs[i];
|
|
255
|
+
if (!seenCommand) {
|
|
256
|
+
if (arg == command)
|
|
257
|
+
seenCommand = true;
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (arg == "--enable" || arg == "--disable") {
|
|
261
|
+
const enabled = arg == "--enable";
|
|
262
|
+
const next = rawArgs[i + 1];
|
|
263
|
+
if (next && !next.startsWith("-")) {
|
|
264
|
+
applyFeatureToggle(out, next, enabled);
|
|
265
|
+
i++;
|
|
266
|
+
}
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (arg.startsWith("--enable=") || arg.startsWith("--disable=")) {
|
|
270
|
+
const enabled = arg.startsWith("--enable=");
|
|
271
|
+
const eq = arg.indexOf("=");
|
|
272
|
+
const value = arg.slice(eq + 1).trim();
|
|
273
|
+
if (value.length) {
|
|
274
|
+
applyFeatureToggle(out, value, enabled);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return out;
|
|
279
|
+
}
|
|
280
|
+
function applyFeatureToggle(out, rawFeature, enabled) {
|
|
281
|
+
const key = rawFeature.trim().toLowerCase();
|
|
282
|
+
if (key == "coverage") {
|
|
283
|
+
out.coverage = enabled;
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (key == "try-as" || key == "try_as" || key == "tryas") {
|
|
287
|
+
out.tryAs = enabled;
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
throw new Error(`unknown feature "${rawFeature}". Supported features: coverage, try-as`);
|
|
291
|
+
}
|
|
189
292
|
function resolveCommandTokens(rawArgs, command) {
|
|
190
293
|
const values = [];
|
|
191
294
|
let seenCommand = false;
|
|
@@ -200,7 +303,7 @@ function resolveCommandTokens(rawArgs, command) {
|
|
|
200
303
|
}
|
|
201
304
|
return values;
|
|
202
305
|
}
|
|
203
|
-
async function runTestSequential(runFlags, configPath, selectors) {
|
|
306
|
+
async function runTestSequential(runFlags, configPath, selectors, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, modeName) {
|
|
204
307
|
const files = await resolveSelectedFiles(configPath, selectors);
|
|
205
308
|
if (!files.length) {
|
|
206
309
|
const scope = selectors.length > 0
|
|
@@ -208,7 +311,7 @@ async function runTestSequential(runFlags, configPath, selectors) {
|
|
|
208
311
|
: "configured input patterns";
|
|
209
312
|
throw new Error(`No test files matched: ${scope}`);
|
|
210
313
|
}
|
|
211
|
-
const reporterSession = await createRunReporter(configPath);
|
|
314
|
+
const reporterSession = await createRunReporter(configPath, undefined, modeName);
|
|
212
315
|
const reporter = reporterSession.reporter;
|
|
213
316
|
const snapshotEnabled = runFlags.snapshot !== false;
|
|
214
317
|
reporter.onRunStart?.({
|
|
@@ -221,7 +324,7 @@ async function runTestSequential(runFlags, configPath, selectors) {
|
|
|
221
324
|
const results = [];
|
|
222
325
|
let failed = false;
|
|
223
326
|
for (const file of files) {
|
|
224
|
-
await build(configPath, [file]);
|
|
327
|
+
await build(configPath, [file], modeName, buildFeatureToggles);
|
|
225
328
|
const artifactKey = path.basename(file).replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
226
329
|
const result = await run(runFlags, configPath, [file], false, {
|
|
227
330
|
reporter,
|
|
@@ -229,12 +332,133 @@ async function runTestSequential(runFlags, configPath, selectors) {
|
|
|
229
332
|
emitRunComplete: false,
|
|
230
333
|
logFileName: `test.${artifactKey}.log.json`,
|
|
231
334
|
coverageFileName: `coverage.${artifactKey}.log.json`,
|
|
335
|
+
modeName,
|
|
232
336
|
});
|
|
233
337
|
results.push(result);
|
|
234
338
|
if (result?.failed)
|
|
235
339
|
failed = true;
|
|
236
340
|
}
|
|
237
341
|
const summary = aggregateRunResults(results);
|
|
342
|
+
summary.stats = applyConfiguredFileTotalToStats(summary.stats, fileSummaryTotal);
|
|
343
|
+
reporter.onRunComplete?.({
|
|
344
|
+
clean: runFlags.clean,
|
|
345
|
+
snapshotEnabled,
|
|
346
|
+
showCoverage: runFlags.showCoverage,
|
|
347
|
+
snapshotSummary: summary.snapshotSummary,
|
|
348
|
+
coverageSummary: summary.coverageSummary,
|
|
349
|
+
stats: summary.stats,
|
|
350
|
+
reports: summary.reports,
|
|
351
|
+
modeSummary: buildSingleModeSummary(summary.stats, summary.snapshotSummary, modeSummaryTotal),
|
|
352
|
+
});
|
|
353
|
+
return failed;
|
|
354
|
+
}
|
|
355
|
+
async function runBuildModes(configPath, selectors, modes, buildFeatureToggles) {
|
|
356
|
+
for (const modeName of modes) {
|
|
357
|
+
await build(configPath, selectors, modeName, buildFeatureToggles);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
async function runRuntimeModes(runFlags, configPath, selectors, modes) {
|
|
361
|
+
const modeSummaryTotal = resolveConfiguredModeTotal(configPath);
|
|
362
|
+
const fileSummaryTotal = await resolveConfiguredFileTotal(configPath);
|
|
363
|
+
if (modes.length > 1) {
|
|
364
|
+
const failed = await runRuntimeMatrix(runFlags, configPath, selectors, modes, modeSummaryTotal, fileSummaryTotal);
|
|
365
|
+
process.exit(failed ? 1 : 0);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
let failed = false;
|
|
369
|
+
for (const modeName of modes) {
|
|
370
|
+
const result = await run(runFlags, configPath, selectors, false, {
|
|
371
|
+
modeName,
|
|
372
|
+
modeSummaryTotal,
|
|
373
|
+
modeSummaryExecuted: 1,
|
|
374
|
+
fileSummaryTotal,
|
|
375
|
+
});
|
|
376
|
+
if (result.failed)
|
|
377
|
+
failed = true;
|
|
378
|
+
}
|
|
379
|
+
process.exit(failed ? 1 : 0);
|
|
380
|
+
}
|
|
381
|
+
async function runRuntimeMatrix(runFlags, configPath, selectors, modes, modeSummaryTotal, fileSummaryTotal) {
|
|
382
|
+
const files = await resolveSelectedFiles(configPath, selectors);
|
|
383
|
+
if (!files.length) {
|
|
384
|
+
const scope = selectors.length > 0
|
|
385
|
+
? selectors.join(", ")
|
|
386
|
+
: "configured input patterns";
|
|
387
|
+
throw new Error(`No test files matched: ${scope}`);
|
|
388
|
+
}
|
|
389
|
+
const reporterSession = await createRunReporter(configPath);
|
|
390
|
+
const reporter = reporterSession.reporter;
|
|
391
|
+
const snapshotEnabled = runFlags.snapshot !== false;
|
|
392
|
+
reporter.onRunStart?.({
|
|
393
|
+
runtimeName: reporterSession.runtimeName,
|
|
394
|
+
clean: runFlags.clean,
|
|
395
|
+
verbose: runFlags.verbose,
|
|
396
|
+
snapshotEnabled,
|
|
397
|
+
updateSnapshots: runFlags.updateSnapshots,
|
|
398
|
+
});
|
|
399
|
+
const silentReporter = {};
|
|
400
|
+
const allResults = [];
|
|
401
|
+
const modeLabels = modes.map((modeName) => modeName ?? "default");
|
|
402
|
+
const showPerModeTimes = Boolean(runFlags.verbose);
|
|
403
|
+
const liveMatrix = reporterSession.reporterKind == "default" && canRewriteStdout();
|
|
404
|
+
const modeState = modes.map(() => ({
|
|
405
|
+
failed: false,
|
|
406
|
+
passed: false,
|
|
407
|
+
}));
|
|
408
|
+
const fileState = files.map(() => ({
|
|
409
|
+
failed: false,
|
|
410
|
+
passed: false,
|
|
411
|
+
}));
|
|
412
|
+
for (let fileIndex = 0; fileIndex < files.length; fileIndex++) {
|
|
413
|
+
const file = files[fileIndex];
|
|
414
|
+
const fileName = path.basename(file);
|
|
415
|
+
const fileResults = [];
|
|
416
|
+
const modeTimes = modes.map(() => "...");
|
|
417
|
+
if (liveMatrix) {
|
|
418
|
+
renderMatrixLiveLine(fileName, modeLabels, modeTimes, showPerModeTimes);
|
|
419
|
+
}
|
|
420
|
+
for (let i = 0; i < modes.length; i++) {
|
|
421
|
+
const modeName = modes[i];
|
|
422
|
+
try {
|
|
423
|
+
const artifactKey = fileName.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
424
|
+
const result = await run(runFlags, configPath, [file], false, {
|
|
425
|
+
reporter: silentReporter,
|
|
426
|
+
reporterKind: "default",
|
|
427
|
+
emitRunStart: false,
|
|
428
|
+
emitRunComplete: false,
|
|
429
|
+
logFileName: `run.${artifactKey}.log.json`,
|
|
430
|
+
coverageFileName: `coverage.${artifactKey}.log.json`,
|
|
431
|
+
modeName,
|
|
432
|
+
});
|
|
433
|
+
modeTimes[i] = formatMatrixModeTime(result.stats.time);
|
|
434
|
+
if (liveMatrix) {
|
|
435
|
+
renderMatrixLiveLine(fileName, modeLabels, modeTimes, showPerModeTimes);
|
|
436
|
+
}
|
|
437
|
+
if (result.failed) {
|
|
438
|
+
modeState[i].failed = true;
|
|
439
|
+
}
|
|
440
|
+
else if (result.stats.passedFiles > 0) {
|
|
441
|
+
modeState[i].passed = true;
|
|
442
|
+
}
|
|
443
|
+
fileResults.push(result);
|
|
444
|
+
allResults.push(result);
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
clearLiveLine();
|
|
448
|
+
throw error;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
renderMatrixFileResult(fileName, modeLabels, fileResults, modeTimes, liveMatrix, showPerModeTimes);
|
|
452
|
+
const verdict = resolveMatrixVerdict(fileResults);
|
|
453
|
+
if (verdict == "fail") {
|
|
454
|
+
fileState[fileIndex].failed = true;
|
|
455
|
+
}
|
|
456
|
+
else if (verdict == "ok") {
|
|
457
|
+
fileState[fileIndex].passed = true;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
const summary = aggregateRunResults(allResults);
|
|
461
|
+
summary.stats = applyMatrixFileSummaryToStats(summary.stats, fileState, fileSummaryTotal);
|
|
238
462
|
reporter.onRunComplete?.({
|
|
239
463
|
clean: runFlags.clean,
|
|
240
464
|
snapshotEnabled,
|
|
@@ -243,23 +467,264 @@ async function runTestSequential(runFlags, configPath, selectors) {
|
|
|
243
467
|
coverageSummary: summary.coverageSummary,
|
|
244
468
|
stats: summary.stats,
|
|
245
469
|
reports: summary.reports,
|
|
470
|
+
modeSummary: buildModeSummary(modeState, modeSummaryTotal),
|
|
246
471
|
});
|
|
472
|
+
return allResults.some((result) => result.failed);
|
|
473
|
+
}
|
|
474
|
+
async function runTestModes(runFlags, configPath, selectors, modes, buildFeatureToggles) {
|
|
475
|
+
const modeSummaryTotal = resolveConfiguredModeTotal(configPath);
|
|
476
|
+
const fileSummaryTotal = await resolveConfiguredFileTotal(configPath);
|
|
477
|
+
if (modes.length > 1) {
|
|
478
|
+
const failed = await runTestMatrix(runFlags, configPath, selectors, modes, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal);
|
|
479
|
+
process.exit(failed ? 1 : 0);
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
let failed = false;
|
|
483
|
+
for (const modeName of modes) {
|
|
484
|
+
const modeFailed = await runTestSequential(runFlags, configPath, selectors, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, modeName);
|
|
485
|
+
if (modeFailed)
|
|
486
|
+
failed = true;
|
|
487
|
+
}
|
|
247
488
|
process.exit(failed ? 1 : 0);
|
|
248
489
|
}
|
|
490
|
+
async function runTestMatrix(runFlags, configPath, selectors, modes, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal) {
|
|
491
|
+
const files = await resolveSelectedFiles(configPath, selectors);
|
|
492
|
+
if (!files.length) {
|
|
493
|
+
const scope = selectors.length > 0
|
|
494
|
+
? selectors.join(", ")
|
|
495
|
+
: "configured input patterns";
|
|
496
|
+
throw new Error(`No test files matched: ${scope}`);
|
|
497
|
+
}
|
|
498
|
+
const reporterSession = await createRunReporter(configPath);
|
|
499
|
+
const reporter = reporterSession.reporter;
|
|
500
|
+
const snapshotEnabled = runFlags.snapshot !== false;
|
|
501
|
+
reporter.onRunStart?.({
|
|
502
|
+
runtimeName: reporterSession.runtimeName,
|
|
503
|
+
clean: runFlags.clean,
|
|
504
|
+
verbose: runFlags.verbose,
|
|
505
|
+
snapshotEnabled,
|
|
506
|
+
updateSnapshots: runFlags.updateSnapshots,
|
|
507
|
+
});
|
|
508
|
+
const silentReporter = {};
|
|
509
|
+
const allResults = [];
|
|
510
|
+
const modeLabels = modes.map((modeName) => modeName ?? "default");
|
|
511
|
+
const showPerModeTimes = Boolean(runFlags.verbose);
|
|
512
|
+
const liveMatrix = reporterSession.reporterKind == "default" && canRewriteStdout();
|
|
513
|
+
const modeState = modes.map(() => ({
|
|
514
|
+
failed: false,
|
|
515
|
+
passed: false,
|
|
516
|
+
}));
|
|
517
|
+
const fileState = files.map(() => ({
|
|
518
|
+
failed: false,
|
|
519
|
+
passed: false,
|
|
520
|
+
}));
|
|
521
|
+
for (let fileIndex = 0; fileIndex < files.length; fileIndex++) {
|
|
522
|
+
const file = files[fileIndex];
|
|
523
|
+
const fileName = path.basename(file);
|
|
524
|
+
const fileResults = [];
|
|
525
|
+
const modeTimes = modes.map(() => "...");
|
|
526
|
+
if (liveMatrix) {
|
|
527
|
+
renderMatrixLiveLine(fileName, modeLabels, modeTimes, showPerModeTimes);
|
|
528
|
+
}
|
|
529
|
+
for (let i = 0; i < modes.length; i++) {
|
|
530
|
+
const modeName = modes[i];
|
|
531
|
+
try {
|
|
532
|
+
await build(configPath, [file], modeName, buildFeatureToggles);
|
|
533
|
+
const artifactKey = fileName.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
534
|
+
const result = await run(runFlags, configPath, [file], false, {
|
|
535
|
+
reporter: silentReporter,
|
|
536
|
+
reporterKind: "default",
|
|
537
|
+
emitRunStart: false,
|
|
538
|
+
emitRunComplete: false,
|
|
539
|
+
logFileName: `test.${artifactKey}.log.json`,
|
|
540
|
+
coverageFileName: `coverage.${artifactKey}.log.json`,
|
|
541
|
+
modeName,
|
|
542
|
+
});
|
|
543
|
+
modeTimes[i] = formatMatrixModeTime(result.stats.time);
|
|
544
|
+
if (liveMatrix) {
|
|
545
|
+
renderMatrixLiveLine(fileName, modeLabels, modeTimes, showPerModeTimes);
|
|
546
|
+
}
|
|
547
|
+
if (result.failed) {
|
|
548
|
+
modeState[i].failed = true;
|
|
549
|
+
}
|
|
550
|
+
else if (result.stats.passedFiles > 0) {
|
|
551
|
+
modeState[i].passed = true;
|
|
552
|
+
}
|
|
553
|
+
fileResults.push(result);
|
|
554
|
+
allResults.push(result);
|
|
555
|
+
}
|
|
556
|
+
catch (error) {
|
|
557
|
+
clearLiveLine();
|
|
558
|
+
throw error;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
renderMatrixFileResult(fileName, modeLabels, fileResults, modeTimes, liveMatrix, showPerModeTimes);
|
|
562
|
+
const verdict = resolveMatrixVerdict(fileResults);
|
|
563
|
+
if (verdict == "fail") {
|
|
564
|
+
fileState[fileIndex].failed = true;
|
|
565
|
+
}
|
|
566
|
+
else if (verdict == "ok") {
|
|
567
|
+
fileState[fileIndex].passed = true;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
const summary = aggregateRunResults(allResults);
|
|
571
|
+
summary.stats = applyMatrixFileSummaryToStats(summary.stats, fileState, fileSummaryTotal);
|
|
572
|
+
reporter.onRunComplete?.({
|
|
573
|
+
clean: runFlags.clean,
|
|
574
|
+
snapshotEnabled,
|
|
575
|
+
showCoverage: runFlags.showCoverage,
|
|
576
|
+
snapshotSummary: summary.snapshotSummary,
|
|
577
|
+
coverageSummary: summary.coverageSummary,
|
|
578
|
+
stats: summary.stats,
|
|
579
|
+
reports: summary.reports,
|
|
580
|
+
modeSummary: buildModeSummary(modeState, modeSummaryTotal),
|
|
581
|
+
});
|
|
582
|
+
return allResults.some((result) => result.failed);
|
|
583
|
+
}
|
|
584
|
+
function renderMatrixFileResult(file, modes, results, modeTimes, liveMatrix, showPerModeTimes) {
|
|
585
|
+
const verdict = resolveMatrixVerdict(results);
|
|
586
|
+
const badge = verdict == "fail"
|
|
587
|
+
? chalk.bgRed.white(" FAIL ")
|
|
588
|
+
: verdict == "ok"
|
|
589
|
+
? chalk.bgGreenBright.black(" PASS ")
|
|
590
|
+
: chalk.bgBlackBright.white(" SKIP ");
|
|
591
|
+
const avg = formatMatrixAverageTime(results);
|
|
592
|
+
const timingText = showPerModeTimes ? modeTimes.join(",") : avg;
|
|
593
|
+
const suffix = showPerModeTimes ? ` ${chalk.dim(`(${modes.join(",")})`)}` : "";
|
|
594
|
+
const line = `${badge} ${file} ${chalk.dim(timingText)}${suffix}`;
|
|
595
|
+
if (liveMatrix)
|
|
596
|
+
clearLiveLine();
|
|
597
|
+
process.stdout.write(line + "\n");
|
|
598
|
+
}
|
|
599
|
+
function resolveMatrixVerdict(results) {
|
|
600
|
+
if (results.some((result) => result.failed))
|
|
601
|
+
return "fail";
|
|
602
|
+
const hasPass = results.some((result) => result.stats.passedFiles > 0);
|
|
603
|
+
if (hasPass)
|
|
604
|
+
return "ok";
|
|
605
|
+
return "skip";
|
|
606
|
+
}
|
|
607
|
+
function canRewriteStdout() {
|
|
608
|
+
return Boolean(process.stdout.isTTY);
|
|
609
|
+
}
|
|
610
|
+
function clearLiveLine() {
|
|
611
|
+
if (!canRewriteStdout())
|
|
612
|
+
return;
|
|
613
|
+
process.stdout.write("\r\x1b[2K");
|
|
614
|
+
}
|
|
615
|
+
function renderMatrixLiveLine(file, modes, modeTimes, showPerModeTimes) {
|
|
616
|
+
if (!canRewriteStdout())
|
|
617
|
+
return;
|
|
618
|
+
const timingText = showPerModeTimes ? modeTimes.join(",") : "...";
|
|
619
|
+
const suffix = showPerModeTimes ? ` ${chalk.dim(`(${modes.join(",")})`)}` : "";
|
|
620
|
+
const line = `${chalk.bgBlackBright.white(" .... ")} ${file} ${chalk.dim(timingText)}${suffix}`;
|
|
621
|
+
process.stdout.write(`\r\x1b[2K${line}`);
|
|
622
|
+
}
|
|
623
|
+
function formatMatrixModeTime(ms) {
|
|
624
|
+
const safeMs = Number.isFinite(ms) ? Math.max(0, ms) : 0;
|
|
625
|
+
return `${safeMs.toFixed(1)}ms`;
|
|
626
|
+
}
|
|
627
|
+
function formatMatrixAverageTime(results) {
|
|
628
|
+
if (!results.length)
|
|
629
|
+
return "0.0ms";
|
|
630
|
+
let total = 0;
|
|
631
|
+
for (const result of results) {
|
|
632
|
+
total += Number.isFinite(result.stats.time) ? Math.max(0, result.stats.time) : 0;
|
|
633
|
+
}
|
|
634
|
+
return `${(total / results.length).toFixed(1)}ms`;
|
|
635
|
+
}
|
|
636
|
+
function buildModeSummary(modeState, totalModes) {
|
|
637
|
+
const total = Math.max(totalModes, modeState.length, 1);
|
|
638
|
+
let skipped = Math.max(0, total - modeState.length);
|
|
639
|
+
let failed = 0;
|
|
640
|
+
for (const mode of modeState) {
|
|
641
|
+
if (mode.failed) {
|
|
642
|
+
failed++;
|
|
643
|
+
}
|
|
644
|
+
else if (!mode.passed) {
|
|
645
|
+
skipped++;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
return {
|
|
649
|
+
failed,
|
|
650
|
+
skipped,
|
|
651
|
+
total,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
function buildSingleModeSummary(stats, snapshotSummary, totalModes) {
|
|
655
|
+
const total = Math.max(totalModes, 1);
|
|
656
|
+
const failed = stats.failedFiles > 0 || snapshotSummary.failed > 0 ? 1 : 0;
|
|
657
|
+
const skippedInExecuted = failed ? 0 : stats.passedFiles > 0 ? 0 : 1;
|
|
658
|
+
return {
|
|
659
|
+
failed,
|
|
660
|
+
skipped: Math.max(0, total - 1) + skippedInExecuted,
|
|
661
|
+
total,
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
function applyConfiguredFileTotalToStats(stats, fileSummaryTotal) {
|
|
665
|
+
const total = Math.max(fileSummaryTotal, 0);
|
|
666
|
+
const executed = stats.failedFiles + stats.passedFiles + stats.skippedFiles;
|
|
667
|
+
const unexecuted = Math.max(0, total - executed);
|
|
668
|
+
return {
|
|
669
|
+
...stats,
|
|
670
|
+
skippedFiles: stats.skippedFiles + unexecuted,
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
function applyMatrixFileSummaryToStats(stats, fileState, fileSummaryTotal) {
|
|
674
|
+
let failedFiles = 0;
|
|
675
|
+
let passedFiles = 0;
|
|
676
|
+
let skippedFiles = 0;
|
|
677
|
+
for (const file of fileState) {
|
|
678
|
+
if (file.failed)
|
|
679
|
+
failedFiles++;
|
|
680
|
+
else if (file.passed)
|
|
681
|
+
passedFiles++;
|
|
682
|
+
else
|
|
683
|
+
skippedFiles++;
|
|
684
|
+
}
|
|
685
|
+
const total = Math.max(fileSummaryTotal, fileState.length, 0);
|
|
686
|
+
const unexecuted = Math.max(0, total - fileState.length);
|
|
687
|
+
return {
|
|
688
|
+
...stats,
|
|
689
|
+
failedFiles,
|
|
690
|
+
passedFiles,
|
|
691
|
+
skippedFiles: skippedFiles + unexecuted,
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
function resolveConfiguredModeTotal(configPath) {
|
|
695
|
+
const resolvedConfigPath = configPath ?? path.join(process.cwd(), "./as-test.config.json");
|
|
696
|
+
const config = loadConfig(resolvedConfigPath, false);
|
|
697
|
+
const configuredModes = Object.keys(config.modes).length;
|
|
698
|
+
return configuredModes || 1;
|
|
699
|
+
}
|
|
700
|
+
async function resolveConfiguredFileTotal(configPath) {
|
|
701
|
+
const files = await resolveSelectedFiles(configPath, []);
|
|
702
|
+
return files.length;
|
|
703
|
+
}
|
|
704
|
+
function resolveExecutionModes(configPath, selectedModes) {
|
|
705
|
+
if (selectedModes.length)
|
|
706
|
+
return selectedModes;
|
|
707
|
+
const resolvedConfigPath = configPath ?? path.join(process.cwd(), "./as-test.config.json");
|
|
708
|
+
const config = loadConfig(resolvedConfigPath, false);
|
|
709
|
+
const configuredModes = Object.keys(config.modes);
|
|
710
|
+
if (!configuredModes.length)
|
|
711
|
+
return [undefined];
|
|
712
|
+
return configuredModes;
|
|
713
|
+
}
|
|
249
714
|
async function resolveSelectedFiles(configPath, selectors) {
|
|
250
715
|
const resolvedConfigPath = configPath ?? path.join(process.cwd(), "./as-test.config.json");
|
|
251
716
|
const config = loadConfig(resolvedConfigPath, true);
|
|
252
717
|
const patterns = resolveInputPatterns(config.input, selectors);
|
|
253
718
|
const matches = await glob(patterns);
|
|
254
719
|
const specs = matches.filter((file) => file.endsWith(".spec.ts"));
|
|
255
|
-
return [...new Set(specs)];
|
|
720
|
+
return [...new Set(specs)].sort((a, b) => a.localeCompare(b));
|
|
256
721
|
}
|
|
257
722
|
function resolveInputPatterns(configured, selectors) {
|
|
258
723
|
const configuredInputs = Array.isArray(configured) ? configured : [configured];
|
|
259
724
|
if (!selectors.length)
|
|
260
725
|
return configuredInputs;
|
|
261
726
|
const patterns = new Set();
|
|
262
|
-
for (const selector of selectors) {
|
|
727
|
+
for (const selector of expandSelectors(selectors)) {
|
|
263
728
|
if (!selector)
|
|
264
729
|
continue;
|
|
265
730
|
if (isBareSuiteSelector(selector)) {
|
|
@@ -273,6 +738,30 @@ function resolveInputPatterns(configured, selectors) {
|
|
|
273
738
|
}
|
|
274
739
|
return [...patterns];
|
|
275
740
|
}
|
|
741
|
+
function expandSelectors(selectors) {
|
|
742
|
+
const expanded = [];
|
|
743
|
+
for (const selector of selectors) {
|
|
744
|
+
if (!selector)
|
|
745
|
+
continue;
|
|
746
|
+
if (!shouldSplitSelector(selector)) {
|
|
747
|
+
expanded.push(selector);
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
for (const token of selector.split(",")) {
|
|
751
|
+
const trimmed = token.trim();
|
|
752
|
+
if (!trimmed.length)
|
|
753
|
+
continue;
|
|
754
|
+
expanded.push(trimmed);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return expanded;
|
|
758
|
+
}
|
|
759
|
+
function shouldSplitSelector(selector) {
|
|
760
|
+
return (selector.includes(",") &&
|
|
761
|
+
!selector.includes("/") &&
|
|
762
|
+
!selector.includes("\\") &&
|
|
763
|
+
!/[*?[\]{}]/.test(selector));
|
|
764
|
+
}
|
|
276
765
|
function isBareSuiteSelector(selector) {
|
|
277
766
|
return (!selector.includes("/") &&
|
|
278
767
|
!selector.includes("\\") &&
|