as-test 1.1.0 → 1.1.2

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
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import chalk from "chalk";
3
- import { build, formatInvocation as formatBuildInvocation, getBuildInvocationPreview, } from "./commands/build.js";
3
+ import { build, BuildFailureError, formatInvocation as formatBuildInvocation, getBuildInvocationPreview, } from "./commands/build.js";
4
4
  import { createRunReporter, run } from "./commands/run.js";
5
5
  import { executeBuildCommand } from "./commands/build.js";
6
6
  import { executeRunCommand } from "./commands/run.js";
@@ -10,7 +10,7 @@ import { executeInitCommand } from "./commands/init.js";
10
10
  import { executeDoctorCommand } from "./commands/doctor.js";
11
11
  import { executeCleanCommand } from "./commands/clean.js";
12
12
  import { fuzz } from "./commands/fuzz-core.js";
13
- import { applyMode, getDefaultModeNames, formatTime, getCliVersion, loadConfig, resolveModeNames, } from "./util.js";
13
+ import { applyMode, formatTime, formatSpecDisplayPath, getDefaultModeNames, getCliVersion, loadConfig, resolveModeNames, } from "./util.js";
14
14
  import * as path from "path";
15
15
  import { spawnSync } from "child_process";
16
16
  import { glob } from "glob";
@@ -134,7 +134,7 @@ else if (COMMANDS.includes(args[0])) {
134
134
  });
135
135
  }
136
136
  else if (command === "clean") {
137
- executeCleanCommand(configPath, selectedModes, resolveExecutionModes).catch((error) => {
137
+ executeCleanCommand(_args, configPath, selectedModes, resolveExecutionModes).catch((error) => {
138
138
  printCliError(error);
139
139
  process.exit(1);
140
140
  });
@@ -370,7 +370,7 @@ function printCommandHelp(command) {
370
370
  }
371
371
  if (command == "clean") {
372
372
  process.stdout.write(chalk.bold("Usage: ast clean [flags]\n\n"));
373
- process.stdout.write("Remove configured build outputs, crash reports, and logs for the selected modes.\n\n");
373
+ process.stdout.write("Remove configured build outputs, crash reports, and logs.\n\n");
374
374
  process.stdout.write(chalk.bold("Flags:\n"));
375
375
  process.stdout.write(" --config <path> Use a specific config file\n");
376
376
  process.stdout.write(" --mode <name[,name...]> Clean one or multiple named modes\n");
@@ -1069,10 +1069,12 @@ function resolveCommandTokens(rawArgs, command) {
1069
1069
  }
1070
1070
  async function buildFileForMode(args) {
1071
1071
  if (args.buildPool) {
1072
+ const buildInvocation = await getBuildInvocationPreview(args.configPath, args.file, args.modeName, args.buildFeatureToggles);
1072
1073
  await args.buildPool.buildFileMode({
1073
1074
  configPath: args.configPath,
1074
1075
  file: args.file,
1075
1076
  modeName: args.modeName,
1077
+ buildCommand: formatBuildInvocation(buildInvocation),
1076
1078
  featureToggles: args.buildFeatureToggles,
1077
1079
  });
1078
1080
  }
@@ -1103,21 +1105,30 @@ async function runTestSequential(runFlags, configPath, selectors, suiteSelectors
1103
1105
  const duplicateSpecBasenames = resolveDuplicateSpecBasenames(files);
1104
1106
  for (const file of files) {
1105
1107
  const buildStartedAt = Date.now();
1106
- await build(configPath, [file], modeName, buildFeatureToggles);
1107
- buildIntervals.push({ start: buildStartedAt, end: Date.now() });
1108
- const buildInvocation = await getBuildInvocationPreview(configPath, file, modeName, buildFeatureToggles);
1109
- const artifactKey = resolvePerFileArtifactKey(file, duplicateSpecBasenames);
1110
- const result = await run(runFlags, configPath, [file], false, {
1111
- reporter,
1112
- webSession,
1113
- suiteSelectors,
1114
- emitRunStart: false,
1115
- emitRunComplete: false,
1116
- logFileName: `test.${artifactKey}.log.json`,
1117
- coverageFileName: `coverage.${artifactKey}.log.json`,
1118
- buildCommand: formatBuildInvocation(buildInvocation),
1119
- modeName,
1120
- });
1108
+ let result;
1109
+ try {
1110
+ await build(configPath, [file], modeName, buildFeatureToggles);
1111
+ buildIntervals.push({ start: buildStartedAt, end: Date.now() });
1112
+ const buildInvocation = await getBuildInvocationPreview(configPath, file, modeName, buildFeatureToggles);
1113
+ const artifactKey = resolvePerFileArtifactKey(file, duplicateSpecBasenames);
1114
+ result = await run(runFlags, configPath, [file], false, {
1115
+ reporter,
1116
+ webSession,
1117
+ suiteSelectors,
1118
+ emitRunStart: false,
1119
+ emitRunComplete: false,
1120
+ logFileName: `test.${artifactKey}.log.json`,
1121
+ coverageFileName: `coverage.${artifactKey}.log.json`,
1122
+ buildCommand: formatBuildInvocation(buildInvocation),
1123
+ modeName,
1124
+ });
1125
+ }
1126
+ catch (error) {
1127
+ const buildFailure = getBuildFailureErrorLike(error);
1128
+ if (!buildFailure)
1129
+ throw error;
1130
+ result = createBuildFailureRunResult(buildFailure);
1131
+ }
1121
1132
  results.push(result);
1122
1133
  if (result?.failed)
1123
1134
  failed = true;
@@ -1304,7 +1315,7 @@ async function runRuntimeMatrix(runFlags, configPath, selectors, suiteSelectors,
1304
1315
  const buildIntervals = [];
1305
1316
  for (let fileIndex = 0; fileIndex < files.length; fileIndex++) {
1306
1317
  const file = files[fileIndex];
1307
- const fileName = path.basename(file);
1318
+ const fileName = formatSpecDisplayPath(file);
1308
1319
  const fileResults = [];
1309
1320
  const modeTimes = modes.map(() => "...");
1310
1321
  if (liveMatrix) {
@@ -1480,7 +1491,7 @@ async function runTestMatrix(runFlags, configPath, selectors, suiteSelectors, fu
1480
1491
  const buildIntervals = [];
1481
1492
  for (let fileIndex = 0; fileIndex < files.length; fileIndex++) {
1482
1493
  const file = files[fileIndex];
1483
- const fileName = path.basename(file);
1494
+ const fileName = formatSpecDisplayPath(file);
1484
1495
  const fileResults = [];
1485
1496
  const modeTimes = modes.map(() => "...");
1486
1497
  if (liveMatrix) {
@@ -1488,6 +1499,7 @@ async function runTestMatrix(runFlags, configPath, selectors, suiteSelectors, fu
1488
1499
  }
1489
1500
  for (let i = 0; i < modes.length; i++) {
1490
1501
  const modeName = modes[i];
1502
+ let result;
1491
1503
  try {
1492
1504
  const buildStartedAt = Date.now();
1493
1505
  await buildFileForMode({
@@ -1499,7 +1511,7 @@ async function runTestMatrix(runFlags, configPath, selectors, suiteSelectors, fu
1499
1511
  buildIntervals.push({ start: buildStartedAt, end: Date.now() });
1500
1512
  const buildInvocation = await getBuildInvocationPreview(configPath, file, modeName, buildFeatureToggles);
1501
1513
  const artifactKey = resolvePerFileArtifactKey(file, duplicateSpecBasenames);
1502
- const result = await run(runFlags, configPath, [file], false, {
1514
+ result = await run(runFlags, configPath, [file], false, {
1503
1515
  reporter: silentReporter,
1504
1516
  reporterKind: "default",
1505
1517
  emitRunStart: false,
@@ -1509,23 +1521,27 @@ async function runTestMatrix(runFlags, configPath, selectors, suiteSelectors, fu
1509
1521
  buildCommand: formatBuildInvocation(buildInvocation),
1510
1522
  modeName,
1511
1523
  });
1512
- modeTimes[i] = formatMatrixModeTime(result.stats.time);
1513
- if (liveMatrix) {
1514
- renderMatrixLiveLine(fileName, modeLabels, modeTimes, showPerModeTimes);
1515
- }
1516
- if (result.failed) {
1517
- modeState[i].failed = true;
1518
- }
1519
- else if (result.stats.passedFiles > 0) {
1520
- modeState[i].passed = true;
1521
- }
1522
- fileResults.push(result);
1523
- allResults.push(result);
1524
1524
  }
1525
1525
  catch (error) {
1526
- clearLiveLine();
1527
- throw error;
1526
+ const buildFailure = getBuildFailureErrorLike(error);
1527
+ if (!buildFailure) {
1528
+ clearLiveLine();
1529
+ throw error;
1530
+ }
1531
+ result = createBuildFailureRunResult(buildFailure);
1532
+ }
1533
+ modeTimes[i] = formatMatrixModeTime(result.stats.time);
1534
+ if (liveMatrix) {
1535
+ renderMatrixLiveLine(fileName, modeLabels, modeTimes, showPerModeTimes);
1536
+ }
1537
+ if (result.failed) {
1538
+ modeState[i].failed = true;
1528
1539
  }
1540
+ else if (result.stats.passedFiles > 0) {
1541
+ modeState[i].passed = true;
1542
+ }
1543
+ fileResults.push(result);
1544
+ allResults.push(result);
1529
1545
  }
1530
1546
  if (reporterSession.reporterKind == "default") {
1531
1547
  renderMatrixFileResult(fileName, modeLabels, fileResults, modeTimes, liveMatrix, showPerModeTimes);
@@ -1610,22 +1626,31 @@ async function runRuntimeSingleParallel(runFlags, configPath, selectors, suiteSe
1610
1626
  const poolWidth = Math.max(runFlags.buildJobs, runFlags.runJobs);
1611
1627
  await runOrderedPool(files, poolWidth, async (file, index) => {
1612
1628
  const token = useQueueDisplay
1613
- ? renderQueuedFileStart(queueDisplay, path.basename(file))
1629
+ ? renderQueuedFileStart(queueDisplay, formatSpecDisplayPath(file))
1614
1630
  : null;
1615
1631
  const buffered = useQueueDisplay
1616
1632
  ? await createBufferedReporter(configPath, runFlags.reporterPath, modeName)
1617
1633
  : null;
1618
- const result = await runLimit(() => run({ ...runFlags, clean: true }, configPath, [file], false, {
1619
- reporter: buffered?.reporter,
1620
- reporterKind: buffered?.reporterKind,
1621
- modeName,
1622
- suiteSelectors,
1623
- emitRunComplete: false,
1624
- fileSummaryTotal: 1,
1625
- modeSummaryTotal,
1626
- modeSummaryExecuted: 1,
1627
- buildCommandsByFile: { [file]: buildCommandsByFile[file] ?? "" },
1628
- }));
1634
+ let result;
1635
+ try {
1636
+ result = await runLimit(() => run({ ...runFlags, clean: true }, configPath, [file], false, {
1637
+ reporter: buffered?.reporter,
1638
+ reporterKind: buffered?.reporterKind,
1639
+ modeName,
1640
+ suiteSelectors,
1641
+ emitRunComplete: false,
1642
+ fileSummaryTotal: 1,
1643
+ modeSummaryTotal,
1644
+ modeSummaryExecuted: 1,
1645
+ buildCommandsByFile: { [file]: buildCommandsByFile[file] ?? "" },
1646
+ }));
1647
+ }
1648
+ catch (error) {
1649
+ const buildFailure = getBuildFailureErrorLike(error);
1650
+ if (!buildFailure)
1651
+ throw error;
1652
+ result = createBuildFailureRunResult(buildFailure);
1653
+ }
1629
1654
  buffered?.reporter.flush?.();
1630
1655
  results[index] = result;
1631
1656
  if (buffered && token != null) {
@@ -1676,7 +1701,7 @@ async function runRuntimeMatrixParallel(runFlags, configPath, selectors, suiteSe
1676
1701
  const buildIntervals = [];
1677
1702
  try {
1678
1703
  await runOrderedPool(files, poolWidth, async (file, fileIndex) => {
1679
- const fileName = path.basename(file);
1704
+ const fileName = formatSpecDisplayPath(file);
1680
1705
  const token = useQueueDisplay
1681
1706
  ? renderQueuedFileStart(queueDisplay, fileName)
1682
1707
  : null;
@@ -1684,28 +1709,37 @@ async function runRuntimeMatrixParallel(runFlags, configPath, selectors, suiteSe
1684
1709
  const modeTimes = modes.map(() => "...");
1685
1710
  for (let i = 0; i < modes.length; i++) {
1686
1711
  const modeName = modes[i];
1687
- const buildStartedAt = Date.now();
1688
- await buildFileForMode({
1689
- configPath,
1690
- file,
1691
- modeName,
1692
- buildFeatureToggles: {},
1693
- buildPool,
1694
- });
1695
- buildIntervals.push({ start: buildStartedAt, end: Date.now() });
1696
- const buildInvocation = await getBuildInvocationPreview(configPath, file, modeName, {});
1697
- const artifactKey = resolvePerFileArtifactKey(file, duplicateSpecBasenames);
1698
- const result = await run(runFlags, configPath, [file], false, {
1699
- reporter: silentReporter,
1700
- reporterKind: "default",
1701
- suiteSelectors,
1702
- emitRunStart: false,
1703
- emitRunComplete: false,
1704
- logFileName: `run.${artifactKey}.log.json`,
1705
- coverageFileName: `coverage.${artifactKey}.log.json`,
1706
- buildCommand: formatBuildInvocation(buildInvocation),
1707
- modeName,
1708
- });
1712
+ let result;
1713
+ try {
1714
+ const buildStartedAt = Date.now();
1715
+ await buildFileForMode({
1716
+ configPath,
1717
+ file,
1718
+ modeName,
1719
+ buildFeatureToggles: {},
1720
+ buildPool,
1721
+ });
1722
+ buildIntervals.push({ start: buildStartedAt, end: Date.now() });
1723
+ const buildInvocation = await getBuildInvocationPreview(configPath, file, modeName, {});
1724
+ const artifactKey = resolvePerFileArtifactKey(file, duplicateSpecBasenames);
1725
+ result = await run(runFlags, configPath, [file], false, {
1726
+ reporter: silentReporter,
1727
+ reporterKind: "default",
1728
+ suiteSelectors,
1729
+ emitRunStart: false,
1730
+ emitRunComplete: false,
1731
+ logFileName: `run.${artifactKey}.log.json`,
1732
+ coverageFileName: `coverage.${artifactKey}.log.json`,
1733
+ buildCommand: formatBuildInvocation(buildInvocation),
1734
+ modeName,
1735
+ });
1736
+ }
1737
+ catch (error) {
1738
+ const buildFailure = getBuildFailureErrorLike(error);
1739
+ if (!buildFailure)
1740
+ throw error;
1741
+ result = createBuildFailureRunResult(buildFailure);
1742
+ }
1709
1743
  modeTimes[i] = formatMatrixModeTime(result.stats.time);
1710
1744
  fileResults.push(result);
1711
1745
  }
@@ -1780,32 +1814,41 @@ async function runTestSingleParallel(runFlags, configPath, selectors, suiteSelec
1780
1814
  try {
1781
1815
  await runOrderedPool(files, poolWidth, async (file, index) => {
1782
1816
  const token = useQueueDisplay
1783
- ? renderQueuedFileStart(queueDisplay, path.basename(file))
1817
+ ? renderQueuedFileStart(queueDisplay, formatSpecDisplayPath(file))
1784
1818
  : null;
1785
- const buildStartedAt = Date.now();
1786
- await buildFileForMode({
1787
- configPath,
1788
- file,
1789
- modeName,
1790
- buildFeatureToggles,
1791
- buildPool,
1792
- });
1793
- buildIntervals.push({ start: buildStartedAt, end: Date.now() });
1794
- const buildInvocation = await getBuildInvocationPreview(configPath, file, modeName, buildFeatureToggles);
1795
- const artifactKey = resolvePerFileArtifactKey(file, duplicateSpecBasenames);
1796
1819
  const buffered = useQueueDisplay
1797
1820
  ? await createBufferedReporter(configPath, runFlags.reporterPath, modeName)
1798
1821
  : null;
1799
- const result = await run({ ...runFlags, clean: true }, configPath, [file], false, {
1800
- reporter: buffered?.reporter,
1801
- reporterKind: buffered?.reporterKind,
1802
- suiteSelectors,
1803
- emitRunComplete: false,
1804
- logFileName: `test.${artifactKey}.log.json`,
1805
- coverageFileName: `coverage.${artifactKey}.log.json`,
1806
- buildCommand: formatBuildInvocation(buildInvocation),
1807
- modeName,
1808
- });
1822
+ let result;
1823
+ try {
1824
+ const buildStartedAt = Date.now();
1825
+ await buildFileForMode({
1826
+ configPath,
1827
+ file,
1828
+ modeName,
1829
+ buildFeatureToggles,
1830
+ buildPool,
1831
+ });
1832
+ buildIntervals.push({ start: buildStartedAt, end: Date.now() });
1833
+ const buildInvocation = await getBuildInvocationPreview(configPath, file, modeName, buildFeatureToggles);
1834
+ const artifactKey = resolvePerFileArtifactKey(file, duplicateSpecBasenames);
1835
+ result = await run({ ...runFlags, clean: true }, configPath, [file], false, {
1836
+ reporter: buffered?.reporter,
1837
+ reporterKind: buffered?.reporterKind,
1838
+ suiteSelectors,
1839
+ emitRunComplete: false,
1840
+ logFileName: `test.${artifactKey}.log.json`,
1841
+ coverageFileName: `coverage.${artifactKey}.log.json`,
1842
+ buildCommand: formatBuildInvocation(buildInvocation),
1843
+ modeName,
1844
+ });
1845
+ }
1846
+ catch (error) {
1847
+ const buildFailure = getBuildFailureErrorLike(error);
1848
+ if (!buildFailure)
1849
+ throw error;
1850
+ result = createBuildFailureRunResult(buildFailure);
1851
+ }
1809
1852
  buffered?.reporter.flush?.();
1810
1853
  results[index] = result;
1811
1854
  if (buffered && token != null) {
@@ -1881,7 +1924,7 @@ async function runTestMatrixParallel(runFlags, configPath, selectors, suiteSelec
1881
1924
  const buildIntervals = [];
1882
1925
  try {
1883
1926
  await runOrderedPool(files, poolWidth, async (file, fileIndex) => {
1884
- const fileName = path.basename(file);
1927
+ const fileName = formatSpecDisplayPath(file);
1885
1928
  const token = useQueueDisplay
1886
1929
  ? renderQueuedFileStart(queueDisplay, fileName)
1887
1930
  : null;
@@ -1889,28 +1932,37 @@ async function runTestMatrixParallel(runFlags, configPath, selectors, suiteSelec
1889
1932
  const modeTimes = modes.map(() => "...");
1890
1933
  for (let i = 0; i < modes.length; i++) {
1891
1934
  const modeName = modes[i];
1892
- const buildStartedAt = Date.now();
1893
- await buildFileForMode({
1894
- configPath,
1895
- file,
1896
- modeName,
1897
- buildFeatureToggles,
1898
- buildPool,
1899
- });
1900
- buildIntervals.push({ start: buildStartedAt, end: Date.now() });
1901
- const buildInvocation = await getBuildInvocationPreview(configPath, file, modeName, buildFeatureToggles);
1902
- const artifactKey = resolvePerFileArtifactKey(file, duplicateSpecBasenames);
1903
- const result = await run(runFlags, configPath, [file], false, {
1904
- reporter: silentReporter,
1905
- reporterKind: "default",
1906
- suiteSelectors,
1907
- emitRunStart: false,
1908
- emitRunComplete: false,
1909
- logFileName: `test.${artifactKey}.log.json`,
1910
- coverageFileName: `coverage.${artifactKey}.log.json`,
1911
- buildCommand: formatBuildInvocation(buildInvocation),
1912
- modeName,
1913
- });
1935
+ let result;
1936
+ try {
1937
+ const buildStartedAt = Date.now();
1938
+ await buildFileForMode({
1939
+ configPath,
1940
+ file,
1941
+ modeName,
1942
+ buildFeatureToggles,
1943
+ buildPool,
1944
+ });
1945
+ buildIntervals.push({ start: buildStartedAt, end: Date.now() });
1946
+ const buildInvocation = await getBuildInvocationPreview(configPath, file, modeName, buildFeatureToggles);
1947
+ const artifactKey = resolvePerFileArtifactKey(file, duplicateSpecBasenames);
1948
+ result = await run(runFlags, configPath, [file], false, {
1949
+ reporter: silentReporter,
1950
+ reporterKind: "default",
1951
+ suiteSelectors,
1952
+ emitRunStart: false,
1953
+ emitRunComplete: false,
1954
+ logFileName: `test.${artifactKey}.log.json`,
1955
+ coverageFileName: `coverage.${artifactKey}.log.json`,
1956
+ buildCommand: formatBuildInvocation(buildInvocation),
1957
+ modeName,
1958
+ });
1959
+ }
1960
+ catch (error) {
1961
+ const buildFailure = getBuildFailureErrorLike(error);
1962
+ if (!buildFailure)
1963
+ throw error;
1964
+ result = createBuildFailureRunResult(buildFailure);
1965
+ }
1914
1966
  modeTimes[i] = formatMatrixModeTime(result.stats.time);
1915
1967
  fileResults.push(result);
1916
1968
  }
@@ -2005,7 +2057,9 @@ async function runFuzzMatrixResultsParallel(configPath, selectors, fuzzerSelecto
2005
2057
  async function runFuzzMatrixResults(configPath, selectors, fuzzerSelectors, modes, overrides, reporter) {
2006
2058
  const results = [];
2007
2059
  for (const modeName of modes) {
2008
- const files = await resolveSelectedFuzzFiles(configPath, selectors, [modeName]);
2060
+ const files = await resolveSelectedFuzzFiles(configPath, selectors, [
2061
+ modeName,
2062
+ ]);
2009
2063
  if (!files.length) {
2010
2064
  continue;
2011
2065
  }
@@ -2211,6 +2265,133 @@ function buildSingleModeSummary(stats, snapshotSummary, totalModes) {
2211
2265
  total,
2212
2266
  };
2213
2267
  }
2268
+ function createBuildFailureRunResult(error) {
2269
+ const message = formatBuildFailureMessage(error);
2270
+ const report = {
2271
+ file: error.file,
2272
+ modeName: error.mode,
2273
+ suites: [
2274
+ {
2275
+ file: error.file,
2276
+ description: formatSpecDisplayPath(error.file),
2277
+ depth: 0,
2278
+ kind: "build-error",
2279
+ verdict: "fail",
2280
+ time: {
2281
+ start: 0,
2282
+ end: 0,
2283
+ },
2284
+ suites: [],
2285
+ logs: [],
2286
+ tests: [
2287
+ {
2288
+ order: 0,
2289
+ type: "build-error",
2290
+ verdict: "fail",
2291
+ left: null,
2292
+ right: null,
2293
+ instr: "build failed before the test could run",
2294
+ message,
2295
+ location: "",
2296
+ },
2297
+ ],
2298
+ modeName: error.mode,
2299
+ buildCommand: formatBuildInvocation(error.invocation),
2300
+ runCommand: "",
2301
+ },
2302
+ ],
2303
+ coverage: {
2304
+ total: 0,
2305
+ covered: 0,
2306
+ uncovered: 0,
2307
+ percent: 100,
2308
+ points: [],
2309
+ },
2310
+ runCommand: "",
2311
+ buildCommand: formatBuildInvocation(error.invocation),
2312
+ snapshotSummary: {
2313
+ matched: 0,
2314
+ created: 0,
2315
+ updated: 0,
2316
+ failed: 0,
2317
+ },
2318
+ };
2319
+ return {
2320
+ failed: true,
2321
+ buildTime: 0,
2322
+ stats: {
2323
+ passedFiles: 0,
2324
+ failedFiles: 1,
2325
+ skippedFiles: 0,
2326
+ passedSuites: 0,
2327
+ failedSuites: 1,
2328
+ skippedSuites: 0,
2329
+ passedTests: 0,
2330
+ failedTests: 1,
2331
+ skippedTests: 0,
2332
+ time: 0,
2333
+ failedEntries: [
2334
+ {
2335
+ ...report.suites[0],
2336
+ file: error.file,
2337
+ modeName: error.mode,
2338
+ buildCommand: report.buildCommand,
2339
+ runCommand: "",
2340
+ },
2341
+ ],
2342
+ },
2343
+ snapshotSummary: report.snapshotSummary,
2344
+ coverageSummary: {
2345
+ enabled: false,
2346
+ showPoints: false,
2347
+ total: 0,
2348
+ covered: 0,
2349
+ uncovered: 0,
2350
+ percent: 100,
2351
+ files: [],
2352
+ },
2353
+ reports: [report],
2354
+ };
2355
+ }
2356
+ function formatBuildFailureMessage(error) {
2357
+ const parts = [];
2358
+ const stderr = error.stderr.trim();
2359
+ const stdout = error.stdout.trim();
2360
+ if (stderr.length) {
2361
+ parts.push(`stderr:\n${stderr}`);
2362
+ }
2363
+ if (stdout.length) {
2364
+ parts.push(`stdout:\n${stdout}`);
2365
+ }
2366
+ if (error.crashLogPath.length) {
2367
+ parts.push(`Crash log:\n${error.crashLogPath}`);
2368
+ }
2369
+ return parts.join("\n\n") || "build failed with no compiler output";
2370
+ }
2371
+ function getBuildFailureErrorLike(error) {
2372
+ if (error instanceof BuildFailureError) {
2373
+ return error;
2374
+ }
2375
+ if (!error || typeof error != "object") {
2376
+ return null;
2377
+ }
2378
+ const candidate = error;
2379
+ if (candidate.name != "BuildFailureError") {
2380
+ return null;
2381
+ }
2382
+ if (typeof candidate.file != "string" ||
2383
+ typeof candidate.mode != "string" ||
2384
+ typeof candidate.stdout != "string" ||
2385
+ typeof candidate.stderr != "string" ||
2386
+ typeof candidate.crashLogPath != "string" ||
2387
+ !candidate.invocation ||
2388
+ typeof candidate.invocation != "object" ||
2389
+ typeof candidate.invocation.command != "string" ||
2390
+ !Array.isArray(candidate.invocation.args)) {
2391
+ return null;
2392
+ }
2393
+ return candidate;
2394
+ }
2214
2395
  function applyConfiguredFileTotalToStats(stats, fileSummaryTotal) {
2215
2396
  const total = Math.max(fileSummaryTotal, 0);
2216
2397
  const executed = stats.failedFiles + stats.passedFiles + stats.skippedFiles;
@@ -2319,7 +2500,7 @@ async function buildNoTestFilesMatchedError(configPath, selectors, includeFuzz =
2319
2500
  if (configuredFiles.length) {
2320
2501
  const sample = configuredFiles
2321
2502
  .slice(0, 5)
2322
- .map((file) => path.basename(file))
2503
+ .map((file) => formatSpecDisplayPath(file))
2323
2504
  .join(", ");
2324
2505
  lines.push(`Configured specs (${configuredFiles.length}): ${sample}${configuredFiles.length > 5 ? ", ..." : ""}`);
2325
2506
  }
@@ -2627,7 +2808,7 @@ async function handleMissingWebBrowsers(missing) {
2627
2808
  const details = "no web-capable browser was found in PATH, BROWSER, or Playwright cache";
2628
2809
  const selected = choosePreferredBrowserInstall(missing);
2629
2810
  const installCommand = selected == "webkit"
2630
- ? 'npx -y playwright install webkit'
2811
+ ? "npx -y playwright install webkit"
2631
2812
  : `npx -y playwright install ${selected}`;
2632
2813
  if (!canPromptForWebInstall()) {
2633
2814
  throw new Error(`web target requires a browser for mode(s) ${scope}; ${details}. Export BROWSER or install one with "${installCommand}".`);
@@ -2747,10 +2928,10 @@ function isPlaywrightBrowserExecutable(browser) {
2747
2928
  function extractPlaywrightDepsSummary(result) {
2748
2929
  const stdout = typeof result.stdout == "string"
2749
2930
  ? result.stdout
2750
- : result.stdout?.toString("utf8") ?? "";
2931
+ : (result.stdout?.toString("utf8") ?? "");
2751
2932
  const stderr = typeof result.stderr == "string"
2752
2933
  ? result.stderr
2753
- : result.stderr?.toString("utf8") ?? "";
2934
+ : (result.stderr?.toString("utf8") ?? "");
2754
2935
  return [stdout.trim(), stderr.trim()].filter(Boolean).join("\n");
2755
2936
  }
2756
2937
  function canPromptForWebInstall() {
@@ -2885,9 +3066,7 @@ function resolveSystemBrowserExecutable(browser) {
2885
3066
  "Firefox.app/Contents/MacOS/firefox",
2886
3067
  "Firefox Developer Edition.app/Contents/MacOS/firefox",
2887
3068
  ],
2888
- msedge: [
2889
- "Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
2890
- ],
3069
+ msedge: ["Microsoft Edge.app/Contents/MacOS/Microsoft Edge"],
2891
3070
  webkit: [],
2892
3071
  };
2893
3072
  for (const root of macSearchRoots) {