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/CHANGELOG.md +17 -3
- package/README.md +14 -7
- package/as-test.config.schema.json +142 -142
- package/assembly/__fuzz__/math.fuzz.ts +22 -0
- package/assembly/__fuzz__/seed-perf.fuzz.ts +4 -2
- package/assembly/__fuzz__/string.fuzz.ts +31 -0
- package/assembly/index.ts +6 -9
- package/assembly/src/expectation.ts +99 -42
- package/assembly/src/fuzz.ts +44 -23
- package/assembly/util/format.ts +113 -0
- package/assembly/util/helpers.ts +7 -13
- package/assembly/util/json.ts +2 -2
- package/assembly/util/wipc.ts +5 -3
- package/bin/build-worker-pool.js +7 -1
- package/bin/commands/build-core.js +6 -1
- package/bin/commands/build.js +1 -1
- package/bin/commands/clean-core.js +61 -16
- package/bin/commands/clean.js +11 -3
- package/bin/commands/fuzz-core.js +2 -2
- package/bin/commands/run-core.js +35 -24
- package/bin/commands/web-runner-source.js +14 -14
- package/bin/commands/web-session.js +6 -1
- package/bin/crash-store.js +3 -1
- package/bin/index.js +303 -124
- package/bin/reporters/default.js +175 -33
- package/bin/util.js +36 -11
- package/bin/wipc.js +7 -2
- package/lib/build/index.js +93 -25
- package/lib/src/index.ts +115 -36
- package/package.json +1 -3
- package/transform/lib/coverage.js +3 -1
package/assembly/util/helpers.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { bgGreen, bgRed } from "./format";
|
|
2
2
|
|
|
3
3
|
export function visualize<T>(value: T): string {
|
|
4
4
|
if (isNullable<T>() && changetype<usize>(value) == <usize>0) {
|
|
@@ -61,16 +61,16 @@ export function diff(left: string, right: string, not: boolean = false): Diff {
|
|
|
61
61
|
const rChar = right.charAt(i);
|
|
62
62
|
if (not) {
|
|
63
63
|
if (lChar == rChar) {
|
|
64
|
-
lDiff +=
|
|
65
|
-
rDiff +=
|
|
64
|
+
lDiff += bgGreen(rChar);
|
|
65
|
+
rDiff += bgRed(lChar);
|
|
66
66
|
} else {
|
|
67
67
|
lDiff += rChar;
|
|
68
68
|
rDiff += lChar;
|
|
69
69
|
}
|
|
70
70
|
} else {
|
|
71
71
|
if (lChar != rChar) {
|
|
72
|
-
lDiff +=
|
|
73
|
-
rDiff +=
|
|
72
|
+
lDiff += bgGreen(rChar);
|
|
73
|
+
rDiff += bgRed(lChar);
|
|
74
74
|
} else {
|
|
75
75
|
lDiff += rChar;
|
|
76
76
|
rDiff += lChar;
|
|
@@ -80,9 +80,9 @@ export function diff(left: string, right: string, not: boolean = false): Diff {
|
|
|
80
80
|
|
|
81
81
|
if (!not) {
|
|
82
82
|
for (; i < left.length; i++) {
|
|
83
|
-
rDiff +=
|
|
83
|
+
rDiff += bgRed(left.charAt(i));
|
|
84
84
|
}
|
|
85
|
-
for (; i < right.length; i++) lDiff +=
|
|
85
|
+
for (; i < right.length; i++) lDiff += bgRed(right.charAt(i));
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
return {
|
|
@@ -90,9 +90,3 @@ export function diff(left: string, right: string, not: boolean = false): Diff {
|
|
|
90
90
|
right: rDiff,
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
|
-
|
|
94
|
-
// @ts-ignore
|
|
95
|
-
@inline
|
|
96
|
-
export function colorText(format: i32[], text: string): string {
|
|
97
|
-
return `\u001b[${format[0].toString()}m${text}\u001b[${format[1].toString()}m`;
|
|
98
|
-
}
|
package/assembly/util/json.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { formatValue } from "./format";
|
|
2
2
|
|
|
3
3
|
export function quote(value: string): string {
|
|
4
4
|
return '"' + escape(value) + '"';
|
|
@@ -36,7 +36,7 @@ export function stringifyValue<T>(value: T): string {
|
|
|
36
36
|
return value.__as_test_json();
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
const formatted =
|
|
39
|
+
const formatted = formatValue<T>(value);
|
|
40
40
|
if (formatted != "none") {
|
|
41
41
|
return quote(formatted);
|
|
42
42
|
}
|
package/assembly/util/wipc.ts
CHANGED
|
@@ -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);
|
|
@@ -305,9 +304,12 @@ function wasiWriteAll(data: ArrayBuffer): void {
|
|
|
305
304
|
store<usize>(iovPtr, <usize>left, sizeof<usize>());
|
|
306
305
|
store<u32>(writtenPtr, 0, 0);
|
|
307
306
|
const errno = wasi_fd_write(1, iovPtr, 1, writtenPtr);
|
|
307
|
+
if (errno == WASI_ERRNO_AGAIN || errno == WASI_ERRNO_INTR) {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
308
310
|
if (errno != 0) return;
|
|
309
311
|
const written = <i32>load<u32>(writtenPtr, 0);
|
|
310
|
-
if (written <= 0)
|
|
312
|
+
if (written <= 0) continue;
|
|
311
313
|
offset += written;
|
|
312
314
|
}
|
|
313
315
|
}
|
package/bin/build-worker-pool.js
CHANGED
|
@@ -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.
|
|
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}`,
|
package/bin/commands/build.js
CHANGED
|
@@ -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");
|
|
@@ -3,36 +3,50 @@ import { existsSync, rmSync } from "fs";
|
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import { applyMode, loadConfig } from "../util.js";
|
|
5
5
|
const DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./as-test.config.json");
|
|
6
|
-
export async function clean(configPath = DEFAULT_CONFIG_PATH, modes = [undefined]) {
|
|
6
|
+
export async function clean(configPath = DEFAULT_CONFIG_PATH, modes = [undefined], fullClean = false) {
|
|
7
7
|
const loadedConfig = loadConfig(configPath, true);
|
|
8
8
|
const targets = new Map();
|
|
9
9
|
const ownership = buildOwnershipMap(loadedConfig);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
if (fullClean) {
|
|
11
|
+
collectRootTarget(targets, loadedConfig.outDir, "build");
|
|
12
|
+
collectRootTarget(targets, loadedConfig.fuzz.crashDir, "crashes");
|
|
13
|
+
collectRootTarget(targets, loadedConfig.coverageDir, "coverage");
|
|
14
|
+
collectRootTarget(targets, loadedConfig.logs, "logs");
|
|
15
|
+
for (const modeName of modes) {
|
|
16
|
+
const active = applyMode(loadedConfig, modeName).config;
|
|
17
|
+
collectTarget(targets, active.outDir, modeName, "build");
|
|
18
|
+
collectTarget(targets, active.fuzz.crashDir, modeName, "crashes");
|
|
19
|
+
collectTarget(targets, active.coverageDir, modeName, "coverage");
|
|
20
|
+
collectTarget(targets, active.logs, modeName, "logs");
|
|
21
|
+
}
|
|
22
|
+
pruneNestedTargets(targets);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
for (const modeName of modes) {
|
|
26
|
+
const active = applyMode(loadedConfig, modeName).config;
|
|
27
|
+
collectTarget(targets, active.outDir, modeName, "build");
|
|
28
|
+
collectTarget(targets, active.fuzz.crashDir, modeName, "crashes");
|
|
29
|
+
collectTarget(targets, active.coverageDir, modeName, "coverage");
|
|
30
|
+
collectTarget(targets, active.logs, modeName, "logs");
|
|
31
|
+
}
|
|
15
32
|
}
|
|
16
33
|
let removed = 0;
|
|
17
|
-
let skipped = 0;
|
|
18
34
|
for (const [targetPath, owners] of [...targets.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
35
|
+
if (!fullClean) {
|
|
36
|
+
const allOwners = ownership.get(targetPath) ?? owners;
|
|
37
|
+
const unselectedOwners = allOwners.filter((owner) => !owners.includes(owner));
|
|
38
|
+
if (unselectedOwners.length) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
25
41
|
}
|
|
26
42
|
if (!existsSync(targetPath)) {
|
|
27
|
-
skipped++;
|
|
28
|
-
process.stdout.write(`${chalk.dim("skip")} ${toRelativePath(targetPath)} ${chalk.dim(`(${owners.join(", ")})`)}\n`);
|
|
29
43
|
continue;
|
|
30
44
|
}
|
|
31
45
|
rmSync(targetPath, { recursive: true, force: true });
|
|
32
46
|
removed++;
|
|
33
47
|
process.stdout.write(`${chalk.bgGreenBright.black(" CLEAN ")} ${toRelativePath(targetPath)} ${chalk.dim(`(${owners.join(", ")})`)}\n`);
|
|
34
48
|
}
|
|
35
|
-
process.stdout.write(`${chalk.bold("Summary:")} removed ${removed} path(s)
|
|
49
|
+
process.stdout.write(`${chalk.bold("Summary:")} removed ${removed} path(s)\n`);
|
|
36
50
|
}
|
|
37
51
|
function buildOwnershipMap(loadedConfig) {
|
|
38
52
|
const ownership = new Map();
|
|
@@ -44,10 +58,25 @@ function buildOwnershipMap(loadedConfig) {
|
|
|
44
58
|
const active = applyMode(loadedConfig, modeName).config;
|
|
45
59
|
collectOwnership(ownership, active.outDir, modeName, "build");
|
|
46
60
|
collectOwnership(ownership, active.fuzz.crashDir, modeName, "crashes");
|
|
61
|
+
collectOwnership(ownership, active.coverageDir, modeName, "coverage");
|
|
47
62
|
collectOwnership(ownership, active.logs, modeName, "logs");
|
|
48
63
|
}
|
|
49
64
|
return ownership;
|
|
50
65
|
}
|
|
66
|
+
function collectRootTarget(targets, rawPath, kind) {
|
|
67
|
+
if (!rawPath || rawPath == "none")
|
|
68
|
+
return;
|
|
69
|
+
const resolved = path.resolve(process.cwd(), rawPath);
|
|
70
|
+
ensureSafeCleanPath(resolved, rawPath, kind);
|
|
71
|
+
const owner = `all:${kind}`;
|
|
72
|
+
const existing = targets.get(resolved);
|
|
73
|
+
if (existing) {
|
|
74
|
+
if (!existing.includes(owner))
|
|
75
|
+
existing.push(owner);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
targets.set(resolved, [owner]);
|
|
79
|
+
}
|
|
51
80
|
function collectOwnership(ownership, rawPath, modeName, kind) {
|
|
52
81
|
if (!rawPath || rawPath == "none")
|
|
53
82
|
return;
|
|
@@ -76,6 +105,22 @@ function collectTarget(targets, rawPath, modeName, kind) {
|
|
|
76
105
|
}
|
|
77
106
|
targets.set(resolved, [owner]);
|
|
78
107
|
}
|
|
108
|
+
function pruneNestedTargets(targets) {
|
|
109
|
+
const paths = [...targets.keys()].sort((a, b) => a.length - b.length);
|
|
110
|
+
for (const targetPath of paths) {
|
|
111
|
+
for (const otherPath of paths) {
|
|
112
|
+
if (targetPath == otherPath)
|
|
113
|
+
continue;
|
|
114
|
+
const relative = path.relative(targetPath, otherPath);
|
|
115
|
+
if (!relative.length ||
|
|
116
|
+
relative == ".." ||
|
|
117
|
+
relative.startsWith(`..${path.sep}`)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
targets.delete(otherPath);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
79
124
|
function ensureSafeCleanPath(resolvedPath, rawPath, kind) {
|
|
80
125
|
const cwd = path.resolve(process.cwd());
|
|
81
126
|
const relative = path.relative(cwd, resolvedPath);
|
package/bin/commands/clean.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { clean } from "./clean-core.js";
|
|
2
|
+
import { loadConfig } from "../util.js";
|
|
2
3
|
export { clean } from "./clean-core.js";
|
|
3
|
-
export async function executeCleanCommand(configPath, selectedModes, resolveExecutionModes) {
|
|
4
|
-
const modeTargets =
|
|
5
|
-
|
|
4
|
+
export async function executeCleanCommand(rawArgs, configPath, selectedModes, resolveExecutionModes) {
|
|
5
|
+
const modeTargets = selectedModes.length > 0
|
|
6
|
+
? resolveExecutionModes(configPath, selectedModes)
|
|
7
|
+
: resolveAllCleanModes(configPath);
|
|
8
|
+
await clean(configPath, modeTargets, selectedModes.length == 0);
|
|
9
|
+
}
|
|
10
|
+
function resolveAllCleanModes(configPath) {
|
|
11
|
+
const resolvedConfigPath = configPath ?? "./as-test.config.json";
|
|
12
|
+
const config = loadConfig(resolvedConfigPath, true);
|
|
13
|
+
return [undefined, ...Object.keys(config.modes)];
|
|
6
14
|
}
|
|
@@ -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);
|
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, 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 ${
|
|
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 ${
|
|
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 ?? "")
|
|
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)
|
|
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)
|
|
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
|
-
? `${
|
|
510
|
-
:
|
|
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 ${
|
|
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:
|
|
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 &&
|
|
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:
|
|
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")),
|
|
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
|
|
1980
|
-
|
|
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 ||
|
|
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:
|
|
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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
6
|
+
import { instantiate } from "as-test/lib";
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
let exports = null;
|
|
9
|
+
const imports = {};
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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 [
|
|
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)
|
package/bin/crash-store.js
CHANGED
|
@@ -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
|
|
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`);
|