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.
- package/CHANGELOG.md +48 -0
- package/as-test.config.schema.json +15 -0
- package/assembly/coverage.ts +22 -26
- package/assembly/index.ts +2 -0
- package/assembly/src/expectation.ts +152 -44
- package/assembly/src/mode.ts +55 -0
- package/bin/commands/build-core.js +190 -65
- package/bin/commands/build.js +3 -1
- package/bin/commands/fuzz-core.js +30 -56
- package/bin/commands/init-core.js +253 -5
- package/bin/commands/run-core.js +38 -119
- package/bin/commands/test.js +1 -1
- package/bin/commands/web-session.js +4 -0
- package/bin/crash-store.js +1 -1
- package/bin/index.js +94 -152
- package/bin/types.js +7 -0
- package/bin/util.js +117 -0
- package/package.json +14 -9
- package/transform/lib/index.js +26 -0
package/bin/commands/run-core.js
CHANGED
|
@@ -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,
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
846
|
-
|
|
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,
|
package/bin/commands/test.js
CHANGED
|
@@ -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)}`,
|
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,
|