as-test 1.0.16 → 1.1.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/README.md +45 -4
- package/as-test.config.schema.json +5 -0
- package/assembly/util/wipc.ts +11 -4
- package/bin/commands/clean-core.js +92 -0
- package/bin/commands/clean.js +6 -0
- package/bin/commands/init-core.js +33 -225
- package/bin/commands/run-core.js +433 -289
- package/bin/commands/web-runner-source.js +14 -700
- package/bin/commands/web-session.js +1144 -0
- package/bin/index.js +390 -78
- package/bin/types.js +1 -0
- package/bin/util.js +16 -1
- package/lib/build/index.d.ts +1 -0
- package/lib/build/index.js +1098 -0
- package/lib/build/web-runner/client.d.ts +1 -0
- package/lib/build/web-runner/client.js +167 -0
- package/lib/build/web-runner/html.d.ts +1 -0
- package/lib/build/web-runner/html.js +201 -0
- package/lib/build/web-runner/worker.d.ts +1 -0
- package/lib/build/web-runner/worker.js +271 -0
- package/lib/src/index.ts +1248 -0
- package/package.json +14 -4
- package/transform/lib/mock.js +50 -27
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
### Upgrading to 1.1.0
|
|
6
|
+
|
|
7
|
+
- refresh generated runners with:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
rm -rf .as-test/runners && npx as-test init
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- generated runners now use a single file per target and import `instantiate(...)` from `as-test/lib`
|
|
14
|
+
- new bindings and web runners no longer use `*.hooks.js`
|
|
15
|
+
- named modes now support `default: false` to make a mode manual-only
|
|
16
|
+
- the repo examples and default config now use mode names like `node:wasi`, `node:bindings`, `chromium`, and `chromium:headless`
|
|
17
|
+
|
|
18
|
+
### Runtime & Runners
|
|
19
|
+
|
|
20
|
+
- feat: replace the split bindings/web hooks model with single-file runners that import `instantiate(...)` from `as-test/lib`, keeping bindings, WASI, and web runner syntax aligned.
|
|
21
|
+
- feat: add `as-test/lib` as the shared JS runtime host layer for bindings, WASI, and web targets, with runtime artifact resolution happening out of sight before runner execution.
|
|
22
|
+
- feat: autodetect bindings helper shape at runtime support level (`raw`, `esm`, or `none`) and keep the generated runner surface minimal.
|
|
23
|
+
- fix: make `ast run` rebuild missing artifacts on demand instead of failing when only some selected outputs already exist.
|
|
24
|
+
- fix: report real lazy-build time in `ast run` summaries instead of always printing `0us build`.
|
|
25
|
+
- fix: remove build artifact copy/reuse shortcuts so each selected file/mode compiles directly, avoiding stale output reuse across modes.
|
|
26
|
+
|
|
27
|
+
### Web Runtime
|
|
28
|
+
|
|
29
|
+
- feat: move headful web execution to a persistent single-browser-session architecture that opens one page, runs multiple binaries through it, and keeps browser-side runtime details hidden from the runner file.
|
|
30
|
+
- feat: redesign the non-headless browser page into a minimal macOS-inspired loading surface with light/dark mode support and simpler status messaging.
|
|
31
|
+
- feat: make headful web runs wait for the user to open the local session URL, and expose a browser-side exit control.
|
|
32
|
+
- fix: keep all browser bootstrap, asset, and websocket traffic on one local port.
|
|
33
|
+
- fix: improve browser discovery and launch behavior across Chromium, Firefox, and WebKit, including Playwright cache lookup, macOS app bundle resolution, paths with spaces, and owned-process teardown.
|
|
34
|
+
- fix: fail terminal-side runs when the browser side disconnects unexpectedly, and close the browser side when the websocket is lost.
|
|
35
|
+
|
|
36
|
+
### WASI
|
|
37
|
+
|
|
38
|
+
- fix: make the WASI stdin transport retry only on retryable WASI read errors (`AGAIN` and `INTR`), which resolves intermittent snapshot reply corruption in `node:wasi` runs.
|
|
39
|
+
|
|
40
|
+
### Modes & CLI
|
|
41
|
+
|
|
42
|
+
- feat: add per-mode `default: boolean` selection so modes can be included in implicit runs or kept manual-only.
|
|
43
|
+
- feat: add `ast clean` to remove configured build outputs, crash reports, and logs for the selected modes.
|
|
44
|
+
- fix: restore unnamed root-config execution alongside named default modes when `--mode` is omitted.
|
|
45
|
+
- fix: make `ast clean --mode ...` skip shared output paths that are still owned by unselected modes instead of deleting them.
|
|
46
|
+
|
|
47
|
+
### Tests
|
|
48
|
+
|
|
49
|
+
- feat: add integration coverage for bindings (`raw`, `esm`, `none`), WASI, and web runtime paths, including browser-resolution regressions and single-origin web runner behavior.
|
|
50
|
+
|
|
3
51
|
## 2026-05-08 - v1.0.16
|
|
4
52
|
|
|
5
53
|
- feat: modes inherit pre-declared properties if not explicitly overriden
|
package/README.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
╠═╣ ╚═╗ ══ ║ ╠═ ╚═╗ ║
|
|
3
3
|
╩ ╩ ╚═╝ ╩ ╚═╝ ╚═╝ ╩ </pre></h1>
|
|
4
4
|
|
|
5
|
+
> **Upgrading to 1.1.0**
|
|
6
|
+
>
|
|
7
|
+
> See [CHANGELOG.md](./CHANGELOG.md) for upgrade notes. In most projects, refreshing generated runners is enough:
|
|
8
|
+
>
|
|
9
|
+
> ```bash
|
|
10
|
+
> rm -rf .as-test/runners && npx as-test init
|
|
11
|
+
> ```
|
|
12
|
+
|
|
5
13
|
<details>
|
|
6
14
|
<summary>Table of Contents</summary>
|
|
7
15
|
|
|
@@ -77,7 +85,7 @@ Minimal `as-test.config.json`:
|
|
|
77
85
|
},
|
|
78
86
|
"runOptions": {
|
|
79
87
|
"runtime": {
|
|
80
|
-
"cmd": "node .as-test/runners/default.wasi.js
|
|
88
|
+
"cmd": "node .as-test/runners/default.wasi.js"
|
|
81
89
|
}
|
|
82
90
|
}
|
|
83
91
|
}
|
|
@@ -323,7 +331,7 @@ For example, a simple WASI setup in `as-test.config.json` can look like this:
|
|
|
323
331
|
},
|
|
324
332
|
"runOptions": {
|
|
325
333
|
"runtime": {
|
|
326
|
-
"cmd": "node ./.as-test/runners/default.wasi.js
|
|
334
|
+
"cmd": "node ./.as-test/runners/default.wasi.js"
|
|
327
335
|
}
|
|
328
336
|
}
|
|
329
337
|
}
|
|
@@ -342,22 +350,41 @@ If you want to keep more than one runtime around, use modes:
|
|
|
342
350
|
"input": ["./assembly/__tests__/*.spec.ts"],
|
|
343
351
|
"modes": {
|
|
344
352
|
"wasi": {
|
|
353
|
+
"default": true,
|
|
345
354
|
"buildOptions": {
|
|
346
355
|
"target": "wasi"
|
|
347
356
|
},
|
|
348
357
|
"runOptions": {
|
|
349
358
|
"runtime": {
|
|
350
|
-
"cmd": "node ./.as-test/runners/default.wasi.js
|
|
359
|
+
"cmd": "node ./.as-test/runners/default.wasi.js"
|
|
351
360
|
}
|
|
352
361
|
}
|
|
353
362
|
},
|
|
354
363
|
"bindings": {
|
|
364
|
+
"default": true,
|
|
355
365
|
"buildOptions": {
|
|
356
366
|
"target": "bindings"
|
|
357
367
|
},
|
|
358
368
|
"runOptions": {
|
|
359
369
|
"runtime": {
|
|
360
|
-
"cmd": "node ./.as-test/runners/default.bindings.js
|
|
370
|
+
"cmd": "node ./.as-test/runners/default.bindings.js"
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Set `"default": false` on a mode when you want to keep it available for explicit `--mode ...` runs without including it in normal runs:
|
|
379
|
+
|
|
380
|
+
```json
|
|
381
|
+
{
|
|
382
|
+
"modes": {
|
|
383
|
+
"web": {
|
|
384
|
+
"default": false,
|
|
385
|
+
"runOptions": {
|
|
386
|
+
"runtime": {
|
|
387
|
+
"browser": "chromium"
|
|
361
388
|
}
|
|
362
389
|
}
|
|
363
390
|
}
|
|
@@ -365,6 +392,20 @@ If you want to keep more than one runtime around, use modes:
|
|
|
365
392
|
}
|
|
366
393
|
```
|
|
367
394
|
|
|
395
|
+
With that setup:
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
npx ast test
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
runs the root/default config plus any modes whose `"default"` flag is not `false`, while:
|
|
402
|
+
|
|
403
|
+
```bash
|
|
404
|
+
npx ast test --mode web
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
still runs the `web` mode explicitly.
|
|
408
|
+
|
|
368
409
|
Modes can also be full config objects. That means a mode can override fuzzing, input globs, output aliases, runtime, build flags, and the rest of the normal config surface:
|
|
369
410
|
|
|
370
411
|
```json
|
|
@@ -305,6 +305,11 @@
|
|
|
305
305
|
"type": "object",
|
|
306
306
|
"additionalProperties": false,
|
|
307
307
|
"properties": {
|
|
308
|
+
"default": {
|
|
309
|
+
"type": "boolean",
|
|
310
|
+
"description": "Include this mode when --mode is omitted. Defaults to true.",
|
|
311
|
+
"default": true
|
|
312
|
+
},
|
|
308
313
|
"$schema": {
|
|
309
314
|
"type": "string"
|
|
310
315
|
},
|
package/assembly/util/wipc.ts
CHANGED
|
@@ -34,6 +34,8 @@ const HEADER_SIZE: i32 = 9;
|
|
|
34
34
|
const IOV_SIZE: usize = sizeof<usize>() * 2;
|
|
35
35
|
const U32_SIZE: usize = sizeof<u32>();
|
|
36
36
|
const REPORT_CHUNK_BYTES: i32 = 65536;
|
|
37
|
+
const WASI_ERRNO_AGAIN: i32 = 6;
|
|
38
|
+
const WASI_ERRNO_INTR: i32 = 27;
|
|
37
39
|
|
|
38
40
|
// @ts-ignore
|
|
39
41
|
const IS_BINDINGS: bool = isDefined(AS_TEST_BINDINGS);
|
|
@@ -319,10 +321,15 @@ function wasiRead(max: i32): ArrayBuffer {
|
|
|
319
321
|
|
|
320
322
|
store<usize>(iovPtr, changetype<usize>(out), 0);
|
|
321
323
|
store<usize>(iovPtr, <usize>max, sizeof<usize>());
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
324
|
+
while (true) {
|
|
325
|
+
store<u32>(readPtr, 0, 0);
|
|
326
|
+
const errno = wasi_fd_read(0, iovPtr, 1, readPtr);
|
|
327
|
+
if (errno == WASI_ERRNO_AGAIN || errno == WASI_ERRNO_INTR) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (errno != 0) return new ArrayBuffer(0);
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
326
333
|
|
|
327
334
|
const size = <i32>load<u32>(readPtr, 0);
|
|
328
335
|
if (size <= 0) return new ArrayBuffer(0);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { existsSync, rmSync } from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { applyMode, loadConfig } from "../util.js";
|
|
5
|
+
const DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./as-test.config.json");
|
|
6
|
+
export async function clean(configPath = DEFAULT_CONFIG_PATH, modes = [undefined]) {
|
|
7
|
+
const loadedConfig = loadConfig(configPath, true);
|
|
8
|
+
const targets = new Map();
|
|
9
|
+
const ownership = buildOwnershipMap(loadedConfig);
|
|
10
|
+
for (const modeName of modes) {
|
|
11
|
+
const active = applyMode(loadedConfig, modeName).config;
|
|
12
|
+
collectTarget(targets, active.outDir, modeName, "build");
|
|
13
|
+
collectTarget(targets, active.fuzz.crashDir, modeName, "crashes");
|
|
14
|
+
collectTarget(targets, active.logs, modeName, "logs");
|
|
15
|
+
}
|
|
16
|
+
let removed = 0;
|
|
17
|
+
let skipped = 0;
|
|
18
|
+
for (const [targetPath, owners] of [...targets.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
19
|
+
const allOwners = ownership.get(targetPath) ?? owners;
|
|
20
|
+
const unselectedOwners = allOwners.filter((owner) => !owners.includes(owner));
|
|
21
|
+
if (unselectedOwners.length) {
|
|
22
|
+
skipped++;
|
|
23
|
+
process.stdout.write(`${chalk.dim("skip")} ${toRelativePath(targetPath)} ${chalk.dim(`(shared with ${unselectedOwners.join(", ")})`)}\n`);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (!existsSync(targetPath)) {
|
|
27
|
+
skipped++;
|
|
28
|
+
process.stdout.write(`${chalk.dim("skip")} ${toRelativePath(targetPath)} ${chalk.dim(`(${owners.join(", ")})`)}\n`);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
rmSync(targetPath, { recursive: true, force: true });
|
|
32
|
+
removed++;
|
|
33
|
+
process.stdout.write(`${chalk.bgGreenBright.black(" CLEAN ")} ${toRelativePath(targetPath)} ${chalk.dim(`(${owners.join(", ")})`)}\n`);
|
|
34
|
+
}
|
|
35
|
+
process.stdout.write(`${chalk.bold("Summary:")} removed ${removed} path(s), skipped ${skipped} missing path(s)\n`);
|
|
36
|
+
}
|
|
37
|
+
function buildOwnershipMap(loadedConfig) {
|
|
38
|
+
const ownership = new Map();
|
|
39
|
+
const modeNames = [
|
|
40
|
+
undefined,
|
|
41
|
+
...Object.keys(loadedConfig.modes),
|
|
42
|
+
];
|
|
43
|
+
for (const modeName of modeNames) {
|
|
44
|
+
const active = applyMode(loadedConfig, modeName).config;
|
|
45
|
+
collectOwnership(ownership, active.outDir, modeName, "build");
|
|
46
|
+
collectOwnership(ownership, active.fuzz.crashDir, modeName, "crashes");
|
|
47
|
+
collectOwnership(ownership, active.logs, modeName, "logs");
|
|
48
|
+
}
|
|
49
|
+
return ownership;
|
|
50
|
+
}
|
|
51
|
+
function collectOwnership(ownership, rawPath, modeName, kind) {
|
|
52
|
+
if (!rawPath || rawPath == "none")
|
|
53
|
+
return;
|
|
54
|
+
const resolved = path.resolve(process.cwd(), rawPath);
|
|
55
|
+
ensureSafeCleanPath(resolved, rawPath, kind);
|
|
56
|
+
const owner = `${modeName ?? "default"}:${kind}`;
|
|
57
|
+
const existing = ownership.get(resolved);
|
|
58
|
+
if (existing) {
|
|
59
|
+
if (!existing.includes(owner))
|
|
60
|
+
existing.push(owner);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
ownership.set(resolved, [owner]);
|
|
64
|
+
}
|
|
65
|
+
function collectTarget(targets, rawPath, modeName, kind) {
|
|
66
|
+
if (!rawPath || rawPath == "none")
|
|
67
|
+
return;
|
|
68
|
+
const resolved = path.resolve(process.cwd(), rawPath);
|
|
69
|
+
ensureSafeCleanPath(resolved, rawPath, kind);
|
|
70
|
+
const owner = `${modeName ?? "default"}:${kind}`;
|
|
71
|
+
const existing = targets.get(resolved);
|
|
72
|
+
if (existing) {
|
|
73
|
+
if (!existing.includes(owner))
|
|
74
|
+
existing.push(owner);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
targets.set(resolved, [owner]);
|
|
78
|
+
}
|
|
79
|
+
function ensureSafeCleanPath(resolvedPath, rawPath, kind) {
|
|
80
|
+
const cwd = path.resolve(process.cwd());
|
|
81
|
+
const relative = path.relative(cwd, resolvedPath);
|
|
82
|
+
if (!relative.length ||
|
|
83
|
+
relative == ".." ||
|
|
84
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
85
|
+
path.parse(resolvedPath).root == resolvedPath) {
|
|
86
|
+
throw new Error(`refusing to clean unsafe ${kind} path "${rawPath}" (${resolvedPath})`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function toRelativePath(targetPath) {
|
|
90
|
+
const relative = path.relative(process.cwd(), targetPath);
|
|
91
|
+
return relative.length ? relative : ".";
|
|
92
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { clean } from "./clean-core.js";
|
|
2
|
+
export { clean } from "./clean-core.js";
|
|
3
|
+
export async function executeCleanCommand(configPath, selectedModes, resolveExecutionModes) {
|
|
4
|
+
const modeTargets = resolveExecutionModes(configPath, selectedModes);
|
|
5
|
+
await clean(configPath, modeTargets);
|
|
6
|
+
}
|
|
@@ -188,7 +188,7 @@ async function runInteractiveOnboarding(options, face) {
|
|
|
188
188
|
},
|
|
189
189
|
{
|
|
190
190
|
value: "web",
|
|
191
|
-
label: "web (default runner: node .as-test/runners/default.web.js
|
|
191
|
+
label: "web (default runner: node .as-test/runners/default.web.js)",
|
|
192
192
|
},
|
|
193
193
|
], face, "wasi"));
|
|
194
194
|
if (options.target || onboardingMode == "quick") {
|
|
@@ -396,10 +396,6 @@ function printPlan(root, target, example, fuzzExample, install) {
|
|
|
396
396
|
path: ".as-test/runners/default.bindings.js",
|
|
397
397
|
isDir: false,
|
|
398
398
|
});
|
|
399
|
-
fileEntries.push({
|
|
400
|
-
path: ".as-test/runners/default.bindings.hooks.js",
|
|
401
|
-
isDir: false,
|
|
402
|
-
});
|
|
403
399
|
fileEntries.push({
|
|
404
400
|
path: ".as-test/runners/default.wasi.js",
|
|
405
401
|
isDir: false,
|
|
@@ -408,10 +404,6 @@ function printPlan(root, target, example, fuzzExample, install) {
|
|
|
408
404
|
path: ".as-test/runners/default.web.js",
|
|
409
405
|
isDir: false,
|
|
410
406
|
});
|
|
411
|
-
fileEntries.push({
|
|
412
|
-
path: ".as-test/runners/default.web.hooks.js",
|
|
413
|
-
isDir: false,
|
|
414
|
-
});
|
|
415
407
|
}
|
|
416
408
|
if (example != "none") {
|
|
417
409
|
fileEntries.push({
|
|
@@ -484,19 +476,28 @@ function applyInit(root, target, example, fuzzExample, force) {
|
|
|
484
476
|
runOptions: {
|
|
485
477
|
runtime: {
|
|
486
478
|
cmd: target == "wasi"
|
|
487
|
-
? "node .as-test/runners/default.wasi.js
|
|
479
|
+
? "node .as-test/runners/default.wasi.js"
|
|
488
480
|
: target == "bindings"
|
|
489
|
-
? "node .as-test/runners/default.bindings.js
|
|
490
|
-
: "node .as-test/runners/default.web.js
|
|
481
|
+
? "node .as-test/runners/default.bindings.js"
|
|
482
|
+
: "node .as-test/runners/default.web.js",
|
|
491
483
|
},
|
|
492
484
|
reporter: "default",
|
|
493
485
|
},
|
|
494
486
|
modes: target == "web"
|
|
495
487
|
? {
|
|
488
|
+
web: {
|
|
489
|
+
default: false,
|
|
490
|
+
runOptions: {
|
|
491
|
+
runtime: {
|
|
492
|
+
cmd: "node .as-test/runners/default.web.js",
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
496
|
"web-headless": {
|
|
497
|
+
default: false,
|
|
497
498
|
runOptions: {
|
|
498
499
|
runtime: {
|
|
499
|
-
cmd: "node .as-test/runners/default.web.js --headless
|
|
500
|
+
cmd: "node .as-test/runners/default.web.js --headless",
|
|
500
501
|
},
|
|
501
502
|
},
|
|
502
503
|
},
|
|
@@ -521,18 +522,10 @@ function applyInit(root, target, example, fuzzExample, force) {
|
|
|
521
522
|
const runnerPath = path.join(root, ".as-test/runners/default.bindings.js");
|
|
522
523
|
writeManagedFile(runnerPath, buildBindingsRunner(), force, summary, ".as-test/runners/default.bindings.js");
|
|
523
524
|
}
|
|
524
|
-
if (target == "wasi" || target == "bindings" || target == "web") {
|
|
525
|
-
const hooksPath = path.join(root, ".as-test/runners/default.bindings.hooks.js");
|
|
526
|
-
writeManagedFile(hooksPath, buildBindingsRunnerHooks(), force, summary, ".as-test/runners/default.bindings.hooks.js");
|
|
527
|
-
}
|
|
528
525
|
if (target == "wasi" || target == "bindings" || target == "web") {
|
|
529
526
|
const runnerPath = path.join(root, ".as-test/runners/default.web.js");
|
|
530
527
|
writeManagedFile(runnerPath, buildWebRunnerSource(), force, summary, ".as-test/runners/default.web.js");
|
|
531
528
|
}
|
|
532
|
-
if (target == "wasi" || target == "bindings" || target == "web") {
|
|
533
|
-
const hooksPath = path.join(root, ".as-test/runners/default.web.hooks.js");
|
|
534
|
-
writeManagedFile(hooksPath, buildWebRunnerHooks(), force, summary, ".as-test/runners/default.web.hooks.js");
|
|
535
|
-
}
|
|
536
529
|
const pkgPath = path.join(root, "package.json");
|
|
537
530
|
const pkg = existsSync(pkgPath)
|
|
538
531
|
? JSON.parse(readFileSync(pkgPath, "utf8"))
|
|
@@ -989,217 +982,32 @@ fuzz("basic string fuzzer", (value: string): bool => {
|
|
|
989
982
|
`;
|
|
990
983
|
}
|
|
991
984
|
function buildWasiRunner() {
|
|
992
|
-
return `import {
|
|
993
|
-
import { WASI } from "wasi";
|
|
985
|
+
return `import { instantiate } from "as-test/lib";
|
|
994
986
|
|
|
995
|
-
const
|
|
996
|
-
process.emitWarning = ((warning, ...args) => {
|
|
997
|
-
const type = typeof args[0] == "string" ? args[0] : "";
|
|
998
|
-
const name = typeof warning?.name == "string" ? warning.name : type;
|
|
999
|
-
const message =
|
|
1000
|
-
typeof warning == "string" ? warning : String(warning?.message ?? "");
|
|
1001
|
-
if (
|
|
1002
|
-
name == "ExperimentalWarning" &&
|
|
1003
|
-
message.includes("WASI is an experimental feature")
|
|
1004
|
-
) {
|
|
1005
|
-
return;
|
|
1006
|
-
}
|
|
1007
|
-
return originalEmitWarning(warning, ...args);
|
|
1008
|
-
});
|
|
1009
|
-
|
|
1010
|
-
const wasmPath = process.argv[2];
|
|
1011
|
-
if (!wasmPath) {
|
|
1012
|
-
process.stderr.write("usage: node ./.as-test/runners/default.wasi.js <file.wasm>\\n");
|
|
1013
|
-
process.exit(1);
|
|
1014
|
-
}
|
|
987
|
+
const imports = {};
|
|
1015
988
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
989
|
+
instantiate(imports)
|
|
990
|
+
.then((instance) => {
|
|
991
|
+
instance.exports.start?.();
|
|
992
|
+
// Add extra startup logic here when needed.
|
|
993
|
+
})
|
|
994
|
+
.catch((error) => {
|
|
995
|
+
throw new Error("Failed to run WASI module: " + String(error));
|
|
1022
996
|
});
|
|
1023
|
-
|
|
1024
|
-
const binary = readFileSync(wasmPath);
|
|
1025
|
-
const module = new WebAssembly.Module(binary);
|
|
1026
|
-
const instance = new WebAssembly.Instance(module, {
|
|
1027
|
-
env: {
|
|
1028
|
-
__as_test_request_fuzz_config() {
|
|
1029
|
-
return 0;
|
|
1030
|
-
},
|
|
1031
|
-
},
|
|
1032
|
-
wasi_snapshot_preview1: wasi.wasiImport,
|
|
1033
|
-
});
|
|
1034
|
-
wasi.start(instance);
|
|
1035
|
-
} catch (error) {
|
|
1036
|
-
process.stderr.write("failed to run WASI module: " + String(error) + "\\n");
|
|
1037
|
-
process.exit(1);
|
|
1038
|
-
}
|
|
1039
997
|
`;
|
|
1040
998
|
}
|
|
1041
999
|
function buildBindingsRunner() {
|
|
1042
|
-
return `import
|
|
1043
|
-
import path from "path";
|
|
1044
|
-
import { pathToFileURL } from "url";
|
|
1045
|
-
|
|
1046
|
-
const HOOKS_PATH = path.resolve(
|
|
1047
|
-
path.dirname(new URL(import.meta.url).pathname),
|
|
1048
|
-
"./default.bindings.hooks.js",
|
|
1049
|
-
);
|
|
1000
|
+
return `import { instantiate } from "as-test/lib";
|
|
1050
1001
|
|
|
1051
|
-
|
|
1052
|
-
const out = Buffer.alloc(length);
|
|
1053
|
-
let offset = 0;
|
|
1054
|
-
while (offset < length) {
|
|
1055
|
-
let read = 0;
|
|
1056
|
-
try {
|
|
1057
|
-
read = fs.readSync(0, out, offset, length - offset, null);
|
|
1058
|
-
} catch (error) {
|
|
1059
|
-
if (error && error.code === "EAGAIN") {
|
|
1060
|
-
continue;
|
|
1061
|
-
}
|
|
1062
|
-
throw error;
|
|
1063
|
-
}
|
|
1064
|
-
if (!read) break;
|
|
1065
|
-
offset += read;
|
|
1066
|
-
}
|
|
1067
|
-
const view = out.subarray(0, offset);
|
|
1068
|
-
return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
function writeRaw(data) {
|
|
1072
|
-
const view = Buffer.from(data);
|
|
1073
|
-
fs.writeSync(1, view);
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
function createRunnerContext({ wasmPath, module, helperPath }) {
|
|
1077
|
-
return {
|
|
1078
|
-
wasmPath,
|
|
1079
|
-
helperPath,
|
|
1080
|
-
module,
|
|
1081
|
-
argv: process.argv.slice(2),
|
|
1082
|
-
env: process.env,
|
|
1083
|
-
readFrame(size) {
|
|
1084
|
-
return readExact(Number(size ?? 0));
|
|
1085
|
-
},
|
|
1086
|
-
writeFrame(data) {
|
|
1087
|
-
writeRaw(data);
|
|
1088
|
-
return true;
|
|
1089
|
-
},
|
|
1090
|
-
};
|
|
1091
|
-
}
|
|
1002
|
+
const imports = {};
|
|
1092
1003
|
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
};
|
|
1101
|
-
process.stdin.read = (size) => ctx.readFrame(size);
|
|
1102
|
-
return {};
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
function mergeImports(...groups) {
|
|
1106
|
-
const out = {};
|
|
1107
|
-
for (const group of groups) {
|
|
1108
|
-
if (!group || typeof group != "object") continue;
|
|
1109
|
-
for (const moduleName of Object.keys(group)) {
|
|
1110
|
-
out[moduleName] = Object.assign(out[moduleName] || {}, group[moduleName]);
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
return out;
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
async function loadRunnerHooks() {
|
|
1117
|
-
if (!fs.existsSync(HOOKS_PATH)) {
|
|
1118
|
-
return {
|
|
1119
|
-
createUserImports() {
|
|
1120
|
-
return {};
|
|
1121
|
-
},
|
|
1122
|
-
async runModule(_exports, _ctx) {},
|
|
1123
|
-
};
|
|
1124
|
-
}
|
|
1125
|
-
const mod = await import(pathToFileURL(HOOKS_PATH).href + "?t=" + Date.now());
|
|
1126
|
-
return {
|
|
1127
|
-
createUserImports:
|
|
1128
|
-
typeof mod.createUserImports == "function"
|
|
1129
|
-
? mod.createUserImports
|
|
1130
|
-
: () => ({}),
|
|
1131
|
-
runModule:
|
|
1132
|
-
typeof mod.runModule == "function" ? mod.runModule : async () => {},
|
|
1133
|
-
};
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
async function instantiateModule(ctx, hooks) {
|
|
1137
|
-
const helper = await import(pathToFileURL(ctx.helperPath).href);
|
|
1138
|
-
if (typeof helper.instantiate !== "function") {
|
|
1139
|
-
throw new Error("bindings helper missing instantiate export");
|
|
1140
|
-
}
|
|
1141
|
-
const imports = mergeImports(
|
|
1142
|
-
createAsTestImports(ctx),
|
|
1143
|
-
await hooks.createUserImports(ctx),
|
|
1144
|
-
);
|
|
1145
|
-
return helper.instantiate(ctx.module, imports);
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
const wasmPathArg = process.argv[2];
|
|
1149
|
-
if (!wasmPathArg) {
|
|
1150
|
-
process.stderr.write("usage: node ./.as-test/runners/default.bindings.js <file.wasm>\\n");
|
|
1151
|
-
process.exit(1);
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
const wasmPath = path.resolve(process.cwd(), wasmPathArg);
|
|
1155
|
-
const jsPath = wasmPath.replace(/\\.wasm$/, ".js");
|
|
1156
|
-
|
|
1157
|
-
try {
|
|
1158
|
-
const binary = fs.readFileSync(wasmPath);
|
|
1159
|
-
const module = new WebAssembly.Module(binary);
|
|
1160
|
-
const ctx = createRunnerContext({ wasmPath, module, helperPath: jsPath });
|
|
1161
|
-
const hooks = await loadRunnerHooks();
|
|
1162
|
-
const exports = await instantiateModule(ctx, hooks);
|
|
1163
|
-
await hooks.runModule(exports, ctx);
|
|
1164
|
-
} catch (error) {
|
|
1165
|
-
process.stderr.write("failed to run bindings module: " + String(error) + "\\n");
|
|
1166
|
-
process.exit(1);
|
|
1167
|
-
}
|
|
1168
|
-
`;
|
|
1169
|
-
}
|
|
1170
|
-
function buildBindingsRunnerHooks() {
|
|
1171
|
-
return `export function createUserImports(_ctx) {
|
|
1172
|
-
return {
|
|
1173
|
-
// env: {
|
|
1174
|
-
// now_ms: () => Date.now(),
|
|
1175
|
-
// },
|
|
1176
|
-
};
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
export async function runModule(_exports, _ctx) {
|
|
1180
|
-
// The generated bindings helper already calls exports._start().
|
|
1181
|
-
// Add extra startup calls here when your module exposes them.
|
|
1182
|
-
//
|
|
1183
|
-
// Example:
|
|
1184
|
-
// _exports.run?.();
|
|
1185
|
-
}
|
|
1186
|
-
`;
|
|
1187
|
-
}
|
|
1188
|
-
function buildWebRunnerHooks() {
|
|
1189
|
-
return `export function createUserImports(_ctx) {
|
|
1190
|
-
return {
|
|
1191
|
-
// env: {
|
|
1192
|
-
// now_ms: () => performance.now(),
|
|
1193
|
-
// },
|
|
1194
|
-
};
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
export async function runModule(_exports, _ctx) {
|
|
1198
|
-
// The generated bindings helper already calls exports._start().
|
|
1199
|
-
// Add extra startup calls here when your module exposes them.
|
|
1200
|
-
//
|
|
1201
|
-
// Example:
|
|
1202
|
-
// _exports.run?.();
|
|
1203
|
-
}
|
|
1004
|
+
instantiate(imports)
|
|
1005
|
+
.then((instance) => {
|
|
1006
|
+
instance.exports.start?.();
|
|
1007
|
+
// Add extra startup logic here when needed.
|
|
1008
|
+
})
|
|
1009
|
+
.catch((error) => {
|
|
1010
|
+
throw new Error("Failed to run bindings module: " + String(error));
|
|
1011
|
+
});
|
|
1204
1012
|
`;
|
|
1205
1013
|
}
|