as-test 1.1.4 → 1.1.6-patch.1
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/CHANGELOG.md +24 -0
- package/assembly/__fuzz__/nested/array.fuzz.ts +13 -0
- package/bin/commands/build-core.js +27 -49
- package/bin/commands/fuzz-core.js +13 -53
- package/bin/commands/run-core.js +98 -99
- package/bin/commands/web-session.js +4 -0
- package/bin/crash-store.js +1 -1
- package/bin/index.js +37 -75
- package/bin/util.js +87 -6
- package/package.json +1 -1
- package/transform/lib/coverage.js +17 -16
- package/transform/lib/index.js +3 -1
- package/transform/lib/types.js +3 -0
- package/transform/lib/visitor.js +70 -67
package/bin/commands/run-core.js
CHANGED
|
@@ -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, formatSpecDisplayPath, formatTime, getExec, loadConfig, tokenizeCommand, } from "../util.js";
|
|
5
|
+
import { applyMode, formatSpecDisplayPath, formatTime, getExec, loadConfig, resolveArtifactPath, resolveSpecRelativePath, 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";
|
|
@@ -16,27 +16,29 @@ import { persistCrashRecord } from "../crash-store.js";
|
|
|
16
16
|
import { describeCoveragePoint } from "../coverage-points.js";
|
|
17
17
|
const DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./as-test.config.json");
|
|
18
18
|
class SnapshotStore {
|
|
19
|
-
constructor(specFile, snapshotDir,
|
|
19
|
+
constructor(specFile, snapshotDir, inputPatterns) {
|
|
20
20
|
this.dirty = false;
|
|
21
21
|
this.created = 0;
|
|
22
22
|
this.updated = 0;
|
|
23
23
|
this.matched = 0;
|
|
24
24
|
this.failed = 0;
|
|
25
25
|
this.warnedMissing = new Set();
|
|
26
|
+
this.specBasename = path.basename(specFile);
|
|
26
27
|
const dir = path.join(process.cwd(), snapshotDir);
|
|
27
|
-
const relative =
|
|
28
|
+
const relative = resolveSpecRelativePath(specFile, inputPatterns).replace(/\.ts$/i, ".snap");
|
|
28
29
|
this.filePath = path.join(dir, relative);
|
|
29
|
-
const sourcePath =
|
|
30
|
+
const sourcePath = existsSync(this.filePath) ? this.filePath : null;
|
|
30
31
|
const loaded = sourcePath
|
|
31
32
|
? readSnapshotFile(sourcePath, specFile)
|
|
32
33
|
: { data: {}, normalized: false, preamble: "" };
|
|
33
34
|
this.data = loaded.data;
|
|
34
35
|
this.preamble = loaded.preamble;
|
|
35
|
-
this.existed = Boolean(sourcePath
|
|
36
|
-
this.dirty = Boolean(
|
|
36
|
+
this.existed = Boolean(sourcePath);
|
|
37
|
+
this.dirty = Boolean(loaded.normalized);
|
|
37
38
|
}
|
|
38
39
|
assert(key, actual, allowSnapshot, createSnapshots, overwriteSnapshots) {
|
|
39
40
|
key = canonicalizeSnapshotKey(key);
|
|
41
|
+
key = normalizeSnapshotKeyPrefix(key, this.specBasename);
|
|
40
42
|
if (!allowSnapshot)
|
|
41
43
|
return { ok: true, expected: actual, warnMissing: false };
|
|
42
44
|
if (!(key in this.data)) {
|
|
@@ -79,21 +81,6 @@ class SnapshotStore {
|
|
|
79
81
|
writeFileSync(this.filePath, formatSnapshotFile(this.data, this.filePath, this.existed ? this.preamble : defaultSnapshotPreamble()));
|
|
80
82
|
}
|
|
81
83
|
}
|
|
82
|
-
function resolveSnapshotSourcePath(specFile, snapshotDir, duplicateSpecBasenames, preferredPath) {
|
|
83
|
-
if (existsSync(preferredPath))
|
|
84
|
-
return preferredPath;
|
|
85
|
-
const base = path.basename(specFile, ".ts");
|
|
86
|
-
const legacyFlat = path.join(snapshotDir, `${base}.snap.json`);
|
|
87
|
-
if (existsSync(legacyFlat))
|
|
88
|
-
return legacyFlat;
|
|
89
|
-
const disambiguator = resolveDisambiguator(specFile, duplicateSpecBasenames);
|
|
90
|
-
if (disambiguator.length) {
|
|
91
|
-
const legacyDisambiguated = path.join(snapshotDir, `${base}.${disambiguator}.snap.json`);
|
|
92
|
-
if (existsSync(legacyDisambiguated))
|
|
93
|
-
return legacyDisambiguated;
|
|
94
|
-
}
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
84
|
function readSnapshotFile(filePath, specFile) {
|
|
98
85
|
const raw = readFileSync(filePath, "utf8");
|
|
99
86
|
if (filePath.endsWith(".json")) {
|
|
@@ -245,21 +232,22 @@ function trimSnapshotPreamble(lines) {
|
|
|
245
232
|
end--;
|
|
246
233
|
return lines.slice(0, end).join("\n");
|
|
247
234
|
}
|
|
235
|
+
// Only the basename of the returned path matters — callers feed this into
|
|
236
|
+
// `path.basename(...)` to localize snapshot keys (strip the "${basename}::"
|
|
237
|
+
// prefix). The full path is therefore synthetic but stable.
|
|
248
238
|
function resolveSnapshotSpecFile(filePath) {
|
|
249
|
-
|
|
250
|
-
const marker = "/snapshots/";
|
|
251
|
-
const markerIndex = normalized.lastIndexOf(marker);
|
|
252
|
-
const suffix = markerIndex >= 0
|
|
253
|
-
? normalized.slice(markerIndex + marker.length)
|
|
254
|
-
: path.basename(normalized);
|
|
255
|
-
const withoutMode = suffix.replace(/^default\//, "");
|
|
256
|
-
const relative = withoutMode.replace(/\.snap$/, ".ts");
|
|
257
|
-
return `assembly/__tests__/${relative}`;
|
|
239
|
+
return path.basename(filePath).replace(/\.snap$/, ".ts");
|
|
258
240
|
}
|
|
259
241
|
function localizeSnapshotKey(specFile, key) {
|
|
260
242
|
const prefix = `${path.basename(specFile)}::`;
|
|
261
243
|
return key.startsWith(prefix) ? key.slice(prefix.length) : key;
|
|
262
244
|
}
|
|
245
|
+
function normalizeSnapshotKeyPrefix(key, specBasename) {
|
|
246
|
+
const sep = key.indexOf("::");
|
|
247
|
+
if (sep < 0)
|
|
248
|
+
return key;
|
|
249
|
+
return `${specBasename}::${key.slice(sep + 2)}`;
|
|
250
|
+
}
|
|
263
251
|
function qualifySnapshotKey(specFile, key) {
|
|
264
252
|
return `${path.basename(specFile)}::${key}`;
|
|
265
253
|
}
|
|
@@ -289,16 +277,8 @@ function canonicalizeSnapshotLocalKey(localKey) {
|
|
|
289
277
|
}
|
|
290
278
|
return localKey;
|
|
291
279
|
}
|
|
292
|
-
function
|
|
293
|
-
const
|
|
294
|
-
const marker = `/${segment}/`;
|
|
295
|
-
const index = normalized.lastIndexOf(marker);
|
|
296
|
-
if (index >= 0)
|
|
297
|
-
return normalized.slice(index + marker.length);
|
|
298
|
-
return path.basename(normalized);
|
|
299
|
-
}
|
|
300
|
-
function writeReadableLog(logRoot, file, suites, modeName, buildCommand, runCommand, snapshotSummary) {
|
|
301
|
-
const relative = resolveArtifactRelativePath(file, "__tests__").replace(/\.ts$/, ".log");
|
|
280
|
+
function writeReadableLog(logRoot, file, inputPatterns, suites, modeName, buildCommand, runCommand, snapshotSummary) {
|
|
281
|
+
const relative = resolveSpecRelativePath(file, inputPatterns).replace(/\.ts$/i, ".log");
|
|
302
282
|
const filePath = path.join(logRoot, relative);
|
|
303
283
|
const dir = path.dirname(filePath);
|
|
304
284
|
if (!existsSync(dir))
|
|
@@ -554,7 +534,6 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
|
|
|
554
534
|
const config = mode.config;
|
|
555
535
|
const inputPatterns = resolveInputPatterns(config.input, selectors);
|
|
556
536
|
const inputFiles = (await glob(inputPatterns)).sort((a, b) => a.localeCompare(b));
|
|
557
|
-
const duplicateSpecBasenames = await resolveDuplicateSpecBasenames(config.input);
|
|
558
537
|
const snapshotEnabled = flags.snapshot !== false;
|
|
559
538
|
const createSnapshots = Boolean(flags.createSnapshots);
|
|
560
539
|
const overwriteSnapshots = Boolean(flags.overwriteSnapshots);
|
|
@@ -615,7 +594,7 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
|
|
|
615
594
|
try {
|
|
616
595
|
for (let i = 0; i < inputFiles.length; i++) {
|
|
617
596
|
const file = inputFiles[i];
|
|
618
|
-
const outFile = path.join(config.outDir,
|
|
597
|
+
const outFile = path.join(config.outDir, resolveArtifactPath(file, config.input));
|
|
619
598
|
if (!existsSync(outFile)) {
|
|
620
599
|
const buildStartedAt = Date.now();
|
|
621
600
|
await build(resolvedConfigPath, [file], options.modeName, { coverage: flags.coverage }, {}, loadedConfig);
|
|
@@ -634,7 +613,7 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
|
|
|
634
613
|
.map((token) => token.replace(/<name>/g, fileBase).replace(/<file>/g, fileToken)),
|
|
635
614
|
};
|
|
636
615
|
const runCommandForLog = formatInvocation(invocation);
|
|
637
|
-
const snapshotStore = new SnapshotStore(file, config.snapshotDir,
|
|
616
|
+
const snapshotStore = new SnapshotStore(file, config.snapshotDir, config.input);
|
|
638
617
|
let report;
|
|
639
618
|
try {
|
|
640
619
|
const runtimeEnv = {
|
|
@@ -647,9 +626,10 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
|
|
|
647
626
|
? { BROWSER: config.runOptions.runtime.browser.trim() }
|
|
648
627
|
: {}),
|
|
649
628
|
};
|
|
629
|
+
const crashEntryKey = resolveSpecRelativePath(file, config.input).replace(/\.ts$/i, "");
|
|
650
630
|
report = webSession
|
|
651
|
-
? await runWebSessionProcess(webSession, file, config.fuzz.crashDir, options.modeName, snapshotStore, snapshotEnabled, createSnapshots, overwriteSnapshots, reporter, reporterKind == "tap", runtimeEnv)
|
|
652
|
-
: await runProcess(invocation, file, config.fuzz.crashDir, options.modeName, snapshotStore, snapshotEnabled, createSnapshots, overwriteSnapshots, reporter, reporterKind == "tap", runtimeEnv);
|
|
631
|
+
? await runWebSessionProcess(webSession, file, config.fuzz.crashDir, crashEntryKey, options.modeName, snapshotStore, snapshotEnabled, createSnapshots, overwriteSnapshots, reporter, reporterKind == "tap", runtimeEnv)
|
|
632
|
+
: await runProcess(invocation, file, config.fuzz.crashDir, crashEntryKey, options.modeName, snapshotStore, snapshotEnabled, createSnapshots, overwriteSnapshots, reporter, reporterKind == "tap", runtimeEnv);
|
|
653
633
|
}
|
|
654
634
|
catch (error) {
|
|
655
635
|
const modeLabel = options.modeName ?? "default";
|
|
@@ -690,7 +670,7 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
|
|
|
690
670
|
mkdirSync(logRoot, { recursive: true });
|
|
691
671
|
}
|
|
692
672
|
for (const report of reports) {
|
|
693
|
-
writeReadableLog(logRoot, report.file, report.suites, options.modeName, options.buildCommandsByFile?.[report.file] ??
|
|
673
|
+
writeReadableLog(logRoot, report.file, config.input, report.suites, options.modeName, options.buildCommandsByFile?.[report.file] ??
|
|
694
674
|
options.buildCommand ??
|
|
695
675
|
"", report.runCommand, report.snapshotSummary);
|
|
696
676
|
}
|
|
@@ -705,10 +685,9 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
|
|
|
705
685
|
coverageDir != "none" &&
|
|
706
686
|
coverageSummary.files.length > 0) {
|
|
707
687
|
const resolvedCoverageDir = path.join(process.cwd(), coverageDir);
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
writeFileSync(path.join(resolvedCoverageDir, options.coverageFileName ?? "coverage.log.json"), JSON.stringify(coverageSummary, null, 2));
|
|
688
|
+
const coverageFilePath = path.join(resolvedCoverageDir, options.coverageFileName ?? "coverage.log.json");
|
|
689
|
+
mkdirSync(path.dirname(coverageFilePath), { recursive: true });
|
|
690
|
+
writeFileSync(coverageFilePath, JSON.stringify(coverageSummary, null, 2));
|
|
712
691
|
}
|
|
713
692
|
if (options.emitRunComplete !== false) {
|
|
714
693
|
const totalModes = Math.max(options.modeSummaryTotal ?? 1, 1);
|
|
@@ -929,51 +908,6 @@ instantiate(imports)
|
|
|
929
908
|
}
|
|
930
909
|
return null;
|
|
931
910
|
}
|
|
932
|
-
function resolveArtifactFileName(file, target, modeName, duplicateSpecBasenames = new Set()) {
|
|
933
|
-
const base = path
|
|
934
|
-
.basename(file)
|
|
935
|
-
.replace(/\.spec\.ts$/, "")
|
|
936
|
-
.replace(/\.ts$/, "");
|
|
937
|
-
const legacy = !modeName
|
|
938
|
-
? `${path.basename(file).replace(".ts", ".wasm")}`
|
|
939
|
-
: `${base}.${modeName}.${target}.wasm`;
|
|
940
|
-
if (!duplicateSpecBasenames.has(path.basename(file))) {
|
|
941
|
-
return legacy;
|
|
942
|
-
}
|
|
943
|
-
const disambiguator = resolveDisambiguator(file, duplicateSpecBasenames);
|
|
944
|
-
if (!disambiguator.length) {
|
|
945
|
-
return legacy;
|
|
946
|
-
}
|
|
947
|
-
const ext = path.extname(legacy);
|
|
948
|
-
const stem = ext.length ? legacy.slice(0, -ext.length) : legacy;
|
|
949
|
-
return `${stem}.${disambiguator}${ext}`;
|
|
950
|
-
}
|
|
951
|
-
async function resolveDuplicateSpecBasenames(configured) {
|
|
952
|
-
const patterns = Array.isArray(configured) ? configured : [configured];
|
|
953
|
-
const files = await glob(patterns);
|
|
954
|
-
const counts = new Map();
|
|
955
|
-
for (const file of files) {
|
|
956
|
-
const base = path.basename(file);
|
|
957
|
-
counts.set(base, (counts.get(base) ?? 0) + 1);
|
|
958
|
-
}
|
|
959
|
-
const duplicates = new Set();
|
|
960
|
-
for (const [base, count] of counts) {
|
|
961
|
-
if (count > 1)
|
|
962
|
-
duplicates.add(base);
|
|
963
|
-
}
|
|
964
|
-
return duplicates;
|
|
965
|
-
}
|
|
966
|
-
function resolveDisambiguator(file, duplicateSpecBasenames) {
|
|
967
|
-
if (!duplicateSpecBasenames.has(path.basename(file)))
|
|
968
|
-
return "";
|
|
969
|
-
const relDir = path.dirname(path.relative(process.cwd(), file));
|
|
970
|
-
if (!relDir.length || relDir == ".")
|
|
971
|
-
return "";
|
|
972
|
-
return relDir
|
|
973
|
-
.replace(/[\\/]+/g, "__")
|
|
974
|
-
.replace(/[^A-Za-z0-9._-]/g, "_")
|
|
975
|
-
.replace(/^_+|_+$/g, "");
|
|
976
|
-
}
|
|
977
911
|
function resolveRuntimeTargetEnv(target, wasmPath) {
|
|
978
912
|
if (target == "bindings") {
|
|
979
913
|
return resolveBindingsRuntimeEnv(wasmPath);
|
|
@@ -1382,9 +1316,52 @@ function isAllowedCoverageSourceFile(file) {
|
|
|
1382
1316
|
const lower = file.toLowerCase();
|
|
1383
1317
|
return lower.endsWith(".ts") || lower.endsWith(".as");
|
|
1384
1318
|
}
|
|
1319
|
+
// AssemblyScript normalizes node_modules/<pkg>/... to ~lib/<pkg>/... in Source.normalizedPath.
|
|
1320
|
+
// This set contains the root names that are actual AS stdlib modules, so we can distinguish
|
|
1321
|
+
// real stdlib (~lib/array.ts) from third-party packages (~lib/json-as/assembly/index.ts).
|
|
1322
|
+
const AS_STDLIB_ROOT_NAMES = new Set([
|
|
1323
|
+
"array",
|
|
1324
|
+
"arraybuffer",
|
|
1325
|
+
"atomics",
|
|
1326
|
+
"bindings",
|
|
1327
|
+
"builtins",
|
|
1328
|
+
"compat",
|
|
1329
|
+
"console",
|
|
1330
|
+
"crypto",
|
|
1331
|
+
"dataview",
|
|
1332
|
+
"date",
|
|
1333
|
+
"diagnostics",
|
|
1334
|
+
"error",
|
|
1335
|
+
"function",
|
|
1336
|
+
"iterator",
|
|
1337
|
+
"map",
|
|
1338
|
+
"math",
|
|
1339
|
+
"memory",
|
|
1340
|
+
"number",
|
|
1341
|
+
"object",
|
|
1342
|
+
"polyfills",
|
|
1343
|
+
"process",
|
|
1344
|
+
"reference",
|
|
1345
|
+
"regexp",
|
|
1346
|
+
"rt",
|
|
1347
|
+
"set",
|
|
1348
|
+
"shared",
|
|
1349
|
+
"staticarray",
|
|
1350
|
+
"string",
|
|
1351
|
+
"symbol",
|
|
1352
|
+
"table",
|
|
1353
|
+
"typedarray",
|
|
1354
|
+
"uri",
|
|
1355
|
+
"util",
|
|
1356
|
+
"vector",
|
|
1357
|
+
]);
|
|
1385
1358
|
function isAssemblyScriptStdlibFile(file) {
|
|
1386
|
-
if (file.startsWith("~lib/"))
|
|
1387
|
-
|
|
1359
|
+
if (file.startsWith("~lib/")) {
|
|
1360
|
+
// Extract the first path segment after ~lib/ (strip any file extension)
|
|
1361
|
+
const after = file.slice("~lib/".length);
|
|
1362
|
+
const root = (after.split("/")[0] ?? "").replace(/\.[^.]+$/, "");
|
|
1363
|
+
return AS_STDLIB_ROOT_NAMES.has(root);
|
|
1364
|
+
}
|
|
1388
1365
|
if (file.includes("/~lib/"))
|
|
1389
1366
|
return true;
|
|
1390
1367
|
if (file.startsWith("assemblyscript/std/"))
|
|
@@ -1402,6 +1379,21 @@ function classifyCoverageFile(file) {
|
|
|
1402
1379
|
}
|
|
1403
1380
|
function resolveCoverageDependencyPackage(file) {
|
|
1404
1381
|
const normalized = file.replace(/\\/g, "/");
|
|
1382
|
+
// AssemblyScript normalizes node_modules/<pkg>/... to ~lib/<pkg>/... at compile time.
|
|
1383
|
+
// Handle that path format so coverage.mode and coverage.dependencies work at runtime.
|
|
1384
|
+
if (normalized.startsWith("~lib/")) {
|
|
1385
|
+
const after = normalized.slice("~lib/".length);
|
|
1386
|
+
const segments = after.split("/").filter(Boolean);
|
|
1387
|
+
if (!segments.length)
|
|
1388
|
+
return null;
|
|
1389
|
+
if (segments[0].startsWith("@")) {
|
|
1390
|
+
if (segments.length < 2)
|
|
1391
|
+
return null;
|
|
1392
|
+
return `${segments[0]}/${segments[1]}`;
|
|
1393
|
+
}
|
|
1394
|
+
// Strip file extension for bare module entries like ~lib/json-as.ts (unusual but safe)
|
|
1395
|
+
return segments[0].replace(/\.[^.]+$/, "") || null;
|
|
1396
|
+
}
|
|
1405
1397
|
const marker = "/node_modules/";
|
|
1406
1398
|
const prefixed = normalized.startsWith("node_modules/")
|
|
1407
1399
|
? `/${normalized}`
|
|
@@ -1536,7 +1528,7 @@ function compareCoveragePoints(a, b) {
|
|
|
1536
1528
|
return a.type.localeCompare(b.type);
|
|
1537
1529
|
return a.hash.localeCompare(b.hash);
|
|
1538
1530
|
}
|
|
1539
|
-
async function runProcess(invocation, specFile, crashDir, modeName, snapshots, snapshotEnabled, createSnapshots, overwriteSnapshots, reporter, tapMode = false, env = process.env) {
|
|
1531
|
+
async function runProcess(invocation, specFile, crashDir, crashEntryKey, modeName, snapshots, snapshotEnabled, createSnapshots, overwriteSnapshots, reporter, tapMode = false, env = process.env) {
|
|
1540
1532
|
const child = spawn(invocation.command, invocation.args, {
|
|
1541
1533
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1542
1534
|
shell: false,
|
|
@@ -1737,6 +1729,7 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
|
|
|
1737
1729
|
persistCrashRecord(crashDir, {
|
|
1738
1730
|
kind: "test",
|
|
1739
1731
|
file: specFile,
|
|
1732
|
+
entryKey: crashEntryKey,
|
|
1740
1733
|
mode: modeName ?? "default",
|
|
1741
1734
|
error: errorText,
|
|
1742
1735
|
stdout: stdoutBuffer,
|
|
@@ -1779,6 +1772,7 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
|
|
|
1779
1772
|
persistCrashRecord(crashDir, {
|
|
1780
1773
|
kind: "test",
|
|
1781
1774
|
file: specFile,
|
|
1775
|
+
entryKey: crashEntryKey,
|
|
1782
1776
|
mode: modeName ?? "default",
|
|
1783
1777
|
error: fullError,
|
|
1784
1778
|
stdout: stdoutBuffer,
|
|
@@ -1807,6 +1801,7 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
|
|
|
1807
1801
|
persistCrashRecord(crashDir, {
|
|
1808
1802
|
kind: "test",
|
|
1809
1803
|
file: specFile,
|
|
1804
|
+
entryKey: crashEntryKey,
|
|
1810
1805
|
mode: modeName ?? "default",
|
|
1811
1806
|
error: errorText || "runtime reported an unknown error",
|
|
1812
1807
|
stdout: stdoutBuffer,
|
|
@@ -1824,6 +1819,7 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
|
|
|
1824
1819
|
persistCrashRecord(crashDir, {
|
|
1825
1820
|
kind: "test",
|
|
1826
1821
|
file: specFile,
|
|
1822
|
+
entryKey: crashEntryKey,
|
|
1827
1823
|
mode: modeName ?? "default",
|
|
1828
1824
|
error: fullError,
|
|
1829
1825
|
stdout: stdoutBuffer,
|
|
@@ -1844,6 +1840,7 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
|
|
|
1844
1840
|
persistCrashRecord(crashDir, {
|
|
1845
1841
|
kind: "test",
|
|
1846
1842
|
file: specFile,
|
|
1843
|
+
entryKey: crashEntryKey,
|
|
1847
1844
|
mode: modeName ?? "default",
|
|
1848
1845
|
error: errorText || "runtime reported an unknown error",
|
|
1849
1846
|
stdout: stdoutBuffer,
|
|
@@ -1855,7 +1852,7 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
|
|
|
1855
1852
|
}
|
|
1856
1853
|
return report;
|
|
1857
1854
|
}
|
|
1858
|
-
async function runWebSessionProcess(session, specFile, crashDir, modeName, snapshots, snapshotEnabled, createSnapshots, overwriteSnapshots, reporter, tapMode = false, env = process.env) {
|
|
1855
|
+
async function runWebSessionProcess(session, specFile, crashDir, crashEntryKey, modeName, snapshots, snapshotEnabled, createSnapshots, overwriteSnapshots, reporter, tapMode = false, env = process.env) {
|
|
1859
1856
|
const input = new PassThrough();
|
|
1860
1857
|
const output = new PassThrough();
|
|
1861
1858
|
let report = null;
|
|
@@ -2079,6 +2076,7 @@ async function runWebSessionProcess(session, specFile, crashDir, modeName, snaps
|
|
|
2079
2076
|
persistCrashRecord(crashDir, {
|
|
2080
2077
|
kind: "test",
|
|
2081
2078
|
file: specFile,
|
|
2079
|
+
entryKey: crashEntryKey,
|
|
2082
2080
|
mode: modeName ?? "default",
|
|
2083
2081
|
error: fullError,
|
|
2084
2082
|
stdout: stdoutBuffer,
|
|
@@ -2113,6 +2111,7 @@ async function runWebSessionProcess(session, specFile, crashDir, modeName, snaps
|
|
|
2113
2111
|
persistCrashRecord(crashDir, {
|
|
2114
2112
|
kind: "test",
|
|
2115
2113
|
file: specFile,
|
|
2114
|
+
entryKey: crashEntryKey,
|
|
2116
2115
|
mode: modeName ?? "default",
|
|
2117
2116
|
error: fullError,
|
|
2118
2117
|
stdout: stdoutBuffer,
|
|
@@ -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)}`,
|
package/bin/crash-store.js
CHANGED
|
@@ -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,
|