Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ test-wasix-napi-cli:
printf '%s\n' "$$output" | grep -Fx "hello world!"

test-wasix-safe-mode:
python3 ./scripts/test-wasix-safe-mode.py --wasmer-bin "$(WASMER_BIN)" --package-dir "$(WASIX_PACKAGE_DIR)"
python3 ./scripts/test-wasix-safe-mode.py --wasmer-bin "$(WASMER_BIN)" --package-dir "$(WASIX_PACKAGE_DIR)" $(WASIX_SAFE_MODE_ARGS)

$(EDGE_BINARY):
$(MAKE) build
Expand Down
14 changes: 14 additions & 0 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,20 @@ What it does not measure:
- timer cancellation or re-scheduling
- `setImmediate` or `queueMicrotask` (covered by `promise-microtask-chain`)

### `dependency-heavy-package`

Loads and executes a small package-like local dependency graph, then prints a deterministic checksum.

What it isolates:
- module-loader plus execution cost on a multi-file dependency graph
- package-style code paths beyond startup-only micro workloads
- short-lived execution where import graph shape matters

What it does not measure:
- third-party package install or registry/network fetch cost
- very large real-world dependency trees
- framework-level startup and steady-state behavior

## Coverage notes

Each benchmark in this directory is intentionally narrow.
Expand Down
8 changes: 7 additions & 1 deletion benchmarks/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,15 @@ case "$BENCHMARK" in
BUN_CMD="$BUN_BIN benchmarks/workloads/timers-settimeout-chain.js"
DENO_CMD="$DENO_BIN run benchmarks/workloads/timers-settimeout-chain.js"
;;
dependency-heavy-package)
EDGE_CMD="$EDGE_BIN benchmarks/workloads/dependency-heavy-package.js"
NODE_CMD="$NODE_BIN benchmarks/workloads/dependency-heavy-package.js"
BUN_CMD="$BUN_BIN benchmarks/workloads/dependency-heavy-package.js"
DENO_CMD="$DENO_BIN run benchmarks/workloads/dependency-heavy-package.js"
;;
*)
echo "Unknown benchmark: $BENCHMARK"
echo "Available benchmarks: empty-startup, console-log, json-parse-stringify, promise-microtask-chain, zlib-deflate-sync, string-compare-split, cli-eval-empty, cli-print-literal, cli-print-process-version, http-loopback, timers-settimeout-chain"
echo "Available benchmarks: empty-startup, console-log, json-parse-stringify, promise-microtask-chain, zlib-deflate-sync, string-compare-split, cli-eval-empty, cli-print-literal, cli-print-process-version, http-loopback, timers-settimeout-chain, dependency-heavy-package"
exit 1
;;
esac
Expand Down
12 changes: 12 additions & 0 deletions benchmarks/workloads/dependency-heavy-package.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"use strict";

import { runDependencyWorkload } from "./fixtures/dependency-heavy-package/index.js";

const ITERATIONS = 2500;
let checksum = 0;

for (let i = 0; i < ITERATIONS; i += 1) {
checksum = (checksum + runDependencyWorkload(i)) % 1000000007;
}

console.log(String(checksum));
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { runDependencyWorkload } from "./src/entry.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "edge-bench-dependency-heavy-package",
"private": true,
"type": "module",
"exports": {
".": "./index.js"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { graphA } from "./graph-a.js";
import { graphB } from "./graph-b.js";
import { TOKENS } from "./tokens.js";

export function runDependencyWorkload(seed) {
const partA = graphA(seed);
const partB = graphB(seed + 7);
const tokenMix = TOKENS[(seed * 5) % TOKENS.length].length * 131;
return (partA + partB + tokenMix) >>> 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { rollingHash } from "./hash.js";
import { normalizeToken } from "./normalize.js";
import { TOKENS } from "./tokens.js";

export function graphA(seed) {
let acc = 0;

for (let i = 0; i < TOKENS.length; i += 1) {
const normalized = normalizeToken(`${TOKENS[i]}-${(seed + i) % 17}`);
acc = (acc + rollingHash(normalized, seed + i)) >>> 0;
}

return acc;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { rollingHash } from "./hash.js";
import { TOKENS } from "./tokens.js";

export function graphB(seed) {
let acc = 0;

for (let i = TOKENS.length - 1; i >= 0; i -= 1) {
const token = `${TOKENS[i]}:${(seed * (i + 3)) % 19}`;
acc = (acc ^ rollingHash(token, seed + i * 13)) >>> 0;
}

return acc;
}
10 changes: 10 additions & 0 deletions benchmarks/workloads/fixtures/dependency-heavy-package/src/hash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function rollingHash(text, seed) {
let hash = (2166136261 ^ seed) >>> 0;

for (let i = 0; i < text.length; i += 1) {
hash ^= text.charCodeAt(i);
hash = Math.imul(hash, 16777619) >>> 0;
}

return hash;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export function normalizeToken(input) {
return input
.trim()
.toLowerCase()
.replace(/[^a-z]/g, "");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export const TOKENS = [
"alpha",
"beta",
"gamma",
"delta",
"epsilon",
"zeta",
"eta",
"theta",
"iota",
"kappa",
"lambda",
"mu",
"nu",
"xi",
"omicron",
"pi",
"rho",
"sigma",
"tau",
"upsilon",
"phi",
"chi",
"psi",
"omega",
];
72 changes: 44 additions & 28 deletions scripts/test-wasix-safe-mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,42 @@ def run_case(wasmer_bin: str, package_dir: Path, timeout: int, name: str, script
print(f"[ok] {name}: {stdout.strip()}")


def build_cases(host: str, include_network: bool) -> list[tuple[str, str, str]]:
cases = [
(
"queueMicrotask",
"console.log('A'); queueMicrotask(() => console.log('B')); console.log('C');",
"A\nC\nB\n",
),
(
"blob.arrayBuffer",
"new Blob([new Uint8Array([65,66,67])]).arrayBuffer().then((ab) => console.log('BLOB', ab.byteLength));",
"BLOB 3\n",
),
]

if include_network:
cases.extend([
(
f"fetch http://{host}/",
f"const keepAlive = setTimeout(() => {{}}, 30000); fetch('http://{host}/').then((r) => console.log('FETCH', r.status)).catch((e) => {{ console.error('FETCHERR', e && (e.stack || e.message || e)); process.exitCode = 1; }}).finally(() => clearTimeout(keepAlive));",
"FETCH 200\n",
),
(
f"https.get https://{host}/",
f"require('node:https').get({{ hostname: '{host}', port: 443, path: '/', servername: '{host}' }}, (r) => {{ console.log('HTTPS', r.statusCode); r.resume(); }}).on('error', (e) => {{ console.error('HTTPSERR', e && (e.stack || e.message || e)); process.exit(1); }});",
"HTTPS 200\n",
),
(
f"tls.connect verified {host}",
f"const tls=require('node:tls'); const s=tls.connect(443,'{host}',{{servername:'{host}'}},()=>{{ console.log('TLS CONNECTED', s.authorized); s.destroy(); }}); s.on('close',()=>console.log('TLS CLOSE')); process.on('exit',(code)=>console.log('TLS EXIT', code)); s.on('error',(e)=>{{ console.error('TLSERR', e && (e.stack || e.message || e)); process.exitCode = 1; }});",
"TLS CONNECTED true\nTLS CLOSE\nTLS EXIT 0\n",
),
])

return cases


def main() -> int:
parser = argparse.ArgumentParser(description="Run WASIX safe-mode smoke tests through Wasmer.")
parser.add_argument(
Expand All @@ -102,44 +138,24 @@ def main() -> int:
default="example.com",
help="Host used for verified HTTPS/TLS smoke coverage.",
)
parser.add_argument(
"--include-network",
action="store_true",
help="Run outbound HTTP/TLS smoke checks in addition to deterministic local checks.",
)
args = parser.parse_args()

package_dir = Path(args.package_dir).resolve()
if not (package_dir / "wasmer.toml").is_file():
raise RuntimeError(f"missing wasmer.toml in {package_dir}")
host = args.https_host

cases = [
(
"queueMicrotask",
"console.log('A'); queueMicrotask(() => console.log('B')); console.log('C');",
"A\nC\nB\n",
),
(
"blob.arrayBuffer",
"new Blob([new Uint8Array([65,66,67])]).arrayBuffer().then((ab) => console.log('BLOB', ab.byteLength));",
"BLOB 3\n",
),
(
f"fetch http://{host}/",
f"const keepAlive = setTimeout(() => {{}}, 30000); fetch('http://{host}/').then((r) => console.log('FETCH', r.status)).catch((e) => {{ console.error('FETCHERR', e && (e.stack || e.message || e)); process.exitCode = 1; }}).finally(() => clearTimeout(keepAlive));",
"FETCH 200\n",
),
(
f"https.get https://{host}/",
f"require('node:https').get({{ hostname: '{host}', port: 443, path: '/', servername: '{host}' }}, (r) => {{ console.log('HTTPS', r.statusCode); r.resume(); }}).on('error', (e) => {{ console.error('HTTPSERR', e && (e.stack || e.message || e)); process.exit(1); }});",
"HTTPS 200\n",
),
(
f"tls.connect verified {host}",
f"const tls=require('node:tls'); const s=tls.connect(443,'{host}',{{servername:'{host}'}},()=>{{ console.log('TLS CONNECTED', s.authorized); s.destroy(); }}); s.on('close',()=>console.log('TLS CLOSE')); process.on('exit',(code)=>console.log('TLS EXIT', code)); s.on('error',(e)=>{{ console.error('TLSERR', e && (e.stack || e.message || e)); process.exitCode = 1; }});",
"TLS CONNECTED true\nTLS CLOSE\nTLS EXIT 0\n",
),
]
cases = build_cases(args.https_host, include_network=args.include_network)

for name, script, expected_stdout in cases:
run_case(args.wasmer_bin, package_dir, args.timeout, name, script, expected_stdout)

if not args.include_network:
print("Skipped outbound network smoke tests; pass --include-network to run them.")
print("All WASIX safe-mode smoke tests passed.")
return 0

Expand Down
39 changes: 39 additions & 0 deletions scripts/test_wasix_safe_mode_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env python3

import importlib.util
import unittest
from pathlib import Path


SCRIPT_PATH = Path(__file__).with_name("test-wasix-safe-mode.py")


def load_script():
spec = importlib.util.spec_from_file_location("test_wasix_safe_mode", SCRIPT_PATH)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module


class WasixSafeModeTests(unittest.TestCase):
def test_build_cases_can_skip_external_network_checks(self):
module = load_script()

cases = module.build_cases("example.com", include_network=False)

names = [case[0] for case in cases]
self.assertEqual(names, ["queueMicrotask", "blob.arrayBuffer"])

def test_build_cases_includes_external_network_checks_when_requested(self):
module = load_script()

cases = module.build_cases("example.com", include_network=True)

names = [case[0] for case in cases]
self.assertIn("fetch http://example.com/", names)
self.assertIn("https.get https://example.com/", names)
self.assertIn("tls.connect verified example.com", names)


if __name__ == "__main__":
unittest.main()
Loading