as-test 1.1.10 → 1.3.0

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.
@@ -8,6 +8,8 @@ import {
8
8
  formatTime,
9
9
  getExec,
10
10
  loadConfig,
11
+ resolveArtifactPath,
12
+ resolveSpecRelativePath,
11
13
  tokenizeCommand,
12
14
  } from "../util.js";
13
15
  import * as path from "path";
@@ -23,7 +25,7 @@ import { persistCrashRecord } from "../crash-store.js";
23
25
  import { describeCoveragePoint } from "../coverage-points.js";
24
26
  const DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./as-test.config.json");
25
27
  class SnapshotStore {
26
- constructor(specFile, snapshotDir, duplicateSpecBasenames = new Set()) {
28
+ constructor(specFile, snapshotDir, inputPatterns) {
27
29
  this.dirty = false;
28
30
  this.created = 0;
29
31
  this.updated = 0;
@@ -32,27 +34,19 @@ class SnapshotStore {
32
34
  this.warnedMissing = new Set();
33
35
  this.specBasename = path.basename(specFile);
34
36
  const dir = path.join(process.cwd(), snapshotDir);
35
- const relative = resolveArtifactRelativePath(specFile, "__tests__").replace(
36
- /\.ts$/,
37
+ const relative = resolveSpecRelativePath(specFile, inputPatterns).replace(
38
+ /\.ts$/i,
37
39
  ".snap",
38
40
  );
39
41
  this.filePath = path.join(dir, relative);
40
- const sourcePath =
41
- resolveSnapshotSourcePath(
42
- specFile,
43
- dir,
44
- duplicateSpecBasenames,
45
- this.filePath,
46
- ) ?? null;
42
+ const sourcePath = existsSync(this.filePath) ? this.filePath : null;
47
43
  const loaded = sourcePath
48
44
  ? readSnapshotFile(sourcePath, specFile)
49
45
  : { data: {}, normalized: false, preamble: "" };
50
46
  this.data = loaded.data;
51
47
  this.preamble = loaded.preamble;
52
- this.existed = Boolean(sourcePath && existsSync(sourcePath));
53
- this.dirty = Boolean(
54
- (sourcePath && sourcePath != this.filePath) || loaded.normalized,
55
- );
48
+ this.existed = Boolean(sourcePath);
49
+ this.dirty = Boolean(loaded.normalized);
56
50
  }
57
51
  assert(key, actual, allowSnapshot, createSnapshots, overwriteSnapshots) {
58
52
  key = canonicalizeSnapshotKey(key);
@@ -103,26 +97,6 @@ class SnapshotStore {
103
97
  );
104
98
  }
105
99
  }
106
- function resolveSnapshotSourcePath(
107
- specFile,
108
- snapshotDir,
109
- duplicateSpecBasenames,
110
- preferredPath,
111
- ) {
112
- if (existsSync(preferredPath)) return preferredPath;
113
- const base = path.basename(specFile, ".ts");
114
- const legacyFlat = path.join(snapshotDir, `${base}.snap.json`);
115
- if (existsSync(legacyFlat)) return legacyFlat;
116
- const disambiguator = resolveDisambiguator(specFile, duplicateSpecBasenames);
117
- if (disambiguator.length) {
118
- const legacyDisambiguated = path.join(
119
- snapshotDir,
120
- `${base}.${disambiguator}.snap.json`,
121
- );
122
- if (existsSync(legacyDisambiguated)) return legacyDisambiguated;
123
- }
124
- return null;
125
- }
126
100
  function readSnapshotFile(filePath, specFile) {
127
101
  const raw = readFileSync(filePath, "utf8");
128
102
  if (filePath.endsWith(".json")) {
@@ -267,17 +241,11 @@ function trimSnapshotPreamble(lines) {
267
241
  while (end > 0 && !(lines[end - 1] ?? "").trim().length) end--;
268
242
  return lines.slice(0, end).join("\n");
269
243
  }
244
+ // Only the basename of the returned path matters — callers feed this into
245
+ // `path.basename(...)` to localize snapshot keys (strip the "${basename}::"
246
+ // prefix). The full path is therefore synthetic but stable.
270
247
  function resolveSnapshotSpecFile(filePath) {
271
- const normalized = filePath.replace(/\\/g, "/");
272
- const marker = "/snapshots/";
273
- const markerIndex = normalized.lastIndexOf(marker);
274
- const suffix =
275
- markerIndex >= 0
276
- ? normalized.slice(markerIndex + marker.length)
277
- : path.basename(normalized);
278
- const withoutMode = suffix.replace(/^default\//, "");
279
- const relative = withoutMode.replace(/\.snap$/, ".ts");
280
- return `assembly/__tests__/${relative}`;
248
+ return path.basename(filePath).replace(/\.snap$/, ".ts");
281
249
  }
282
250
  function localizeSnapshotKey(specFile, key) {
283
251
  const prefix = `${path.basename(specFile)}::`;
@@ -315,24 +283,18 @@ function canonicalizeSnapshotLocalKey(localKey) {
315
283
  }
316
284
  return localKey;
317
285
  }
318
- function resolveArtifactRelativePath(sourceFile, segment) {
319
- const normalized = sourceFile.replace(/\\/g, "/");
320
- const marker = `/${segment}/`;
321
- const index = normalized.lastIndexOf(marker);
322
- if (index >= 0) return normalized.slice(index + marker.length);
323
- return path.basename(normalized);
324
- }
325
286
  function writeReadableLog(
326
287
  logRoot,
327
288
  file,
289
+ inputPatterns,
328
290
  suites,
329
291
  modeName,
330
292
  buildCommand,
331
293
  runCommand,
332
294
  snapshotSummary,
333
295
  ) {
334
- const relative = resolveArtifactRelativePath(file, "__tests__").replace(
335
- /\.ts$/,
296
+ const relative = resolveSpecRelativePath(file, inputPatterns).replace(
297
+ /\.ts$/i,
336
298
  ".log",
337
299
  );
338
300
  const filePath = path.join(logRoot, relative);
@@ -605,9 +567,6 @@ export async function run(
605
567
  const inputFiles = (await glob(inputPatterns)).sort((a, b) =>
606
568
  a.localeCompare(b),
607
569
  );
608
- const duplicateSpecBasenames = await resolveDuplicateSpecBasenames(
609
- config.input,
610
- );
611
570
  const snapshotEnabled = flags.snapshot !== false;
612
571
  const createSnapshots = Boolean(flags.createSnapshots);
613
572
  const overwriteSnapshots = Boolean(flags.overwriteSnapshots);
@@ -685,12 +644,7 @@ export async function run(
685
644
  const file = inputFiles[i];
686
645
  const outFile = path.join(
687
646
  config.outDir,
688
- resolveArtifactFileName(
689
- file,
690
- config.buildOptions.target,
691
- options.modeName,
692
- duplicateSpecBasenames,
693
- ),
647
+ resolveArtifactPath(file, config.input),
694
648
  );
695
649
  if (!existsSync(outFile)) {
696
650
  const buildStartedAt = Date.now();
@@ -725,7 +679,7 @@ export async function run(
725
679
  const snapshotStore = new SnapshotStore(
726
680
  file,
727
681
  config.snapshotDir,
728
- duplicateSpecBasenames,
682
+ config.input,
729
683
  );
730
684
  let report;
731
685
  try {
@@ -739,11 +693,16 @@ export async function run(
739
693
  ? { BROWSER: config.runOptions.runtime.browser.trim() }
740
694
  : {}),
741
695
  };
696
+ const crashEntryKey = resolveSpecRelativePath(
697
+ file,
698
+ config.input,
699
+ ).replace(/\.ts$/i, "");
742
700
  report = webSession
743
701
  ? await runWebSessionProcess(
744
702
  webSession,
745
703
  file,
746
704
  config.fuzz.crashDir,
705
+ crashEntryKey,
747
706
  options.modeName,
748
707
  snapshotStore,
749
708
  snapshotEnabled,
@@ -757,6 +716,7 @@ export async function run(
757
716
  invocation,
758
717
  file,
759
718
  config.fuzz.crashDir,
719
+ crashEntryKey,
760
720
  options.modeName,
761
721
  snapshotStore,
762
722
  snapshotEnabled,
@@ -815,6 +775,7 @@ export async function run(
815
775
  writeReadableLog(
816
776
  logRoot,
817
777
  report.file,
778
+ config.input,
818
779
  report.suites,
819
780
  options.modeName,
820
781
  options.buildCommandsByFile?.[report.file] ??
@@ -842,16 +803,12 @@ export async function run(
842
803
  coverageSummary.files.length > 0
843
804
  ) {
844
805
  const resolvedCoverageDir = path.join(process.cwd(), coverageDir);
845
- if (!existsSync(resolvedCoverageDir)) {
846
- mkdirSync(resolvedCoverageDir, { recursive: true });
847
- }
848
- writeFileSync(
849
- path.join(
850
- resolvedCoverageDir,
851
- options.coverageFileName ?? "coverage.log.json",
852
- ),
853
- JSON.stringify(coverageSummary, null, 2),
806
+ const coverageFilePath = path.join(
807
+ resolvedCoverageDir,
808
+ options.coverageFileName ?? "coverage.log.json",
854
809
  );
810
+ mkdirSync(path.dirname(coverageFilePath), { recursive: true });
811
+ writeFileSync(coverageFilePath, JSON.stringify(coverageSummary, null, 2));
855
812
  }
856
813
  if (options.emitRunComplete !== false) {
857
814
  const totalModes = Math.max(options.modeSummaryTotal ?? 1, 1);
@@ -1091,53 +1048,6 @@ instantiate(imports)
1091
1048
  }
1092
1049
  return null;
1093
1050
  }
1094
- function resolveArtifactFileName(
1095
- file,
1096
- target,
1097
- modeName,
1098
- duplicateSpecBasenames = new Set(),
1099
- ) {
1100
- const base = path
1101
- .basename(file)
1102
- .replace(/\.spec\.ts$/, "")
1103
- .replace(/\.ts$/, "");
1104
- const legacy = !modeName
1105
- ? `${path.basename(file).replace(".ts", ".wasm")}`
1106
- : `${base}.${modeName}.${target}.wasm`;
1107
- if (!duplicateSpecBasenames.has(path.basename(file))) {
1108
- return legacy;
1109
- }
1110
- const disambiguator = resolveDisambiguator(file, duplicateSpecBasenames);
1111
- if (!disambiguator.length) {
1112
- return legacy;
1113
- }
1114
- const ext = path.extname(legacy);
1115
- const stem = ext.length ? legacy.slice(0, -ext.length) : legacy;
1116
- return `${stem}.${disambiguator}${ext}`;
1117
- }
1118
- async function resolveDuplicateSpecBasenames(configured) {
1119
- const patterns = Array.isArray(configured) ? configured : [configured];
1120
- const files = await glob(patterns);
1121
- const counts = new Map();
1122
- for (const file of files) {
1123
- const base = path.basename(file);
1124
- counts.set(base, (counts.get(base) ?? 0) + 1);
1125
- }
1126
- const duplicates = new Set();
1127
- for (const [base, count] of counts) {
1128
- if (count > 1) duplicates.add(base);
1129
- }
1130
- return duplicates;
1131
- }
1132
- function resolveDisambiguator(file, duplicateSpecBasenames) {
1133
- if (!duplicateSpecBasenames.has(path.basename(file))) return "";
1134
- const relDir = path.dirname(path.relative(process.cwd(), file));
1135
- if (!relDir.length || relDir == ".") return "";
1136
- return relDir
1137
- .replace(/[\\/]+/g, "__")
1138
- .replace(/[^A-Za-z0-9._-]/g, "_")
1139
- .replace(/^_+|_+$/g, "");
1140
- }
1141
1051
  function resolveRuntimeTargetEnv(target, wasmPath) {
1142
1052
  if (target == "bindings") {
1143
1053
  return resolveBindingsRuntimeEnv(wasmPath);
@@ -1757,6 +1667,7 @@ async function runProcess(
1757
1667
  invocation,
1758
1668
  specFile,
1759
1669
  crashDir,
1670
+ crashEntryKey,
1760
1671
  modeName,
1761
1672
  snapshots,
1762
1673
  snapshotEnabled,
@@ -1975,6 +1886,7 @@ async function runProcess(
1975
1886
  persistCrashRecord(crashDir, {
1976
1887
  kind: "test",
1977
1888
  file: specFile,
1889
+ entryKey: crashEntryKey,
1978
1890
  mode: modeName ?? "default",
1979
1891
  error: errorText,
1980
1892
  stdout: stdoutBuffer,
@@ -2031,6 +1943,7 @@ async function runProcess(
2031
1943
  persistCrashRecord(crashDir, {
2032
1944
  kind: "test",
2033
1945
  file: specFile,
1946
+ entryKey: crashEntryKey,
2034
1947
  mode: modeName ?? "default",
2035
1948
  error: fullError,
2036
1949
  stdout: stdoutBuffer,
@@ -2075,6 +1988,7 @@ async function runProcess(
2075
1988
  persistCrashRecord(crashDir, {
2076
1989
  kind: "test",
2077
1990
  file: specFile,
1991
+ entryKey: crashEntryKey,
2078
1992
  mode: modeName ?? "default",
2079
1993
  error: errorText || "runtime reported an unknown error",
2080
1994
  stdout: stdoutBuffer,
@@ -2105,6 +2019,7 @@ async function runProcess(
2105
2019
  persistCrashRecord(crashDir, {
2106
2020
  kind: "test",
2107
2021
  file: specFile,
2022
+ entryKey: crashEntryKey,
2108
2023
  mode: modeName ?? "default",
2109
2024
  error: fullError,
2110
2025
  stdout: stdoutBuffer,
@@ -2132,6 +2047,7 @@ async function runProcess(
2132
2047
  persistCrashRecord(crashDir, {
2133
2048
  kind: "test",
2134
2049
  file: specFile,
2050
+ entryKey: crashEntryKey,
2135
2051
  mode: modeName ?? "default",
2136
2052
  error: errorText || "runtime reported an unknown error",
2137
2053
  stdout: stdoutBuffer,
@@ -2155,6 +2071,7 @@ async function runWebSessionProcess(
2155
2071
  session,
2156
2072
  specFile,
2157
2073
  crashDir,
2074
+ crashEntryKey,
2158
2075
  modeName,
2159
2076
  snapshots,
2160
2077
  snapshotEnabled,
@@ -2407,6 +2324,7 @@ async function runWebSessionProcess(
2407
2324
  persistCrashRecord(crashDir, {
2408
2325
  kind: "test",
2409
2326
  file: specFile,
2327
+ entryKey: crashEntryKey,
2410
2328
  mode: modeName ?? "default",
2411
2329
  error: fullError,
2412
2330
  stdout: stdoutBuffer,
@@ -2462,6 +2380,7 @@ async function runWebSessionProcess(
2462
2380
  persistCrashRecord(crashDir, {
2463
2381
  kind: "test",
2464
2382
  file: specFile,
2383
+ entryKey: crashEntryKey,
2465
2384
  mode: modeName ?? "default",
2466
2385
  error: fullError,
2467
2386
  stdout: stdoutBuffer,
@@ -11,8 +11,8 @@ export async function executeTestCommand(
11
11
  const listFlags = deps.resolveListFlags(rawArgs, "test");
12
12
  const featureToggles = deps.resolveFeatureToggles(rawArgs, "test");
13
13
  const buildFeatureToggles = {
14
- tryAs: featureToggles.tryAs,
15
14
  coverage: featureToggles.coverage,
15
+ featureOverrides: featureToggles.featureOverrides,
16
16
  };
17
17
  const showCoverageMode = deps.resolveShowCoverageMode(rawArgs, "test");
18
18
  const runFlags = {
@@ -56,6 +56,10 @@ export class PersistentWebSessionHost {
56
56
  ? env.AS_TEST_HELPER_PATH
57
57
  : null;
58
58
  const jobId = String(this.nextJobId++);
59
+ // URL paths use only the basename — that is safe because each job has
60
+ // exactly one wasm and one helper, scoped by the jobId path component.
61
+ // Nested directory artifacts (e.g. nested/array.spec.wasm) are read
62
+ // from disk via `wasmPath`/`helperPath`, not via these URLs.
59
63
  const browserEnv = {
60
64
  ...env,
61
65
  AS_TEST_WASM_PATH: `/job/${jobId}/${path.basename(wasmPath)}`,
@@ -5,9 +5,9 @@ export function persistCrashRecord(rootDir, record) {
5
5
  ? record.entryKey
6
6
  : crashEntryKey(record.file);
7
7
  const dir = path.resolve(process.cwd(), rootDir);
8
- mkdirSync(dir, { recursive: true });
9
8
  const jsonPath = path.join(dir, `${entry}.json`);
10
9
  const logPath = path.join(dir, `${entry}.log`);
10
+ mkdirSync(path.dirname(jsonPath), { recursive: true });
11
11
  const payload = {
12
12
  timestamp: new Date().toISOString(),
13
13
  ...record,