as-test 1.0.12 → 1.0.14

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/bin/index.js CHANGED
@@ -62,6 +62,7 @@ else if (COMMANDS.includes(args[0])) {
62
62
  else if (command === "run") {
63
63
  executeRunCommand(_args, flags, configPath, selectedModes, {
64
64
  resolveCommandArgs,
65
+ resolveSuiteSelectors,
65
66
  resolveListFlags,
66
67
  resolveFeatureToggles,
67
68
  resolveParallelJobs,
@@ -78,6 +79,8 @@ else if (COMMANDS.includes(args[0])) {
78
79
  else if (command === "test") {
79
80
  executeTestCommand(_args, flags, configPath, selectedModes, {
80
81
  resolveCommandArgs,
82
+ resolveSuiteSelectors,
83
+ resolveFuzzerSelectors,
81
84
  resolveListFlags,
82
85
  resolveFeatureToggles,
83
86
  resolveParallelJobs,
@@ -95,6 +98,7 @@ else if (COMMANDS.includes(args[0])) {
95
98
  else if (command === "fuzz") {
96
99
  executeFuzzCommand(_args, configPath, selectedModes, {
97
100
  resolveCommandArgs,
101
+ resolveFuzzerSelectors,
98
102
  resolveListFlags,
99
103
  resolveJobs,
100
104
  resolveExecutionModes,
@@ -252,6 +256,8 @@ function printCommandHelp(command) {
252
256
  process.stdout.write(" --overwrite-snapshots Overwrite existing snapshot entries on mismatch\n");
253
257
  process.stdout.write(" --no-snapshot Disable snapshot assertions for this run\n");
254
258
  process.stdout.write(" --show-coverage Print uncovered coverage point details\n");
259
+ process.stdout.write(" --suite <name[,name...]> Filter results to matching suite names or suite slug paths\n");
260
+ process.stdout.write(" --suites <name[,name...]> Alias for --suite\n");
255
261
  process.stdout.write(" --enable <feature> Enable feature (coverage|try-as)\n");
256
262
  process.stdout.write(" --disable <feature> Disable feature (coverage|try-as)\n");
257
263
  process.stdout.write(" --reporter <name|path> Use built-in reporter (default|tap) or custom module path\n");
@@ -278,6 +284,8 @@ function printCommandHelp(command) {
278
284
  process.stdout.write(" --overwrite-snapshots Overwrite existing snapshot entries on mismatch\n");
279
285
  process.stdout.write(" --no-snapshot Disable snapshot assertions for this run\n");
280
286
  process.stdout.write(" --show-coverage Print uncovered coverage point details\n");
287
+ process.stdout.write(" --suite <name[,name...]> Filter results to matching suite names or suite slug paths\n");
288
+ process.stdout.write(" --suites <name[,name...]> Alias for --suite\n");
281
289
  process.stdout.write(" --enable <feature> Enable feature (coverage|try-as)\n");
282
290
  process.stdout.write(" --disable <feature> Disable feature (coverage|try-as)\n");
283
291
  process.stdout.write(" --fuzz Run fuzz targets after the normal test pass\n");
@@ -304,6 +312,10 @@ function printCommandHelp(command) {
304
312
  process.stdout.write(" --mode <name[,name...]> Run one or multiple named config modes\n");
305
313
  process.stdout.write(" --runs <value> Override fuzz iteration count, e.g. 500, 1.5x, +10%, +100000\n");
306
314
  process.stdout.write(" --seed <n> Pin fuzz seed (default uses random seed)\n");
315
+ process.stdout.write(" --fuzzer <name[,name...]> Filter results to matching fuzz target names\n");
316
+ process.stdout.write(" --fuzzers <name[,name...]> Alias for --fuzzer\n");
317
+ process.stdout.write(" --suite <name[,name...]> Alias for --fuzzer\n");
318
+ process.stdout.write(" --suites <name[,name...]> Alias for --fuzzer\n");
307
319
  process.stdout.write(" --jobs <n> Run files through an ordered worker pool\n");
308
320
  process.stdout.write(" --build-jobs <n> Limit concurrent build tasks (defaults to --jobs)\n");
309
321
  process.stdout.write(" --run-jobs <n> Limit concurrent run tasks (defaults to --jobs)\n");
@@ -384,9 +396,22 @@ function resolveCommandArgs(rawArgs, command) {
384
396
  i++;
385
397
  continue;
386
398
  }
399
+ if (arg == "--suite" ||
400
+ arg == "--suites" ||
401
+ arg == "--fuzzer" ||
402
+ arg == "--fuzzers") {
403
+ i++;
404
+ continue;
405
+ }
387
406
  if (arg.startsWith("--reporter=")) {
388
407
  continue;
389
408
  }
409
+ if (arg.startsWith("--suite=") ||
410
+ arg.startsWith("--suites=") ||
411
+ arg.startsWith("--fuzzer=") ||
412
+ arg.startsWith("--fuzzers=")) {
413
+ continue;
414
+ }
390
415
  if (arg == "--tap") {
391
416
  continue;
392
417
  }
@@ -500,6 +525,47 @@ function resolveFuzzOverrides(rawArgs, command) {
500
525
  }
501
526
  return out;
502
527
  }
528
+ function resolveSuiteSelectors(rawArgs, command) {
529
+ return resolveNamedSelectors(rawArgs, command, ["--suite", "--suites"]);
530
+ }
531
+ function resolveFuzzerSelectors(rawArgs, command) {
532
+ const flags = command == "fuzz"
533
+ ? ["--fuzzer", "--fuzzers", "--suite", "--suites"]
534
+ : ["--fuzzer", "--fuzzers"];
535
+ return resolveNamedSelectors(rawArgs, command, flags);
536
+ }
537
+ function resolveNamedSelectors(rawArgs, command, flags) {
538
+ const out = [];
539
+ let seenCommand = false;
540
+ for (let i = 0; i < rawArgs.length; i++) {
541
+ const arg = rawArgs[i];
542
+ if (!seenCommand) {
543
+ if (arg == command)
544
+ seenCommand = true;
545
+ continue;
546
+ }
547
+ let parsed = null;
548
+ for (const flag of flags) {
549
+ parsed = parseStringFlag(rawArgs, i, flag);
550
+ if (parsed)
551
+ break;
552
+ }
553
+ if (!parsed)
554
+ continue;
555
+ appendNamedSelectorTokens(out, parsed.value);
556
+ if (parsed.consumeNext)
557
+ i++;
558
+ }
559
+ return [...new Set(out)];
560
+ }
561
+ function appendNamedSelectorTokens(out, value) {
562
+ for (const token of value.split(",")) {
563
+ const normalized = token.trim();
564
+ if (!normalized.length)
565
+ continue;
566
+ out.push(normalized);
567
+ }
568
+ }
503
569
  function parseFuzzRunsFlag(rawArgs, index, flag) {
504
570
  const arg = rawArgs[index];
505
571
  let value = "";
@@ -996,7 +1062,7 @@ async function buildFileForMode(cache, args) {
996
1062
  }
997
1063
  return false;
998
1064
  }
999
- async function runTestSequential(runFlags, configPath, selectors, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, allowNoSpecFiles = false, modeName, reporterOverride, emitRunComplete = true) {
1065
+ async function runTestSequential(runFlags, configPath, selectors, suiteSelectors, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, allowNoSpecFiles = false, modeName, reporterOverride, emitRunComplete = true) {
1000
1066
  const files = await resolveSelectedFiles(configPath, selectors);
1001
1067
  if (!files.length) {
1002
1068
  if (!allowNoSpecFiles) {
@@ -1025,6 +1091,7 @@ async function runTestSequential(runFlags, configPath, selectors, buildFeatureTo
1025
1091
  const artifactKey = resolvePerFileArtifactKey(file, duplicateSpecBasenames);
1026
1092
  const result = await run(runFlags, configPath, [file], false, {
1027
1093
  reporter,
1094
+ suiteSelectors,
1028
1095
  emitRunStart: false,
1029
1096
  emitRunComplete: false,
1030
1097
  logFileName: `test.${artifactKey}.log.json`,
@@ -1113,7 +1180,7 @@ async function runBuildModes(configPath, selectors, modes, buildFeatureToggles,
1113
1180
  }
1114
1181
  process.stdout.write(`${chalk.bold("Summary:")} built ${builtCount} file(s) across ${modes.length || 1} mode(s) in ${formatTime(Date.now() - allStartedAt)}\n`);
1115
1182
  }
1116
- async function runRuntimeModes(runFlags, configPath, selectors, modes) {
1183
+ async function runRuntimeModes(runFlags, configPath, selectors, suiteSelectors, modes) {
1117
1184
  await ensureWebBrowsersReady(configPath, modes, runFlags.browser);
1118
1185
  const modeSummaryTotal = Math.max(modes.length, 1);
1119
1186
  const fileSummaryTotal = await resolveConfiguredFileTotal(configPath);
@@ -1123,13 +1190,13 @@ async function runRuntimeModes(runFlags, configPath, selectors, modes) {
1123
1190
  };
1124
1191
  if (effectiveRunFlags.jobs > 1) {
1125
1192
  if (modes.length > 1) {
1126
- const failed = await runRuntimeMatrixParallel(effectiveRunFlags, configPath, selectors, modes, modeSummaryTotal, fileSummaryTotal);
1193
+ const failed = await runRuntimeMatrixParallel(effectiveRunFlags, configPath, selectors, suiteSelectors, modes, modeSummaryTotal, fileSummaryTotal);
1127
1194
  process.exit(failed ? 1 : 0);
1128
1195
  return;
1129
1196
  }
1130
1197
  let failed = false;
1131
1198
  for (const modeName of modes) {
1132
- const result = await runRuntimeSingleParallel(effectiveRunFlags, configPath, selectors, modeName, modeSummaryTotal, fileSummaryTotal);
1199
+ const result = await runRuntimeSingleParallel(effectiveRunFlags, configPath, selectors, suiteSelectors, modeName, modeSummaryTotal, fileSummaryTotal);
1133
1200
  if (result)
1134
1201
  failed = true;
1135
1202
  }
@@ -1137,7 +1204,7 @@ async function runRuntimeModes(runFlags, configPath, selectors, modes) {
1137
1204
  return;
1138
1205
  }
1139
1206
  if (modes.length > 1) {
1140
- const failed = await runRuntimeMatrix(effectiveRunFlags, configPath, selectors, modes, modeSummaryTotal, fileSummaryTotal);
1207
+ const failed = await runRuntimeMatrix(effectiveRunFlags, configPath, selectors, suiteSelectors, modes, modeSummaryTotal, fileSummaryTotal);
1141
1208
  process.exit(failed ? 1 : 0);
1142
1209
  return;
1143
1210
  }
@@ -1147,6 +1214,7 @@ async function runRuntimeModes(runFlags, configPath, selectors, modes) {
1147
1214
  const result = await run(effectiveRunFlags, configPath, selectors, false, {
1148
1215
  reporterPath: effectiveRunFlags.reporterPath,
1149
1216
  modeName,
1217
+ suiteSelectors,
1150
1218
  modeSummaryTotal,
1151
1219
  modeSummaryExecuted: 1,
1152
1220
  fileSummaryTotal,
@@ -1157,7 +1225,7 @@ async function runRuntimeModes(runFlags, configPath, selectors, modes) {
1157
1225
  }
1158
1226
  process.exit(failed ? 1 : 0);
1159
1227
  }
1160
- async function runRuntimeMatrix(runFlags, configPath, selectors, modes, modeSummaryTotal, fileSummaryTotal) {
1228
+ async function runRuntimeMatrix(runFlags, configPath, selectors, suiteSelectors, modes, modeSummaryTotal, fileSummaryTotal) {
1161
1229
  const files = await resolveSelectedFiles(configPath, selectors);
1162
1230
  if (!files.length) {
1163
1231
  throw await buildNoTestFilesMatchedError(configPath, selectors);
@@ -1205,6 +1273,7 @@ async function runRuntimeMatrix(runFlags, configPath, selectors, modes, modeSumm
1205
1273
  const result = await run(runFlags, configPath, [file], false, {
1206
1274
  reporter: silentReporter,
1207
1275
  reporterKind: "default",
1276
+ suiteSelectors,
1208
1277
  emitRunStart: false,
1209
1278
  emitRunComplete: false,
1210
1279
  logFileName: `run.${artifactKey}.log.json`,
@@ -1256,7 +1325,7 @@ async function runRuntimeMatrix(runFlags, configPath, selectors, modes, modeSumm
1256
1325
  });
1257
1326
  return allResults.some((result) => result.failed);
1258
1327
  }
1259
- async function runTestModes(runFlags, configPath, selectors, modes, buildFeatureToggles, fuzzEnabled, fuzzOverrides) {
1328
+ async function runTestModes(runFlags, configPath, selectors, suiteSelectors, fuzzerSelectors, modes, buildFeatureToggles, fuzzEnabled, fuzzOverrides) {
1260
1329
  await ensureWebBrowsersReady(configPath, modes, runFlags.browser);
1261
1330
  const modeSummaryTotal = Math.max(modes.length, 1);
1262
1331
  const fileSummaryTotal = await resolveConfiguredFileTotal(configPath, selectors);
@@ -1266,13 +1335,13 @@ async function runTestModes(runFlags, configPath, selectors, modes, buildFeature
1266
1335
  };
1267
1336
  if (effectiveRunFlags.jobs > 1) {
1268
1337
  if (modes.length > 1) {
1269
- const failed = await runTestMatrixParallel(effectiveRunFlags, configPath, selectors, modes, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, fuzzEnabled, fuzzOverrides);
1338
+ const failed = await runTestMatrixParallel(effectiveRunFlags, configPath, selectors, suiteSelectors, fuzzerSelectors, modes, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, fuzzEnabled, fuzzOverrides);
1270
1339
  process.exit(failed ? 1 : 0);
1271
1340
  return;
1272
1341
  }
1273
1342
  let failed = false;
1274
1343
  for (const modeName of modes) {
1275
- const modeFailed = await runTestSingleParallel(effectiveRunFlags, configPath, selectors, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, fuzzEnabled, fuzzOverrides, modeName);
1344
+ const modeFailed = await runTestSingleParallel(effectiveRunFlags, configPath, selectors, suiteSelectors, fuzzerSelectors, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, fuzzEnabled, fuzzOverrides, modeName);
1276
1345
  if (modeFailed)
1277
1346
  failed = true;
1278
1347
  }
@@ -1280,21 +1349,21 @@ async function runTestModes(runFlags, configPath, selectors, modes, buildFeature
1280
1349
  return;
1281
1350
  }
1282
1351
  if (modes.length > 1) {
1283
- const failed = await runTestMatrix(effectiveRunFlags, configPath, selectors, modes, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, fuzzEnabled, fuzzOverrides);
1352
+ const failed = await runTestMatrix(effectiveRunFlags, configPath, selectors, suiteSelectors, fuzzerSelectors, modes, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, fuzzEnabled, fuzzOverrides);
1284
1353
  process.exit(failed ? 1 : 0);
1285
1354
  return;
1286
1355
  }
1287
1356
  let failed = false;
1288
1357
  for (const modeName of modes) {
1289
1358
  const reporterSession = await createRunReporter(configPath, effectiveRunFlags.reporterPath, modeName);
1290
- const modeResult = await runTestSequential(effectiveRunFlags, configPath, selectors, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, fuzzEnabled, modeName, reporterSession.reporter, !fuzzEnabled);
1359
+ const modeResult = await runTestSequential(effectiveRunFlags, configPath, selectors, suiteSelectors, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, fuzzEnabled, modeName, reporterSession.reporter, !fuzzEnabled);
1291
1360
  if (modeResult.failed)
1292
1361
  failed = true;
1293
1362
  if (fuzzEnabled) {
1294
1363
  if (reporterSession.reporterKind == "default") {
1295
1364
  process.stdout.write("\n");
1296
1365
  }
1297
- const fuzzResults = await runFuzzMatrixResults(configPath, selectors, [modeName], fuzzOverrides, reporterSession.reporter);
1366
+ const fuzzResults = await runFuzzMatrixResults(configPath, selectors, fuzzerSelectors, [modeName], fuzzOverrides, reporterSession.reporter);
1298
1367
  if (fuzzResults.some(hasFuzzFailures))
1299
1368
  failed = true;
1300
1369
  reporterSession.reporter.onRunComplete?.({
@@ -1315,13 +1384,13 @@ async function runTestModes(runFlags, configPath, selectors, modes, buildFeature
1315
1384
  }
1316
1385
  process.exit(failed ? 1 : 0);
1317
1386
  }
1318
- async function runTestMatrix(runFlags, configPath, selectors, modes, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, fuzzEnabled, fuzzOverrides) {
1387
+ async function runTestMatrix(runFlags, configPath, selectors, suiteSelectors, fuzzerSelectors, modes, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, fuzzEnabled, fuzzOverrides) {
1319
1388
  const files = await resolveSelectedFiles(configPath, selectors);
1320
1389
  if (!files.length) {
1321
1390
  if (!fuzzEnabled) {
1322
1391
  throw await buildNoTestFilesMatchedError(configPath, selectors);
1323
1392
  }
1324
- const fuzzFiles = await resolveSelectedFuzzFiles(configPath, selectors);
1393
+ const fuzzFiles = await resolveSelectedFuzzFiles(configPath, selectors, modes);
1325
1394
  if (!fuzzFiles.length) {
1326
1395
  throw await buildNoTestFilesMatchedError(configPath, selectors, true);
1327
1396
  }
@@ -1420,7 +1489,7 @@ async function runTestMatrix(runFlags, configPath, selectors, modes, buildFeatur
1420
1489
  if (reporterSession.reporterKind == "default") {
1421
1490
  process.stdout.write("\n");
1422
1491
  }
1423
- const fuzzResults = await runFuzzMatrixResults(configPath, selectors, modes, fuzzOverrides, reporter);
1492
+ const fuzzResults = await runFuzzMatrixResults(configPath, selectors, fuzzerSelectors, modes, fuzzOverrides, reporter);
1424
1493
  if (fuzzResults.some(hasFuzzFailures))
1425
1494
  failed = true;
1426
1495
  fuzzSummary = summarizeFuzzExecutions(fuzzResults);
@@ -1441,14 +1510,14 @@ async function runTestMatrix(runFlags, configPath, selectors, modes, buildFeatur
1441
1510
  reporter.flush?.();
1442
1511
  return failed;
1443
1512
  }
1444
- async function runFuzzModes(configPath, selectors, modes, rawArgs) {
1513
+ async function runFuzzModes(configPath, selectors, fuzzerSelectors, modes, rawArgs) {
1445
1514
  const overrides = resolveFuzzOverrides(rawArgs, "fuzz");
1446
1515
  const parallelSettings = resolveFuzzParallelJobs(rawArgs);
1447
1516
  const clean = rawArgs.includes("--clean");
1448
- const fuzzFiles = await resolveSelectedFuzzFiles(configPath, selectors);
1517
+ const fuzzFiles = await resolveSelectedFuzzFiles(configPath, selectors, modes);
1449
1518
  const { jobs, buildJobs, runJobs } = resolveEffectiveParallelJobs(parallelSettings, fuzzFiles.length);
1450
1519
  if (jobs > 1) {
1451
- const results = await runFuzzMatrixResultsParallel(configPath, selectors, modes, overrides, jobs, buildJobs, runJobs, clean);
1520
+ const results = await runFuzzMatrixResultsParallel(configPath, selectors, fuzzerSelectors, modes, overrides, jobs, buildJobs, runJobs, clean);
1452
1521
  const reporterSession = await createRunReporter(configPath);
1453
1522
  reporterSession.reporter.onFuzzComplete?.(buildFuzzCompleteEvent(results, modes));
1454
1523
  reporterSession.reporter.flush?.();
@@ -1456,12 +1525,12 @@ async function runFuzzModes(configPath, selectors, modes, rawArgs) {
1456
1525
  return;
1457
1526
  }
1458
1527
  const reporterSession = await createRunReporter(configPath);
1459
- const results = await runFuzzMatrixResults(configPath, selectors, modes, overrides, reporterSession.reporter);
1528
+ const results = await runFuzzMatrixResults(configPath, selectors, fuzzerSelectors, modes, overrides, reporterSession.reporter);
1460
1529
  reporterSession.reporter.onFuzzComplete?.(buildFuzzCompleteEvent(results, modes));
1461
1530
  reporterSession.reporter.flush?.();
1462
1531
  process.exit(results.some(hasFuzzFailures) ? 1 : 0);
1463
1532
  }
1464
- async function runRuntimeSingleParallel(runFlags, configPath, selectors, modeName, modeSummaryTotal, fileSummaryTotal) {
1533
+ async function runRuntimeSingleParallel(runFlags, configPath, selectors, suiteSelectors, modeName, modeSummaryTotal, fileSummaryTotal) {
1465
1534
  const files = await resolveSelectedFiles(configPath, selectors);
1466
1535
  if (!files.length) {
1467
1536
  throw await buildNoTestFilesMatchedError(configPath, selectors);
@@ -1493,6 +1562,7 @@ async function runRuntimeSingleParallel(runFlags, configPath, selectors, modeNam
1493
1562
  reporter: buffered?.reporter,
1494
1563
  reporterKind: buffered?.reporterKind,
1495
1564
  modeName,
1565
+ suiteSelectors,
1496
1566
  emitRunComplete: false,
1497
1567
  fileSummaryTotal: 1,
1498
1568
  modeSummaryTotal,
@@ -1522,7 +1592,7 @@ async function runRuntimeSingleParallel(runFlags, configPath, selectors, modeNam
1522
1592
  reporter.flush?.();
1523
1593
  return results.some((result) => result.failed);
1524
1594
  }
1525
- async function runRuntimeMatrixParallel(runFlags, configPath, selectors, modes, modeSummaryTotal, fileSummaryTotal) {
1595
+ async function runRuntimeMatrixParallel(runFlags, configPath, selectors, suiteSelectors, modes, modeSummaryTotal, fileSummaryTotal) {
1526
1596
  const files = await resolveSelectedFiles(configPath, selectors);
1527
1597
  if (!files.length) {
1528
1598
  throw await buildNoTestFilesMatchedError(configPath, selectors);
@@ -1572,6 +1642,7 @@ async function runRuntimeMatrixParallel(runFlags, configPath, selectors, modes,
1572
1642
  const result = await run(runFlags, configPath, [file], false, {
1573
1643
  reporter: silentReporter,
1574
1644
  reporterKind: "default",
1645
+ suiteSelectors,
1575
1646
  emitRunStart: false,
1576
1647
  emitRunComplete: false,
1577
1648
  logFileName: `run.${artifactKey}.log.json`,
@@ -1627,7 +1698,7 @@ async function runRuntimeMatrixParallel(runFlags, configPath, selectors, modes,
1627
1698
  reporter.flush?.();
1628
1699
  return allResults.some((result) => result.failed);
1629
1700
  }
1630
- async function runTestSingleParallel(runFlags, configPath, selectors, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, fuzzEnabled, fuzzOverrides, modeName) {
1701
+ async function runTestSingleParallel(runFlags, configPath, selectors, suiteSelectors, fuzzerSelectors, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, fuzzEnabled, fuzzOverrides, modeName) {
1631
1702
  const files = await resolveSelectedFiles(configPath, selectors);
1632
1703
  if (!files.length && !fuzzEnabled) {
1633
1704
  throw await buildNoTestFilesMatchedError(configPath, selectors);
@@ -1672,6 +1743,7 @@ async function runTestSingleParallel(runFlags, configPath, selectors, buildFeatu
1672
1743
  const result = await run({ ...runFlags, clean: true }, configPath, [file], false, {
1673
1744
  reporter: buffered?.reporter,
1674
1745
  reporterKind: buffered?.reporterKind,
1746
+ suiteSelectors,
1675
1747
  emitRunComplete: false,
1676
1748
  logFileName: `test.${artifactKey}.log.json`,
1677
1749
  coverageFileName: `coverage.${artifactKey}.log.json`,
@@ -1699,7 +1771,7 @@ async function runTestSingleParallel(runFlags, configPath, selectors, buildFeatu
1699
1771
  if (reporterSession.reporterKind == "default") {
1700
1772
  process.stdout.write("\n");
1701
1773
  }
1702
- const fuzzResults = await runFuzzMatrixResultsParallel(configPath, selectors, [modeName], fuzzOverrides, runFlags.jobs, runFlags.buildJobs, runFlags.runJobs, runFlags.clean);
1774
+ const fuzzResults = await runFuzzMatrixResultsParallel(configPath, selectors, fuzzerSelectors, [modeName], fuzzOverrides, runFlags.jobs, runFlags.buildJobs, runFlags.runJobs, runFlags.clean);
1703
1775
  if (fuzzResults.some(hasFuzzFailures))
1704
1776
  failed = true;
1705
1777
  fuzzSummary = summarizeFuzzExecutions(fuzzResults);
@@ -1720,13 +1792,13 @@ async function runTestSingleParallel(runFlags, configPath, selectors, buildFeatu
1720
1792
  reporter.flush?.();
1721
1793
  return failed;
1722
1794
  }
1723
- async function runTestMatrixParallel(runFlags, configPath, selectors, modes, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, fuzzEnabled, fuzzOverrides) {
1795
+ async function runTestMatrixParallel(runFlags, configPath, selectors, suiteSelectors, fuzzerSelectors, modes, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, fuzzEnabled, fuzzOverrides) {
1724
1796
  const files = await resolveSelectedFiles(configPath, selectors);
1725
1797
  if (!files.length) {
1726
1798
  if (!fuzzEnabled) {
1727
1799
  throw await buildNoTestFilesMatchedError(configPath, selectors);
1728
1800
  }
1729
- const fuzzFiles = await resolveSelectedFuzzFiles(configPath, selectors);
1801
+ const fuzzFiles = await resolveSelectedFuzzFiles(configPath, selectors, modes);
1730
1802
  if (!fuzzFiles.length) {
1731
1803
  throw await buildNoTestFilesMatchedError(configPath, selectors, true);
1732
1804
  }
@@ -1776,6 +1848,7 @@ async function runTestMatrixParallel(runFlags, configPath, selectors, modes, bui
1776
1848
  const result = await run(runFlags, configPath, [file], false, {
1777
1849
  reporter: silentReporter,
1778
1850
  reporterKind: "default",
1851
+ suiteSelectors,
1779
1852
  emitRunStart: false,
1780
1853
  emitRunComplete: false,
1781
1854
  logFileName: `test.${artifactKey}.log.json`,
@@ -1823,7 +1896,7 @@ async function runTestMatrixParallel(runFlags, configPath, selectors, modes, bui
1823
1896
  if (reporterSession.reporterKind == "default") {
1824
1897
  process.stdout.write("\n");
1825
1898
  }
1826
- const fuzzResults = await runFuzzMatrixResultsParallel(configPath, selectors, modes, fuzzOverrides, runFlags.jobs, runFlags.buildJobs, runFlags.runJobs, runFlags.clean);
1899
+ const fuzzResults = await runFuzzMatrixResultsParallel(configPath, selectors, fuzzerSelectors, modes, fuzzOverrides, runFlags.jobs, runFlags.buildJobs, runFlags.runJobs, runFlags.clean);
1827
1900
  if (fuzzResults.some(hasFuzzFailures))
1828
1901
  failed = true;
1829
1902
  fuzzSummary = summarizeFuzzExecutions(fuzzResults);
@@ -1844,8 +1917,12 @@ async function runTestMatrixParallel(runFlags, configPath, selectors, modes, bui
1844
1917
  reporter.flush?.();
1845
1918
  return failed;
1846
1919
  }
1847
- async function runFuzzMatrixResultsParallel(configPath, selectors, modes, overrides, jobs, buildJobs, runJobs, clean) {
1848
- const files = await resolveSelectedFuzzFiles(configPath, selectors);
1920
+ async function runFuzzMatrixResultsParallel(configPath, selectors, fuzzerSelectors, modes, overrides, jobs, buildJobs, runJobs, clean) {
1921
+ const filesByMode = new Map();
1922
+ for (const modeName of modes) {
1923
+ filesByMode.set(modeName, await resolveSelectedFuzzFiles(configPath, selectors, [modeName]));
1924
+ }
1925
+ const files = [...new Set([...filesByMode.values()].flat())].sort((a, b) => a.localeCompare(b));
1849
1926
  if (!files.length) {
1850
1927
  throw new Error(`No fuzz files matched: ${selectors.length ? selectors.join(", ") : "configured input patterns"}`);
1851
1928
  }
@@ -1856,7 +1933,9 @@ async function runFuzzMatrixResultsParallel(configPath, selectors, modes, overri
1856
1933
  const token = renderQueuedFileStart(queueDisplay, path.basename(file));
1857
1934
  const fileResults = [];
1858
1935
  for (const modeName of modes) {
1859
- const modeResults = await fuzz(configPath, [file], modeName, overrides);
1936
+ if (!(filesByMode.get(modeName)?.includes(file) ?? false))
1937
+ continue;
1938
+ const modeResults = await fuzz(configPath, [file], modeName, overrides, fuzzerSelectors);
1860
1939
  fileResults.push(...modeResults);
1861
1940
  }
1862
1941
  ordered[index] = fileResults;
@@ -1868,20 +1947,23 @@ async function runFuzzMatrixResultsParallel(configPath, selectors, modes, overri
1868
1947
  queueDisplay.flush();
1869
1948
  return ordered.flat();
1870
1949
  }
1871
- async function runFuzzMatrixResults(configPath, selectors, modes, overrides, reporter) {
1872
- const files = await resolveSelectedFuzzFiles(configPath, selectors);
1873
- if (!files.length) {
1874
- throw new Error(`No fuzz files matched: ${selectors.length ? selectors.join(", ") : "configured input patterns"}`);
1875
- }
1950
+ async function runFuzzMatrixResults(configPath, selectors, fuzzerSelectors, modes, overrides, reporter) {
1876
1951
  const results = [];
1877
- for (const file of files) {
1878
- const fileResults = [];
1879
- for (const modeName of modes) {
1880
- const modeResults = await fuzz(configPath, [file], modeName, overrides);
1952
+ for (const modeName of modes) {
1953
+ const files = await resolveSelectedFuzzFiles(configPath, selectors, [modeName]);
1954
+ if (!files.length) {
1955
+ continue;
1956
+ }
1957
+ for (const file of files) {
1958
+ const fileResults = [];
1959
+ const modeResults = await fuzz(configPath, [file], modeName, overrides, fuzzerSelectors);
1881
1960
  fileResults.push(...modeResults);
1882
1961
  results.push(...modeResults);
1962
+ reporter?.onFuzzFileComplete?.({ file, results: fileResults });
1883
1963
  }
1884
- reporter?.onFuzzFileComplete?.({ file, results: fileResults });
1964
+ }
1965
+ if (!results.length) {
1966
+ throw new Error(`No fuzz files matched: ${selectors.length ? selectors.join(", ") : "configured input patterns"}`);
1885
1967
  }
1886
1968
  return results;
1887
1969
  }
@@ -2141,13 +2223,21 @@ async function resolveSelectedFiles(configPath, selectors, warn = true) {
2141
2223
  const specs = matches.filter((file) => file.endsWith(".spec.ts"));
2142
2224
  return [...new Set(specs)].sort((a, b) => a.localeCompare(b));
2143
2225
  }
2144
- async function resolveSelectedFuzzFiles(configPath, selectors) {
2226
+ async function resolveSelectedFuzzFiles(configPath, selectors, modes = [undefined]) {
2145
2227
  const resolvedConfigPath = configPath ?? path.join(process.cwd(), "./as-test.config.json");
2146
- const config = loadConfig(resolvedConfigPath, false);
2147
- const patterns = resolveFuzzPatterns(config.fuzz.input, selectors);
2148
- const matches = await glob(patterns);
2149
- const fuzzFiles = matches.filter((file) => file.endsWith(".fuzz.ts"));
2150
- return [...new Set(fuzzFiles)].sort((a, b) => a.localeCompare(b));
2228
+ const files = new Set();
2229
+ for (const modeName of modes) {
2230
+ const loaded = loadConfig(resolvedConfigPath, false);
2231
+ const applied = applyMode(loaded, modeName);
2232
+ const config = applied.config;
2233
+ const patterns = resolveFuzzPatterns(config.fuzz.input, selectors);
2234
+ const matches = await glob(patterns);
2235
+ for (const file of matches) {
2236
+ if (file.endsWith(".fuzz.ts"))
2237
+ files.add(file);
2238
+ }
2239
+ }
2240
+ return [...files].sort((a, b) => a.localeCompare(b));
2151
2241
  }
2152
2242
  async function resolveSelectedTestInputs(configPath, selectors) {
2153
2243
  const [specs, fuzz] = await Promise.all([
@@ -2589,9 +2679,9 @@ async function listExecutionPlan(command, configPath, selectors, modes, listFlag
2589
2679
  return;
2590
2680
  const specFiles = command == "fuzz" ? [] : await resolveSelectedFiles(configPath, selectors);
2591
2681
  const fuzzFiles = command == "fuzz"
2592
- ? await resolveSelectedFuzzFiles(configPath, selectors)
2682
+ ? await resolveSelectedFuzzFiles(configPath, selectors, modes)
2593
2683
  : command == "test" && fuzzEnabled
2594
- ? await resolveSelectedFuzzFiles(configPath, selectors)
2684
+ ? await resolveSelectedFuzzFiles(configPath, selectors, modes)
2595
2685
  : [];
2596
2686
  const files = command == "fuzz" ? fuzzFiles : specFiles;
2597
2687
  if (!specFiles.length && !fuzzFiles.length) {
@@ -2637,8 +2727,7 @@ async function listExecutionPlan(command, configPath, selectors, modes, listFlag
2637
2727
  }
2638
2728
  }
2639
2729
  const envOverrides = {
2640
- ...config.env,
2641
- ...(modeName ? (config.modes[modeName]?.env ?? {}) : {}),
2730
+ ...active.env,
2642
2731
  ...(command == "build"
2643
2732
  ? active.buildOptions.env
2644
2733
  : command == "run" || command == "test"
@@ -329,7 +329,7 @@ function renderFailedFuzzers(results) {
329
329
  for (const result of results) {
330
330
  for (const modeResult of result.modes) {
331
331
  const relativeFile = toRelativeResultPath(modeResult.file);
332
- const repro = buildFuzzReproCommand(relativeFile, modeResult.seed, modeResult.modeName);
332
+ const repro = buildFuzzReproCommand(relativeFile, modeResult.seed, modeResult.modeName, modeResult.fuzzers[0]?.selector);
333
333
  if (modeResult.crashes > 0 && !modeResult.fuzzers.length) {
334
334
  if (!rendered) {
335
335
  console.log("");
@@ -353,18 +353,19 @@ function renderFailedFuzzers(results) {
353
353
  console.log("");
354
354
  rendered = true;
355
355
  }
356
+ const fuzzerRepro = buildFuzzReproCommand(relativeFile, modeResult.seed, modeResult.modeName, fuzzer.selector);
356
357
  console.log(`${chalk.bgRed(" FAIL ")} ${formatFuzzFailureTitle(modeResult.file, fuzzer.name)}`);
357
358
  if (fuzzer.failure) {
358
359
  renderAssertionFailureDetails(fuzzer.failure.left, fuzzer.failure.right, fuzzer.failure.message);
359
360
  }
360
361
  console.log(chalk.dim(`Mode: ${modeResult.modeName}`));
361
362
  console.log(chalk.dim(`Runs: ${fuzzer.passed + fuzzer.failed + fuzzer.crashed} completed (${fuzzer.passed} passed, ${fuzzer.failed} failed, ${fuzzer.crashed} crashed)`));
362
- console.log(chalk.dim(`Repro: ${repro}`));
363
+ console.log(chalk.dim(`Repro: ${fuzzerRepro}`));
363
364
  console.log(chalk.dim(`Seed: ${modeResult.seed}`));
364
365
  if (fuzzer.failures?.length) {
365
366
  console.log(chalk.dim(`Failing seeds: ${formatFailingSeeds(fuzzer)}`));
366
367
  for (const failure of fuzzer.failures) {
367
- console.log(chalk.dim(`Repro ${failure.run + 1}: ${buildFuzzReproCommand(relativeFile, failure.seed, modeResult.modeName, 1)}`));
368
+ console.log(chalk.dim(`Repro ${failure.run + 1}: ${buildFuzzReproCommand(relativeFile, failure.seed, modeResult.modeName, fuzzer.selector, 1)}`));
368
369
  if (failure.input) {
369
370
  console.log(chalk.dim(`Input ${failure.run + 1}: ${JSON.stringify(failure.input)}`));
370
371
  }
@@ -405,10 +406,11 @@ function averageFuzzModeTime(results) {
405
406
  return 0;
406
407
  return results.reduce((sum, result) => sum + result.time, 0) / results.length;
407
408
  }
408
- function buildFuzzReproCommand(file, seed, modeName, runs) {
409
+ function buildFuzzReproCommand(file, seed, modeName, fuzzer, runs) {
409
410
  const modeArg = modeName != "default" ? ` --mode ${modeName}` : "";
411
+ const fuzzerArg = fuzzer?.length ? ` --fuzzer ${fuzzer}` : "";
410
412
  const runsArg = typeof runs == "number" ? ` --runs ${runs}` : "";
411
- return `ast fuzz ${file}${modeArg} --seed ${seed}${runsArg}`;
413
+ return `ast fuzz ${file}${modeArg}${fuzzerArg} --seed ${seed}${runsArg}`;
412
414
  }
413
415
  function formatFailingSeeds(fuzzer) {
414
416
  return (fuzzer.failures ?? []).map((failure) => String(failure.seed)).join(", ");
@@ -467,9 +469,10 @@ function renderFailedSuites(failedEntries) {
467
469
  collectSuiteFailures(failed, file, [], printed);
468
470
  }
469
471
  }
470
- function collectSuiteFailures(suite, file, path, printed) {
472
+ function collectSuiteFailures(suite, file, path, printed, inheritedModeName = "") {
471
473
  const suiteAny = suite;
472
474
  const nextPath = [...path, String(suiteAny.description ?? "unknown")];
475
+ const modeName = String(suiteAny.modeName ?? inheritedModeName);
473
476
  const tests = Array.isArray(suiteAny.tests)
474
477
  ? suiteAny.tests
475
478
  : [];
@@ -481,7 +484,7 @@ function collectSuiteFailures(suite, file, path, printed) {
481
484
  const title = `${nextPath.join(" > ")}#${assertionIndex}`;
482
485
  const loc = String(test.location ?? "");
483
486
  const where = loc.length ? `${file}:${loc}` : file;
484
- const modeName = String(suiteAny.modeName ?? "");
487
+ const suitePath = String(suiteAny.path ?? "");
485
488
  const message = String(test.message ?? "");
486
489
  const dedupeKey = `${file}::${modeName}::${title}::${String(test.left)}::${String(test.right)}::${message}`;
487
490
  if (printed.has(dedupeKey))
@@ -491,15 +494,22 @@ function collectSuiteFailures(suite, file, path, printed) {
491
494
  if (modeName.length) {
492
495
  console.log(chalk.dim(`Mode: ${modeName}`));
493
496
  }
497
+ if (suitePath.length) {
498
+ console.log(chalk.dim(`Repro: ${buildSuiteReproCommand(toRelativeResultPath(file), suitePath, modeName)}`));
499
+ }
494
500
  renderAssertionFailureDetails(test.left, test.right, message);
495
501
  }
496
502
  const suites = Array.isArray(suiteAny.suites)
497
503
  ? suiteAny.suites
498
504
  : [];
499
505
  for (const sub of suites) {
500
- collectSuiteFailures(sub, file, nextPath, printed);
506
+ collectSuiteFailures(sub, file, nextPath, printed, modeName);
501
507
  }
502
508
  }
509
+ function buildSuiteReproCommand(file, suitePath, modeName) {
510
+ const modeArg = modeName && modeName != "default" ? ` --mode ${modeName}` : "";
511
+ return `ast run ${file}${modeArg} --suite ${suitePath}`;
512
+ }
503
513
  function normalizeFailureMessage(message) {
504
514
  return message.replace(/\r\n/g, "\n").trim();
505
515
  }
package/bin/types.js CHANGED
@@ -60,9 +60,7 @@ export class Runtime {
60
60
  }
61
61
  export class ModeConfig {
62
62
  constructor() {
63
- this.buildOptions = {};
64
- this.runOptions = {};
65
- this.env = {};
63
+ this.config = new Config();
66
64
  }
67
65
  }
68
66
  export class ReporterConfig {