as-test 1.0.11 → 1.0.13

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,11 +284,13 @@ 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");
284
292
  process.stdout.write(" --fuzz-runs <value> Override fuzz iteration count, e.g. 500, 1.5x, +10%, +100000\n");
285
- process.stdout.write(" --fuzz-seed <n> Override fuzz seed for this run\n");
293
+ process.stdout.write(" --fuzz-seed <n> Pin fuzz seed for this run (default uses random seed)\n");
286
294
  process.stdout.write(" --parallel Run files through an ordered worker pool using an automatic worker count\n");
287
295
  process.stdout.write(" --jobs <n> Run files through an ordered worker pool\n");
288
296
  process.stdout.write(" --build-jobs <n> Limit concurrent build tasks (defaults to --jobs)\n");
@@ -303,7 +311,11 @@ function printCommandHelp(command) {
303
311
  process.stdout.write(" --config <path> Use a specific config file\n");
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
- process.stdout.write(" --seed <n> Override fuzz seed\n");
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,7 +1384,7 @@ 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) {
@@ -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
1517
  const fuzzFiles = await resolveSelectedFuzzFiles(configPath, selectors);
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,7 +1792,7 @@ 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) {
@@ -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,7 +1917,7 @@ 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) {
1920
+ async function runFuzzMatrixResultsParallel(configPath, selectors, fuzzerSelectors, modes, overrides, jobs, buildJobs, runJobs, clean) {
1848
1921
  const files = await resolveSelectedFuzzFiles(configPath, selectors);
1849
1922
  if (!files.length) {
1850
1923
  throw new Error(`No fuzz files matched: ${selectors.length ? selectors.join(", ") : "configured input patterns"}`);
@@ -1856,7 +1929,7 @@ async function runFuzzMatrixResultsParallel(configPath, selectors, modes, overri
1856
1929
  const token = renderQueuedFileStart(queueDisplay, path.basename(file));
1857
1930
  const fileResults = [];
1858
1931
  for (const modeName of modes) {
1859
- const modeResults = await fuzz(configPath, [file], modeName, overrides);
1932
+ const modeResults = await fuzz(configPath, [file], modeName, overrides, fuzzerSelectors);
1860
1933
  fileResults.push(...modeResults);
1861
1934
  }
1862
1935
  ordered[index] = fileResults;
@@ -1868,7 +1941,7 @@ async function runFuzzMatrixResultsParallel(configPath, selectors, modes, overri
1868
1941
  queueDisplay.flush();
1869
1942
  return ordered.flat();
1870
1943
  }
1871
- async function runFuzzMatrixResults(configPath, selectors, modes, overrides, reporter) {
1944
+ async function runFuzzMatrixResults(configPath, selectors, fuzzerSelectors, modes, overrides, reporter) {
1872
1945
  const files = await resolveSelectedFuzzFiles(configPath, selectors);
1873
1946
  if (!files.length) {
1874
1947
  throw new Error(`No fuzz files matched: ${selectors.length ? selectors.join(", ") : "configured input patterns"}`);
@@ -1877,7 +1950,7 @@ async function runFuzzMatrixResults(configPath, selectors, modes, overrides, rep
1877
1950
  for (const file of files) {
1878
1951
  const fileResults = [];
1879
1952
  for (const modeName of modes) {
1880
- const modeResults = await fuzz(configPath, [file], modeName, overrides);
1953
+ const modeResults = await fuzz(configPath, [file], modeName, overrides, fuzzerSelectors);
1881
1954
  fileResults.push(...modeResults);
1882
1955
  results.push(...modeResults);
1883
1956
  }
@@ -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
- if (fuzzer.failure?.message?.length) {
358
- console.log(chalk.dim(`Message: ${fuzzer.failure.message}`));
358
+ if (fuzzer.failure) {
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,69 +484,75 @@ 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))
488
491
  continue;
489
492
  printed.add(dedupeKey);
490
- const left = JSON.stringify(test.left);
491
- const right = JSON.stringify(test.right);
492
- if (left == "null" && right == "null") {
493
- console.log(`${chalk.bgRed(" FAIL ")} ${chalk.dim(title)} ${chalk.dim("(" + where + ")")}`);
494
- if (modeName.length) {
495
- console.log(chalk.dim(`Mode: ${modeName}`));
496
- }
497
- const normalizedMessage = normalizeFailureMessage(message);
498
- if (normalizedMessage.length) {
499
- for (const line of normalizedMessage.split("\n")) {
500
- console.log(chalk.dim(line));
501
- }
502
- }
503
- else {
504
- console.log(chalk.dim("runtime error"));
505
- }
506
- console.log("");
507
- continue;
508
- }
509
- const diffResult = diff(left, right);
510
- let expected = "";
511
- for (const res of diffResult.diff) {
512
- switch (res.type) {
513
- case "correct":
514
- expected += chalk.dim(res.value);
515
- break;
516
- case "extra":
517
- expected += chalk.red.strikethrough(res.value);
518
- break;
519
- case "missing":
520
- expected += chalk.bgBlack(res.value);
521
- break;
522
- case "wrong":
523
- expected += chalk.bgRed(res.value);
524
- break;
525
- case "untouched":
526
- case "spacer":
527
- break;
528
- }
529
- }
530
493
  console.log(`${chalk.bgRed(" FAIL ")} ${chalk.dim(title)} ${chalk.dim("(" + where + ")")}`);
531
494
  if (modeName.length) {
532
495
  console.log(chalk.dim(`Mode: ${modeName}`));
533
496
  }
534
- console.log(`${chalk.dim("(expected) ->")} ${expected}`);
535
- console.log(`${chalk.dim("(received) ->")} ${chalk.dim(left)}\n`);
497
+ if (suitePath.length) {
498
+ console.log(chalk.dim(`Repro: ${buildSuiteReproCommand(toRelativeResultPath(file), suitePath, modeName)}`));
499
+ }
500
+ renderAssertionFailureDetails(test.left, test.right, message);
536
501
  }
537
502
  const suites = Array.isArray(suiteAny.suites)
538
503
  ? suiteAny.suites
539
504
  : [];
540
505
  for (const sub of suites) {
541
- collectSuiteFailures(sub, file, nextPath, printed);
506
+ collectSuiteFailures(sub, file, nextPath, printed, modeName);
542
507
  }
543
508
  }
509
+ function buildSuiteReproCommand(file, suitePath, modeName) {
510
+ const modeArg = modeName && modeName != "default" ? ` --mode ${modeName}` : "";
511
+ return `ast run ${file}${modeArg} --suite ${suitePath}`;
512
+ }
544
513
  function normalizeFailureMessage(message) {
545
514
  return message.replace(/\r\n/g, "\n").trim();
546
515
  }
516
+ function renderAssertionFailureDetails(leftRaw, rightRaw, messageRaw) {
517
+ const left = JSON.stringify(leftRaw);
518
+ const right = JSON.stringify(rightRaw);
519
+ const message = String(messageRaw ?? "");
520
+ if (left == "null" && right == "null") {
521
+ const normalizedMessage = normalizeFailureMessage(message);
522
+ if (normalizedMessage.length) {
523
+ for (const line of normalizedMessage.split("\n")) {
524
+ console.log(chalk.dim(line));
525
+ }
526
+ }
527
+ else {
528
+ console.log(chalk.dim("runtime error"));
529
+ }
530
+ return;
531
+ }
532
+ const diffResult = diff(left, right);
533
+ let expected = "";
534
+ for (const res of diffResult.diff) {
535
+ switch (res.type) {
536
+ case "correct":
537
+ expected += chalk.dim(res.value);
538
+ break;
539
+ case "extra":
540
+ expected += chalk.red.strikethrough(res.value);
541
+ break;
542
+ case "missing":
543
+ expected += chalk.bgBlack(res.value);
544
+ break;
545
+ case "wrong":
546
+ expected += chalk.bgRed(res.value);
547
+ break;
548
+ case "untouched":
549
+ case "spacer":
550
+ break;
551
+ }
552
+ }
553
+ console.log(`${chalk.dim("(expected) ->")} ${expected}`);
554
+ console.log(`${chalk.dim("(received) ->")} ${chalk.dim(left)}\n`);
555
+ }
547
556
  function renderSnapshotSummary(snapshotSummary, leadingGap = true) {
548
557
  if (leadingGap) {
549
558
  console.log("");
package/bin/types.js CHANGED
@@ -77,7 +77,7 @@ export class FuzzConfig {
77
77
  constructor() {
78
78
  this.input = ["./assembly/__fuzz__/*.fuzz.ts"];
79
79
  this.runs = 1000;
80
- this.seed = 1337;
80
+ this.seed = -1;
81
81
  this.maxInputBytes = 4096;
82
82
  this.target = "bindings";
83
83
  this.corpusDir = "./.as-test/fuzz/corpus";
package/bin/util.js CHANGED
@@ -115,7 +115,7 @@ export function loadConfig(CONFIG_PATH, warn = false) {
115
115
  ? [fuzzRaw.input]
116
116
  : new FuzzConfig().input;
117
117
  config.fuzz.runs = normalizePositiveNumber(config.fuzz.runs, 1000);
118
- config.fuzz.seed = normalizeNonNegativeNumber(config.fuzz.seed, 1337);
118
+ config.fuzz.seed = normalizeNonNegativeNumber(config.fuzz.seed, -1);
119
119
  config.fuzz.maxInputBytes = normalizePositiveNumber(config.fuzz.maxInputBytes, 4096);
120
120
  config.fuzz.target =
121
121
  typeof config.fuzz.target == "string" && config.fuzz.target.length
@@ -1111,7 +1111,7 @@ export function getPkgRunner() {
1111
1111
  return "npx";
1112
1112
  }
1113
1113
  export function getExec(exec) {
1114
- const PATH = process.env.PATH.split(delimiter);
1114
+ const PATH = (process.env.PATH ?? "").split(delimiter);
1115
1115
  for (const pathDir of PATH) {
1116
1116
  const fullPath = join(pathDir, exec + (process.platform === "win32" ? ".exe" : ""));
1117
1117
  if (existsSync(fullPath)) {