as-test 1.1.1 → 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.
@@ -35,7 +35,10 @@ export function formatValue<T>(value: T, deep: boolean = false): string {
35
35
  let out = "Map(" + keys.length.toString() + ") { ";
36
36
  for (let i = 0; i < keys.length; i++) {
37
37
  if (i) out += ", ";
38
- out += formatValue(changetype<valueof<typeof keys>>(unchecked(keys[i])), true);
38
+ out += formatValue(
39
+ changetype<valueof<typeof keys>>(unchecked(keys[i])),
40
+ true,
41
+ );
39
42
  out += " => ";
40
43
  out += formatValue(
41
44
  changetype<valueof<typeof values>>(unchecked(values[i])),
@@ -73,31 +76,37 @@ export function formatValue<T>(value: T, deep: boolean = false): string {
73
76
  return nameof<T>();
74
77
  }
75
78
 
79
+
76
80
  @inline
77
81
  export function colorText(format: i32[], text: string): string {
78
82
  return `\u001b[${format[0].toString()}m${text}\u001b[${format[1].toString()}m`;
79
83
  }
80
84
 
85
+
81
86
  @inline
82
87
  export function red(text: string): string {
83
88
  return colorText([31, 39], text);
84
89
  }
85
90
 
91
+
86
92
  @inline
87
93
  export function green(text: string): string {
88
94
  return colorText([32, 39], text);
89
95
  }
90
96
 
97
+
91
98
  @inline
92
99
  export function bgRed(text: string): string {
93
100
  return colorText([41, 49], text);
94
101
  }
95
102
 
103
+
96
104
  @inline
97
105
  export function bgGreen(text: string): string {
98
106
  return colorText([42, 49], text);
99
107
  }
100
108
 
109
+
101
110
  @inline
102
111
  export function bold(text: string): string {
103
112
  return colorText([1, 22], text);
@@ -154,8 +154,7 @@ export function requestFuzzConfig(): FuzzConfigReply {
154
154
  const runs = body.slice(0, first);
155
155
  const seed =
156
156
  second >= 0 ? body.slice(first + 1, second) : body.slice(first + 1);
157
- const kind =
158
- second >= 0 && third >= 0 ? body.slice(second + 1, third) : "";
157
+ const kind = second >= 0 && third >= 0 ? body.slice(second + 1, third) : "";
159
158
  const value = third >= 0 ? body.slice(third + 1) : "";
160
159
  if (runs.length) reply.runs = I32.parseInt(runs);
161
160
  if (seed.length) reply.seed = U64.parseInt(seed);
@@ -21,6 +21,7 @@ export class BuildWorkerPool {
21
21
  configPath: args.configPath,
22
22
  file: args.file,
23
23
  modeName: args.modeName,
24
+ buildCommand: args.buildCommand,
24
25
  featureToggles,
25
26
  overrides,
26
27
  resolve,
@@ -80,7 +81,12 @@ export class BuildWorkerPool {
80
81
  worker.busy = false;
81
82
  worker.task = null;
82
83
  if (failedTask) {
83
- failedTask.reject(new Error("build worker exited unexpectedly"));
84
+ const modeLabel = failedTask.modeName ?? "default";
85
+ const fileLabel = failedTask.file;
86
+ const commandText = failedTask.buildCommand?.trim().length
87
+ ? `\nBuild command: ${failedTask.buildCommand}`
88
+ : "";
89
+ failedTask.reject(new Error(`build worker exited unexpectedly while building ${fileLabel} in mode ${modeLabel}${commandText}`));
84
90
  }
85
91
  if (!pool || this.closed)
86
92
  return;
@@ -8,7 +8,7 @@ import { applyMode, getPkgRunner, loadConfig, tokenizeCommand, resolveProjectMod
8
8
  import { persistCrashRecord } from "../crash-store.js";
9
9
  import { BuildWorkerPool } from "../build-worker-pool.js";
10
10
  const DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./as-test.config.json");
11
- class BuildFailureError extends Error {
11
+ export class BuildFailureError extends Error {
12
12
  constructor(args) {
13
13
  super(args.message);
14
14
  this.name = "BuildFailureError";
@@ -18,6 +18,7 @@ class BuildFailureError extends Error {
18
18
  this.stdout = args.stdout;
19
19
  this.stderr = args.stderr;
20
20
  this.kind = args.kind;
21
+ this.crashLogPath = args.crashLogPath;
21
22
  }
22
23
  }
23
24
  export async function build(configPath = DEFAULT_CONFIG_PATH, selectors = [], modeName, featureToggles = {}, overrides = {}, resolvedConfig) {
@@ -49,10 +50,13 @@ export async function build(configPath = DEFAULT_CONFIG_PATH, selectors = [], mo
49
50
  !hasCustomBuildCommand(config)) {
50
51
  const pool = getSerialBuildWorkerPool();
51
52
  for (const file of inputFiles) {
53
+ const outFile = `${config.outDir}/${resolveArtifactFileName(file, config.buildOptions.target, modeName, duplicateSpecBasenames)}`;
54
+ const invocation = getBuildCommand(config, pkgRunner, file, outFile, modeName, featureToggles);
52
55
  await pool.buildFileMode({
53
56
  configPath,
54
57
  file,
55
58
  modeName,
59
+ buildCommand: formatInvocation(invocation),
56
60
  featureToggles,
57
61
  overrides,
58
62
  });
@@ -90,6 +94,7 @@ export async function build(configPath = DEFAULT_CONFIG_PATH, selectors = [], mo
90
94
  stdout,
91
95
  stderr,
92
96
  kind,
97
+ crashLogPath: crash.logPath,
93
98
  message: `Failed to build ${path.basename(file)} in mode ${modeLabel} with ${stderr || stdout || "unknown build error"}\n` +
94
99
  `Build command: ${buildCommand}\n` +
95
100
  `Crash log: ${crash.logPath}`,
@@ -1,6 +1,6 @@
1
1
  import { closeSerialBuildWorkerPool, } from "./build-core.js";
2
2
  export { build } from "./build-core.js";
3
- export { formatInvocation, getBuildInvocationPreview, getBuildReuseInfo, } from "./build-core.js";
3
+ export { BuildFailureError, formatInvocation, getBuildInvocationPreview, getBuildReuseInfo, } from "./build-core.js";
4
4
  export async function executeBuildCommand(rawArgs, configPath, selectedModes, deps) {
5
5
  const commandArgs = deps.resolveCommandArgs(rawArgs, "build");
6
6
  const listFlags = deps.resolveListFlags(rawArgs, "build");
@@ -112,7 +112,9 @@ function pruneNestedTargets(targets) {
112
112
  if (targetPath == otherPath)
113
113
  continue;
114
114
  const relative = path.relative(targetPath, otherPath);
115
- if (!relative.length || relative == ".." || relative.startsWith(`..${path.sep}`)) {
115
+ if (!relative.length ||
116
+ relative == ".." ||
117
+ relative.startsWith(`..${path.sep}`)) {
116
118
  continue;
117
119
  }
118
120
  targets.delete(otherPath);
@@ -1,16 +1,10 @@
1
- import chalk from "chalk";
2
- import { createInterface } from "readline";
3
1
  import { clean } from "./clean-core.js";
4
2
  import { loadConfig } from "../util.js";
5
3
  export { clean } from "./clean-core.js";
6
4
  export async function executeCleanCommand(rawArgs, configPath, selectedModes, resolveExecutionModes) {
7
- const force = rawArgs.includes("-f") || rawArgs.includes("--force");
8
5
  const modeTargets = selectedModes.length > 0
9
6
  ? resolveExecutionModes(configPath, selectedModes)
10
7
  : resolveAllCleanModes(configPath);
11
- if (!force && selectedModes.length == 0) {
12
- await confirmFullClean(configPath);
13
- }
14
8
  await clean(configPath, modeTargets, selectedModes.length == 0);
15
9
  }
16
10
  function resolveAllCleanModes(configPath) {
@@ -18,34 +12,3 @@ function resolveAllCleanModes(configPath) {
18
12
  const config = loadConfig(resolvedConfigPath, true);
19
13
  return [undefined, ...Object.keys(config.modes)];
20
14
  }
21
- async function confirmFullClean(configPath) {
22
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
23
- throw new Error('clean without --mode requires confirmation. Re-run with "-f" or "--force" to skip the prompt.');
24
- }
25
- const target = configPath ? ` in ${configPath}` : "";
26
- process.stdout.write(chalk.bold.blue("◇ Confirm Clean") +
27
- "\n" +
28
- `│ This will remove configured build outputs, crash reports, and logs for every mode${target}.\n` +
29
- "│\n");
30
- const answer = await promptLine("Continue? [Y/n] ");
31
- const normalized = answer.trim().toLowerCase();
32
- if (normalized == "" || normalized == "y" || normalized == "yes")
33
- return;
34
- if (normalized == "n" || normalized == "no") {
35
- process.stdout.write(chalk.dim("clean cancelled\n"));
36
- process.exit(0);
37
- }
38
- throw new Error(`invalid answer "${answer}". Expected yes or no.`);
39
- }
40
- function promptLine(question) {
41
- return new Promise((resolve) => {
42
- const rl = createInterface({
43
- input: process.stdin,
44
- output: process.stdout,
45
- });
46
- rl.question(question, (answer) => {
47
- rl.close();
48
- resolve(answer);
49
- });
50
- });
51
- }
@@ -292,10 +292,10 @@ function buildFuzzCrashEntryKey(file, modeName) {
292
292
  return `${path.basename(file).replace(/\.ts$/, "")}.${sanitizeEntryName(modeName)}`;
293
293
  }
294
294
  function sanitizeEntryName(name) {
295
- return name
295
+ return (name
296
296
  .toLowerCase()
297
297
  .replace(/[^a-z0-9]+/g, "-")
298
- .replace(/^-+|-+$/g, "") || "fuzzer";
298
+ .replace(/^-+|-+$/g, "") || "fuzzer");
299
299
  }
300
300
  function captureFrames(onFrame) {
301
301
  const originalWrite = process.stdout.write.bind(process.stdout);
@@ -2,7 +2,7 @@ import chalk from "chalk";
2
2
  import { spawn } from "child_process";
3
3
  import { glob } from "glob";
4
4
  import { Channel, MessageType } from "../wipc.js";
5
- import { applyMode, formatTime, getExec, loadConfig, tokenizeCommand, } from "../util.js";
5
+ import { applyMode, formatSpecDisplayPath, formatTime, getExec, loadConfig, tokenizeCommand, } from "../util.js";
6
6
  import * as path from "path";
7
7
  import { pathToFileURL } from "url";
8
8
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -390,7 +390,7 @@ function resolveSuiteSelectionMatches(suites, selectors, file) {
390
390
  if (normalized.includes("/")) {
391
391
  const resolved = resolveExplicitSuitePath(suites, normalized);
392
392
  if (!resolved) {
393
- throw new Error(`No suites matched "${selector}" in ${path.basename(file)}.`);
393
+ throw new Error(`No suites matched "${selector}" in ${formatSpecDisplayPath(file)}.`);
394
394
  }
395
395
  matches.push({
396
396
  kind: "path",
@@ -402,7 +402,7 @@ function resolveSuiteSelectionMatches(suites, selectors, file) {
402
402
  }
403
403
  const resolved = resolveBareSuiteSelector(suites, normalized);
404
404
  if (!resolved) {
405
- throw new Error(`No suites matched "${selector}" in ${path.basename(file)}.`);
405
+ throw new Error(`No suites matched "${selector}" in ${formatSpecDisplayPath(file)}.`);
406
406
  }
407
407
  matches.push({
408
408
  kind: "bare",
@@ -437,7 +437,9 @@ function resolveBareSuiteSelector(suites, selector) {
437
437
  return null;
438
438
  const matches = [];
439
439
  walkSuites(suites, (suite, depth) => {
440
- const leaf = String(suite.path ?? "").split("/").pop() ?? "";
440
+ const leaf = String(suite.path ?? "")
441
+ .split("/")
442
+ .pop() ?? "";
441
443
  if (leaf == slug) {
442
444
  matches.push({ path: String(suite.path), depth });
443
445
  }
@@ -457,7 +459,9 @@ function walkSuites(suites, visitor, depth = 0) {
457
459
  for (const suite of suites) {
458
460
  if (visitor(suite, depth))
459
461
  return true;
460
- const childSuites = Array.isArray(suite?.suites) ? suite.suites : [];
462
+ const childSuites = Array.isArray(suite?.suites)
463
+ ? suite.suites
464
+ : [];
461
465
  if (walkSuites(childSuites, visitor, depth + 1))
462
466
  return true;
463
467
  }
@@ -466,7 +470,9 @@ function walkSuites(suites, visitor, depth = 0) {
466
470
  function cloneSelectedSuites(suites, selected, file, modeName) {
467
471
  const out = [];
468
472
  for (const suite of suites) {
469
- const childSuites = Array.isArray(suite.suites) ? suite.suites : [];
473
+ const childSuites = Array.isArray(suite.suites)
474
+ ? suite.suites
475
+ : [];
470
476
  const selectedChildren = cloneSelectedSuites(childSuites, selected, file, modeName);
471
477
  const keep = selected.has(String(suite.path ?? "")) || selectedChildren.length > 0;
472
478
  if (!keep)
@@ -506,8 +512,8 @@ function collectReadableFailures(suites, file, pathParts) {
506
512
  out.push({
507
513
  title: `${nextPath.join(" > ")}#${i + 1}`,
508
514
  where: String(test.location ?? "").length
509
- ? `${path.basename(file)}:${String(test.location ?? "")}`
510
- : path.basename(file),
515
+ ? `${formatSpecDisplayPath(file)}:${String(test.location ?? "")}`
516
+ : formatSpecDisplayPath(file),
511
517
  message: String(test.message ?? ""),
512
518
  left: JSON.stringify(test.left ?? ""),
513
519
  right: JSON.stringify(test.right ?? ""),
@@ -648,7 +654,7 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
648
654
  catch (error) {
649
655
  const modeLabel = options.modeName ?? "default";
650
656
  const details = error instanceof Error ? error.message : String(error);
651
- throw new Error(`Failed to run ${path.basename(file)} in mode ${modeLabel} with ${details}`);
657
+ throw new Error(`Failed to run ${formatSpecDisplayPath(file)} in mode ${modeLabel} with ${details}`);
652
658
  }
653
659
  const normalized = normalizeReport(report);
654
660
  const selectedSuites = options.suiteSelectors?.length
@@ -665,6 +671,7 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
665
671
  suites: selectedSuites,
666
672
  coverage: normalized.coverage,
667
673
  runCommand: runCommandForLog,
674
+ buildCommand: options.buildCommandsByFile?.[file] ?? options.buildCommand ?? "",
668
675
  snapshotSummary: {
669
676
  matched: snapshotStore.matched,
670
677
  created: snapshotStore.created,
@@ -1492,7 +1499,7 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
1492
1499
  const runtimeEvents = {
1493
1500
  sawFileStart: false,
1494
1501
  sawFileEnd: false,
1495
- fileName: path.basename(specFile),
1502
+ fileName: formatSpecDisplayPath(specFile),
1496
1503
  fileVerdict: "none",
1497
1504
  fileTime: "",
1498
1505
  suiteStarts: 0,
@@ -1667,7 +1674,8 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
1667
1674
  const code = await new Promise((resolve) => {
1668
1675
  child.on("close", (exitCode) => resolve(exitCode ?? 1));
1669
1676
  });
1670
- if (stderrPendingLine.length && !shouldSuppressWasiWarningLine(stderrPendingLine)) {
1677
+ if (stderrPendingLine.length &&
1678
+ !shouldSuppressWasiWarningLine(stderrPendingLine)) {
1671
1679
  stderrBuffer += stderrPendingLine;
1672
1680
  }
1673
1681
  const processSpawnError = spawnError;
@@ -1686,8 +1694,7 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
1686
1694
  if (reportStream.sawChunkStart) {
1687
1695
  if (!reportStream.sawChunkEnd) {
1688
1696
  parseError =
1689
- parseError ??
1690
- "missing report:end marker for chunked report payload";
1697
+ parseError ?? "missing report:end marker for chunked report payload";
1691
1698
  }
1692
1699
  else {
1693
1700
  const chunkedPayload = reportStream.chunks.join("");
@@ -1806,7 +1813,7 @@ async function runWebSessionProcess(session, specFile, crashDir, modeName, snaps
1806
1813
  const runtimeEvents = {
1807
1814
  sawFileStart: false,
1808
1815
  sawFileEnd: false,
1809
- fileName: path.basename(specFile),
1816
+ fileName: formatSpecDisplayPath(specFile),
1810
1817
  fileVerdict: "none",
1811
1818
  fileTime: "",
1812
1819
  suiteStarts: 0,
@@ -1968,7 +1975,7 @@ async function runWebSessionProcess(session, specFile, crashDir, modeName, snaps
1968
1975
  });
1969
1976
  let code = 0;
1970
1977
  try {
1971
- await session.runJob(Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] == "string")), path.basename(specFile), (frame) => {
1978
+ await session.runJob(Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] == "string")), formatSpecDisplayPath(specFile), (frame) => {
1972
1979
  input.write(frame);
1973
1980
  });
1974
1981
  }
@@ -1976,8 +1983,9 @@ async function runWebSessionProcess(session, specFile, crashDir, modeName, snaps
1976
1983
  code = 1;
1977
1984
  await session.close(error instanceof Error ? error : new Error(String(error)));
1978
1985
  stderrBuffer +=
1979
- (error instanceof Error ? error.stack ?? error.message : String(error)) +
1980
- "\n";
1986
+ (error instanceof Error
1987
+ ? (error.stack ?? error.message)
1988
+ : String(error)) + "\n";
1981
1989
  }
1982
1990
  finally {
1983
1991
  input.end();
@@ -1986,8 +1994,7 @@ async function runWebSessionProcess(session, specFile, crashDir, modeName, snaps
1986
1994
  if (reportStream.sawChunkStart) {
1987
1995
  if (!reportStream.sawChunkEnd) {
1988
1996
  parseError =
1989
- parseError ??
1990
- "missing report:end marker for chunked report payload";
1997
+ parseError ?? "missing report:end marker for chunked report payload";
1991
1998
  }
1992
1999
  else {
1993
2000
  const chunkedPayload = reportStream.chunks.join("");
@@ -2091,7 +2098,7 @@ function synthesizeReportFromRuntimeEvents(specFile, runtimeEvents) {
2091
2098
  suites: [
2092
2099
  {
2093
2100
  file: specFile,
2094
- description: runtimeEvents.fileName || path.basename(specFile),
2101
+ description: runtimeEvents.fileName || formatSpecDisplayPath(specFile),
2095
2102
  depth: 0,
2096
2103
  kind: "file",
2097
2104
  verdict,
@@ -2136,7 +2143,7 @@ function appendRuntimeFailureReport(report, specFile, modeName, title, details,
2136
2143
  const suites = Array.isArray(report?.suites) ? report.suites : [];
2137
2144
  suites.push({
2138
2145
  file: specFile,
2139
- description: path.basename(specFile),
2146
+ description: formatSpecDisplayPath(specFile),
2140
2147
  depth: 0,
2141
2148
  kind: "runtime-error",
2142
2149
  verdict: "fail",
@@ -2221,9 +2228,11 @@ function readFileReport(stats, fileReport) {
2221
2228
  : [];
2222
2229
  const file = String(fileReportAny.file ?? "");
2223
2230
  const modeName = String(fileReportAny.modeName ?? "");
2231
+ const runCommand = String(fileReportAny.runCommand ?? "");
2232
+ const buildCommand = String(fileReportAny.buildCommand ?? "");
2224
2233
  let fileVerdict = "none";
2225
2234
  for (const suite of suites) {
2226
- fileVerdict = mergeVerdict(fileVerdict, readSuite(stats, suite, file, modeName));
2235
+ fileVerdict = mergeVerdict(fileVerdict, readSuite(stats, suite, file, modeName, runCommand, buildCommand));
2227
2236
  }
2228
2237
  if (fileVerdict == "fail") {
2229
2238
  stats.failedFiles++;
@@ -2235,7 +2244,7 @@ function readFileReport(stats, fileReport) {
2235
2244
  stats.skippedFiles++;
2236
2245
  }
2237
2246
  }
2238
- function readSuite(stats, suite, file, modeName) {
2247
+ function readSuite(stats, suite, file, modeName, runCommand, buildCommand) {
2239
2248
  const suiteAny = suite;
2240
2249
  const kind = String(suiteAny.kind ?? "");
2241
2250
  let verdict = normalizeVerdict(suiteAny.verdict);
@@ -2247,7 +2256,7 @@ function readSuite(stats, suite, file, modeName) {
2247
2256
  ? suiteAny.suites
2248
2257
  : [];
2249
2258
  for (const subSuite of subSuites) {
2250
- verdict = mergeVerdict(verdict, readSuite(stats, subSuite, file, modeName));
2259
+ verdict = mergeVerdict(verdict, readSuite(stats, subSuite, file, modeName, runCommand, buildCommand));
2251
2260
  }
2252
2261
  const tests = Array.isArray(suiteAny.tests)
2253
2262
  ? suiteAny.tests
@@ -2285,6 +2294,8 @@ function readSuite(stats, suite, file, modeName) {
2285
2294
  ...suiteAny,
2286
2295
  file,
2287
2296
  modeName,
2297
+ runCommand,
2298
+ buildCommand,
2288
2299
  });
2289
2300
  }
2290
2301
  else if (verdict == "ok") {
@@ -1,21 +1,21 @@
1
1
  export function buildWebRunnerSource() {
2
2
  return `// Feel free to edit this file!
3
- // Runner files use the name <mode>.<type>.js, where <type> is bindings, wasi, or web.
4
- // To create a runner for another mode, copy this file to <new-mode>.<type>.js.
3
+ // Runner files use the name <mode>.<type>.js, where <type> is bindings, wasi, or web.
4
+ // To create a runner for another mode, copy this file to <new-mode>.<type>.js.
5
5
 
6
- import { instantiate } from "as-test/lib";
6
+ import { instantiate } from "as-test/lib";
7
7
 
8
- let exports = null;
9
- const imports = {};
8
+ let exports = null;
9
+ const imports = {};
10
10
 
11
- instantiate(imports)
12
- .then((instance) => {
13
- exports = instance.exports;
14
- instance.exports.start?.();
15
- // Add extra startup logic here when needed.
16
- })
17
- .catch((error) => {
18
- throw new Error("Failed to run web module: " + String(error));
19
- });
11
+ instantiate(imports)
12
+ .then((instance) => {
13
+ exports = instance.exports;
14
+ instance.exports.start?.();
15
+ // Add extra startup logic here when needed.
16
+ })
17
+ .catch((error) => {
18
+ throw new Error("Failed to run web module: " + String(error));
19
+ });
20
20
  `;
21
21
  }
@@ -488,7 +488,12 @@ function resolveHeadlessFlags(commandValue) {
488
488
  const lower = commandValue.toLowerCase();
489
489
  if (lower.includes("firefox"))
490
490
  return ["-headless"];
491
- return ["--headless=new", "--disable-gpu", "--no-first-run", "--no-default-browser-check"];
491
+ return [
492
+ "--headless=new",
493
+ "--disable-gpu",
494
+ "--no-first-run",
495
+ "--no-default-browser-check",
496
+ ];
492
497
  }
493
498
  function hasExecutable(command) {
494
499
  if (!command.length)
@@ -1,7 +1,9 @@
1
1
  import { mkdirSync, writeFileSync } from "fs";
2
2
  import * as path from "path";
3
3
  export function persistCrashRecord(rootDir, record) {
4
- const entry = record.entryKey?.length ? record.entryKey : crashEntryKey(record.file);
4
+ const entry = record.entryKey?.length
5
+ ? record.entryKey
6
+ : crashEntryKey(record.file);
5
7
  const dir = path.resolve(process.cwd(), rootDir);
6
8
  mkdirSync(dir, { recursive: true });
7
9
  const jsonPath = path.join(dir, `${entry}.json`);