@sproutboat/runtime 0.42.2 → 0.44.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 +16 -0
- package/package.json +1 -1
- package/src/egress.test.ts +76 -0
- package/src/native-fetch-prelude.js +164 -14
- package/src/transport-embedded.js +78 -7
- package/test/native-base64.ts +12 -26
- package/test/native-bodies.ts +19 -26
- package/test/native-byte-text.ts +3 -19
- package/test/native-compile.ts +163 -0
- package/test/native-cpu-requests.ts +3 -20
- package/test/native-cpu-stamp.ts +3 -20
- package/test/native-crypto.ts +3 -22
- package/test/native-domexception.ts +3 -21
- package/test/native-errors.ts +12 -24
- package/test/native-events.ts +21 -25
- package/test/native-fetch-request.ts +11 -20
- package/test/native-getrandomvalues.ts +3 -21
- package/test/native-headers.ts +3 -21
- package/test/native-http.ts +10 -20
- package/test/native-iterators.ts +24 -27
- package/test/native-json-strings.ts +3 -21
- package/test/native-json.ts +15 -25
- package/test/native-jwt.ts +3 -22
- package/test/native-model-resolver.ts +5 -24
- package/test/native-r2-transfer.ts +101 -0
- package/test/native-raw-body.ts +7 -53
- package/test/native-request-log.ts +197 -0
- package/test/native-request-streaming.ts +3 -19
- package/test/native-request.ts +24 -26
- package/test/native-response-defaults.ts +4 -22
- package/test/native-service-bindings.ts +53 -27
- package/test/native-sockets-broker.ts +3 -20
- package/test/native-streaming.ts +4 -22
- package/test/native-streams.ts +22 -25
- package/test/native-subtle.ts +3 -21
- package/test/native-textdecoder-encodings.ts +3 -21
- package/test/native-timers.ts +4 -22
- package/test/native-unhandled-rejection.ts +3 -20
- package/test/native-url.ts +15 -25
- package/test/native-utf8.ts +15 -21
- package/test/native-waituntil.ts +7 -23
- package/test/native-web-apis.ts +3 -22
- package/test/native-websockets.ts +3 -19
package/test/native-bodies.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
|
-
import { join
|
|
5
|
-
import {
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { compileNative } from "./native-compile";
|
|
6
6
|
import { preludePath, TRANSPORT_MARKER, transportPath, wrapNativeFetchHandler } from "../src/wrap";
|
|
7
7
|
|
|
8
8
|
// Bodies on the wire and the Fetch classes, against Bun: byte bodies go out as their bytes
|
|
@@ -80,14 +80,7 @@ function freePort(): number {
|
|
|
80
80
|
const workdir = await mkdtemp(join(tmpdir(), "sb-native-bodies-"));
|
|
81
81
|
let server: ReturnType<typeof Bun.spawn> | undefined;
|
|
82
82
|
try {
|
|
83
|
-
const
|
|
84
|
-
const node = Bun.which("node");
|
|
85
|
-
if (!node) throw new Error("native streams test needs node on PATH");
|
|
86
|
-
|
|
87
|
-
const [core, broker] = await Promise.all([
|
|
88
|
-
readFile(preludePath, "utf8"),
|
|
89
|
-
readFile(transportPath("broker"), "utf8"),
|
|
90
|
-
]);
|
|
83
|
+
const [core, broker] = await Promise.all([readFile(preludePath, "utf8"), readFile(transportPath("broker"), "utf8")]);
|
|
91
84
|
assert(core.includes(TRANSPORT_MARKER), "runtime prelude is missing its transport marker");
|
|
92
85
|
const prelude = core.replace(TRANSPORT_MARKER, broker);
|
|
93
86
|
const generated = join(workdir, "bodies.generated.js");
|
|
@@ -95,18 +88,7 @@ try {
|
|
|
95
88
|
await writeFile(generated, wrapNativeFetchHandler(handler, prelude, undefined, undefined, undefined, "2026-10-21"));
|
|
96
89
|
await rm(binary, { force: true });
|
|
97
90
|
|
|
98
|
-
|
|
99
|
-
cwd: workdir,
|
|
100
|
-
env: process.env,
|
|
101
|
-
stdout: "pipe",
|
|
102
|
-
stderr: "pipe",
|
|
103
|
-
});
|
|
104
|
-
const [exitCode, stdout, stderr] = await Promise.all([
|
|
105
|
-
compile.exited,
|
|
106
|
-
new Response(compile.stdout).text(),
|
|
107
|
-
new Response(compile.stderr).text(),
|
|
108
|
-
]);
|
|
109
|
-
assert.equal(exitCode, 0, `Porffor native compile failed:\n${stderr}\n${stdout}`);
|
|
91
|
+
await compileNative({ generated, binary, dir: workdir });
|
|
110
92
|
|
|
111
93
|
const port = freePort();
|
|
112
94
|
server = Bun.spawn([binary], {
|
|
@@ -142,7 +124,9 @@ try {
|
|
|
142
124
|
const form = await fetch(`http://127.0.0.1:${port}/form`);
|
|
143
125
|
const boundary = (form.headers.get("content-type") || "").split("boundary=")[1];
|
|
144
126
|
assert(boundary, "multipart content type");
|
|
145
|
-
const parsed = await new Response(await form.arrayBuffer(), {
|
|
127
|
+
const parsed = await new Response(await form.arrayBuffer(), {
|
|
128
|
+
headers: { "content-type": `multipart/form-data; boundary=${boundary}` },
|
|
129
|
+
}).formData();
|
|
146
130
|
assert.equal(parsed.get("a"), "1");
|
|
147
131
|
const file = parsed.get("f");
|
|
148
132
|
assert(file instanceof File, "the file part parses back as a File");
|
|
@@ -153,11 +137,20 @@ try {
|
|
|
153
137
|
const got = JSON.parse(await (await fetch(`http://127.0.0.1:${port}/cases`)).text()) as string[];
|
|
154
138
|
const want: string[] = [];
|
|
155
139
|
// SAFETY: CASE_SOURCES is the handler's own literal list of case functions.
|
|
156
|
-
const reference = new Function(`return (() => { ${CASE_SOURCES} return cases; })()`)() as Array<
|
|
140
|
+
const reference = new Function(`return (() => { ${CASE_SOURCES} return cases; })()`)() as Array<
|
|
141
|
+
() => Promise<string> | string
|
|
142
|
+
>;
|
|
157
143
|
for (const run of reference) {
|
|
158
|
-
try {
|
|
144
|
+
try {
|
|
145
|
+
want.push(String(await run()));
|
|
146
|
+
} catch (e) {
|
|
147
|
+
want.push("THROWS " + (e instanceof Error ? e.name : String(e)));
|
|
148
|
+
}
|
|
159
149
|
}
|
|
160
|
-
assert.deepEqual(
|
|
150
|
+
assert.deepEqual(
|
|
151
|
+
got,
|
|
152
|
+
want.map((w) => (w.startsWith("workerd:") ? "workerd:TypeError" : w)),
|
|
153
|
+
);
|
|
161
154
|
console.log(`Native bodies are sent as bytes and ${want.length} Fetch class cases match Bun`);
|
|
162
155
|
} finally {
|
|
163
156
|
if (server) {
|
package/test/native-byte-text.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
|
-
import { join
|
|
5
|
-
import {
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { compileNative } from "./native-compile";
|
|
6
6
|
import { preludePath, TRANSPORT_MARKER, transportPath, wrapNativeFetchHandler } from "../src/wrap";
|
|
7
7
|
|
|
8
8
|
// sproutd #22: __sbFromUtf8 and __sbToBytes run in C (__sbFromUtf8Native, __sbToBytesNative).
|
|
@@ -92,28 +92,12 @@ function freePort(): number {
|
|
|
92
92
|
const workdir = await mkdtemp(join(tmpdir(), "sb-native-byte-text-"));
|
|
93
93
|
let server: ReturnType<typeof Bun.spawn> | undefined;
|
|
94
94
|
try {
|
|
95
|
-
const porffor = await ensurePorffor();
|
|
96
|
-
const node = Bun.which("node");
|
|
97
|
-
if (!node) throw new Error("native byte-text test needs node on PATH");
|
|
98
95
|
const broker = await readFile(transportPath("broker"), "utf8");
|
|
99
96
|
assert(core.includes(TRANSPORT_MARKER), "runtime prelude is missing its transport marker");
|
|
100
97
|
const generated = join(workdir, "bytes.generated.js");
|
|
101
98
|
const binary = join(workdir, "bytes-native");
|
|
102
99
|
await writeFile(generated, wrapNativeFetchHandler(handler, core.replace(TRANSPORT_MARKER, broker)));
|
|
103
|
-
|
|
104
|
-
[node, resolve(porffor, "runtime/index.js"), "native", generated, "-o", binary, "-s", "-O0"],
|
|
105
|
-
{
|
|
106
|
-
cwd: workdir,
|
|
107
|
-
stdout: "pipe",
|
|
108
|
-
stderr: "pipe",
|
|
109
|
-
},
|
|
110
|
-
);
|
|
111
|
-
const [exitCode, stdout, stderr] = await Promise.all([
|
|
112
|
-
compile.exited,
|
|
113
|
-
new Response(compile.stdout).text(),
|
|
114
|
-
new Response(compile.stderr).text(),
|
|
115
|
-
]);
|
|
116
|
-
assert.equal(exitCode, 0, `Porffor native compile failed:\n${stderr}\n${stdout}`);
|
|
100
|
+
await compileNative({ generated, binary, dir: workdir });
|
|
117
101
|
|
|
118
102
|
const port = freePort();
|
|
119
103
|
// Peak RSS: /proc's high-water mark on Linux, time(1) on macOS.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { ensurePorffor } from "../../toolchain/src/acquire";
|
|
6
|
+
import { ensurePorfforA21 } from "../../toolchain/src/a21/acquire";
|
|
7
|
+
import {
|
|
8
|
+
composeStandaloneA21,
|
|
9
|
+
SHARED_PRELUDE_FILE,
|
|
10
|
+
STANDALONE_A21_COMPILE_FLAGS,
|
|
11
|
+
} from "../../toolchain/src/a21/standalone";
|
|
12
|
+
import { buildStandaloneA21 } from "../../toolchain/src/a21/standalone-build";
|
|
13
|
+
import { standaloneChannel } from "../../toolchain/src/a21/standalone-channel";
|
|
14
|
+
import { bearsslEnv } from "./bearssl";
|
|
15
|
+
|
|
16
|
+
export type NativeCompileInput = {
|
|
17
|
+
/** The generated module (`wrapNativeFetchHandler` output), already written to disk. */
|
|
18
|
+
generated: string;
|
|
19
|
+
/** Where the compiled binary goes. */
|
|
20
|
+
binary: string;
|
|
21
|
+
/** Working directory of the compile, and where alpha 22 puts its intermediate files. Default: the binary's directory. */
|
|
22
|
+
dir?: string;
|
|
23
|
+
/** Toolchain cache root for the alpha 19 tree (`ensurePorffor({ cacheRoot })`). */
|
|
24
|
+
cacheRoot?: string;
|
|
25
|
+
/** Objects and link flags for the link line (alpha 19: `SB_EXTRA_LINK`). */
|
|
26
|
+
extraLink?: string[];
|
|
27
|
+
/** Flags for every C compile (alpha 19: `SB_EXTRA_CFLAGS`). */
|
|
28
|
+
extraCflags?: string[];
|
|
29
|
+
/** Link BearSSL, as a test that uses crypto.subtle needs. */
|
|
30
|
+
bearssl?: boolean;
|
|
31
|
+
/** `-O` level. Default 0. */
|
|
32
|
+
optimize?: number;
|
|
33
|
+
/** Extra environment for the alpha 19 compiler process (the default is `process.env`). */
|
|
34
|
+
env?: Record<string, string>;
|
|
35
|
+
/** Message prefix of the failure assertion. Default "Porffor native compile failed". */
|
|
36
|
+
failure?: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type UwsModule = {
|
|
40
|
+
UWS_COMMIT: string;
|
|
41
|
+
ensureUWebSockets: () => string;
|
|
42
|
+
ensureUSocketsBuilt: (dir: string) => void;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
let renderQueue: Promise<void> = Promise.resolve();
|
|
46
|
+
|
|
47
|
+
const channel = () => standaloneChannel();
|
|
48
|
+
|
|
49
|
+
const split = (flags: string | undefined): string[] => (flags ?? "").split(" ").filter(Boolean);
|
|
50
|
+
|
|
51
|
+
async function drain(child: Bun.Subprocess<"ignore", "pipe", "pipe">) {
|
|
52
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
53
|
+
child.exited,
|
|
54
|
+
new Response(child.stdout).text(),
|
|
55
|
+
new Response(child.stderr).text(),
|
|
56
|
+
]);
|
|
57
|
+
return { exitCode, stdout, stderr };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The host uWebSockets checkout with a built uSockets.a, built on first use. */
|
|
61
|
+
async function uwsDirectory(alpha19: string): Promise<string> {
|
|
62
|
+
// SAFETY: the patched alpha 19 tree's compiler/uwebsockets.js exports these (see the toolchain's standaloneShimSource).
|
|
63
|
+
const mod: UwsModule = await import(resolve(alpha19, "compiler/uwebsockets.js"));
|
|
64
|
+
const dir = resolve(homedir(), ".cache/porffor/deps", `uWebSockets-${mod.UWS_COMMIT}`);
|
|
65
|
+
if (!existsSync(join(dir, "uSockets/uSockets.a"))) {
|
|
66
|
+
// SAFETY: the compiler's prefs.js installs `Prefs` on globalThis as a plain flag object.
|
|
67
|
+
const g = globalThis as { Prefs?: { eventLoop?: boolean } };
|
|
68
|
+
const prev = g.Prefs;
|
|
69
|
+
g.Prefs = { ...prev, eventLoop: true };
|
|
70
|
+
try {
|
|
71
|
+
mod.ensureUSocketsBuilt(mod.ensureUWebSockets());
|
|
72
|
+
} finally {
|
|
73
|
+
if (prev === undefined) delete g.Prefs;
|
|
74
|
+
else g.Prefs = prev;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return dir;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Compile a generated module into a native binary on the standalone channel (alpha 22 by default, `SPROUTBOAT_PORFFOR_CHANNEL=alpha-19` for the fallback). */
|
|
81
|
+
export async function compileNative(input: NativeCompileInput): Promise<void> {
|
|
82
|
+
const which = channel();
|
|
83
|
+
const dir = input.dir ?? dirname(input.binary);
|
|
84
|
+
const optimize = input.optimize ?? 0;
|
|
85
|
+
const failure = input.failure ?? "Porffor native compile failed";
|
|
86
|
+
const bearssl = input.bearssl ? await bearsslEnv() : undefined;
|
|
87
|
+
const extraLink = [...(input.extraLink ?? []), ...split(bearssl?.SB_EXTRA_LINK)];
|
|
88
|
+
const extraCflags = [...(input.extraCflags ?? []), ...split(bearssl?.SB_EXTRA_CFLAGS)];
|
|
89
|
+
const alpha19 = await ensurePorffor(input.cacheRoot === undefined ? {} : { cacheRoot: input.cacheRoot });
|
|
90
|
+
|
|
91
|
+
if (which === "alpha-19") {
|
|
92
|
+
const node = Bun.which("node");
|
|
93
|
+
if (!node) throw new Error("native compile needs node on PATH");
|
|
94
|
+
const env = { ...process.env, ...input.env };
|
|
95
|
+
if (extraLink.length > 0) env.SB_EXTRA_LINK = extraLink.join(" ");
|
|
96
|
+
if (extraCflags.length > 0) env.SB_EXTRA_CFLAGS = extraCflags.join(" ");
|
|
97
|
+
const compile = Bun.spawn(
|
|
98
|
+
[
|
|
99
|
+
node,
|
|
100
|
+
resolve(alpha19, "runtime/index.js"),
|
|
101
|
+
"native",
|
|
102
|
+
input.generated,
|
|
103
|
+
"-o",
|
|
104
|
+
input.binary,
|
|
105
|
+
"-s",
|
|
106
|
+
`-O${optimize}`,
|
|
107
|
+
],
|
|
108
|
+
{ cwd: dir, env, stdout: "pipe", stderr: "pipe" },
|
|
109
|
+
);
|
|
110
|
+
const { exitCode, stdout, stderr } = await drain(compile);
|
|
111
|
+
if (exitCode !== 0) throw new Error(`${failure}:\n${stderr}\n${stdout}`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const bun = process.execPath;
|
|
116
|
+
const alpha22 = await ensurePorfforA21("alpha-22");
|
|
117
|
+
const work = join(dir, `a22-${(input.binary.split("/").pop() ?? "bin").replace(/[^\w.-]/g, "_")}`);
|
|
118
|
+
await rm(work, { recursive: true, force: true });
|
|
119
|
+
await mkdir(work, { recursive: true });
|
|
120
|
+
const composed = composeStandaloneA21(await readFile(input.generated, "utf8"), alpha19);
|
|
121
|
+
// Beside the generated module: its relative imports (a handler's sibling helper files) resolve from the
|
|
122
|
+
// directory of the file they were written in, and the shared prelude carries the module's body. The
|
|
123
|
+
// chain recognises the prelude by its file name, so a test that compiles several variants from one
|
|
124
|
+
// directory at once (native-websockets) takes turns: the render is the only step that reads it.
|
|
125
|
+
const source = dirname(resolve(input.generated));
|
|
126
|
+
const entry = join(source, "entry.a22.js");
|
|
127
|
+
const turn = renderQueue;
|
|
128
|
+
let release = () => {};
|
|
129
|
+
renderQueue = new Promise<void>((done) => (release = done));
|
|
130
|
+
await turn;
|
|
131
|
+
let rendered: Awaited<ReturnType<typeof drain>>;
|
|
132
|
+
try {
|
|
133
|
+
await writeFile(join(source, SHARED_PRELUDE_FILE), composed.sharedPrelude);
|
|
134
|
+
await writeFile(entry, composed.entry);
|
|
135
|
+
const unitsDir = join(work, "units");
|
|
136
|
+
const render = Bun.spawn(
|
|
137
|
+
[bun, resolve(alpha22, "runtime/index.js"), "c", entry, "-o", `${unitsDir}/`, ...STANDALONE_A21_COMPILE_FLAGS],
|
|
138
|
+
{ cwd: work, env: { ...process.env, ...input.env }, stdout: "pipe", stderr: "pipe" },
|
|
139
|
+
);
|
|
140
|
+
rendered = await drain(render);
|
|
141
|
+
} finally {
|
|
142
|
+
release();
|
|
143
|
+
}
|
|
144
|
+
if (rendered.exitCode !== 0) throw new Error(`${failure} (alpha 22 render):\n${rendered.stderr}\n${rendered.stdout}`);
|
|
145
|
+
const unitsDir = join(work, "units");
|
|
146
|
+
await rm(input.binary, { force: true });
|
|
147
|
+
await buildStandaloneA21({
|
|
148
|
+
unitsDir,
|
|
149
|
+
alpha19Root: alpha19,
|
|
150
|
+
outPath: input.binary,
|
|
151
|
+
cc: ["cc"],
|
|
152
|
+
cxx: ["c++"],
|
|
153
|
+
uwsDir: await uwsDirectory(alpha19),
|
|
154
|
+
extraLink,
|
|
155
|
+
extraCflags,
|
|
156
|
+
optimize,
|
|
157
|
+
flto: false,
|
|
158
|
+
musl: false,
|
|
159
|
+
strip: true,
|
|
160
|
+
}).catch((error: Error) => {
|
|
161
|
+
throw new Error(`${failure} (alpha 22 build):\n${error.message}`);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
|
-
import { join
|
|
5
|
-
import {
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { compileNative } from "./native-compile";
|
|
6
6
|
import { preludePath, TRANSPORT_MARKER, transportPath, wrapNativeFetchHandler } from "../src/wrap";
|
|
7
7
|
|
|
8
8
|
// Per-request CPU (design 02): x-sb-cpu-ms counts the thread CPU time of the slices in which one
|
|
@@ -43,9 +43,6 @@ function freePort(): number {
|
|
|
43
43
|
const workdir = await mkdtemp(join(tmpdir(), "sb-native-cpu-requests-"));
|
|
44
44
|
const servers: ReturnType<typeof Bun.spawn>[] = [];
|
|
45
45
|
async function build(date: string): Promise<number> {
|
|
46
|
-
const porffor = await ensurePorffor();
|
|
47
|
-
const node = Bun.which("node");
|
|
48
|
-
if (!node) throw new Error("native cpu test needs node on PATH");
|
|
49
46
|
const [core, broker] = await Promise.all([readFile(preludePath, "utf8"), readFile(transportPath("broker"), "utf8")]);
|
|
50
47
|
assert(core.includes(TRANSPORT_MARKER), "runtime prelude is missing its transport marker");
|
|
51
48
|
const generated = join(workdir, `cpu-${date}.generated.js`);
|
|
@@ -54,21 +51,7 @@ async function build(date: string): Promise<number> {
|
|
|
54
51
|
generated,
|
|
55
52
|
wrapNativeFetchHandler(handler, core.replace(TRANSPORT_MARKER, broker), undefined, undefined, undefined, date),
|
|
56
53
|
);
|
|
57
|
-
|
|
58
|
-
[node, resolve(porffor, "runtime/index.js"), "native", generated, "-o", binary, "-s", "-O0"],
|
|
59
|
-
{
|
|
60
|
-
cwd: workdir,
|
|
61
|
-
env: process.env,
|
|
62
|
-
stdout: "pipe",
|
|
63
|
-
stderr: "pipe",
|
|
64
|
-
},
|
|
65
|
-
);
|
|
66
|
-
const [exitCode, stdout, stderr] = await Promise.all([
|
|
67
|
-
compile.exited,
|
|
68
|
-
new Response(compile.stdout).text(),
|
|
69
|
-
new Response(compile.stderr).text(),
|
|
70
|
-
]);
|
|
71
|
-
assert.equal(exitCode, 0, `Porffor native compile failed:\n${stderr}\n${stdout}`);
|
|
54
|
+
await compileNative({ generated, binary, dir: workdir });
|
|
72
55
|
const port = freePort();
|
|
73
56
|
const server = Bun.spawn([binary], {
|
|
74
57
|
cwd: workdir,
|
package/test/native-cpu-stamp.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
|
-
import { join
|
|
5
|
-
import {
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { compileNative } from "./native-compile";
|
|
6
6
|
import { preludePath, TRANSPORT_MARKER, transportPath, wrapNativeFetchHandler } from "../src/wrap";
|
|
7
7
|
|
|
8
8
|
// The x-sb-cpu-ms stamp is added to the handler's own Response (it used to build a second one),
|
|
@@ -52,29 +52,12 @@ async function start(binary: string, env: Record<string, string>): Promise<numbe
|
|
|
52
52
|
throw new Error("compiled handler did not start");
|
|
53
53
|
}
|
|
54
54
|
try {
|
|
55
|
-
const porffor = await ensurePorffor();
|
|
56
|
-
const node = Bun.which("node");
|
|
57
|
-
if (!node) throw new Error("native cpu stamp test needs node on PATH");
|
|
58
55
|
const [core, broker] = await Promise.all([readFile(preludePath, "utf8"), readFile(transportPath("broker"), "utf8")]);
|
|
59
56
|
assert(core.includes(TRANSPORT_MARKER), "runtime prelude is missing its transport marker");
|
|
60
57
|
const generated = join(workdir, "stamp.generated.js");
|
|
61
58
|
const binary = join(workdir, "stamp-native");
|
|
62
59
|
await writeFile(generated, wrapNativeFetchHandler(handler, core.replace(TRANSPORT_MARKER, broker)));
|
|
63
|
-
|
|
64
|
-
[node, resolve(porffor, "runtime/index.js"), "native", generated, "-o", binary, "-s", "-O0"],
|
|
65
|
-
{
|
|
66
|
-
cwd: workdir,
|
|
67
|
-
env: process.env,
|
|
68
|
-
stdout: "pipe",
|
|
69
|
-
stderr: "pipe",
|
|
70
|
-
},
|
|
71
|
-
);
|
|
72
|
-
const [exitCode, stdout, stderr] = await Promise.all([
|
|
73
|
-
compile.exited,
|
|
74
|
-
new Response(compile.stdout).text(),
|
|
75
|
-
new Response(compile.stderr).text(),
|
|
76
|
-
]);
|
|
77
|
-
assert.equal(exitCode, 0, `Porffor native compile failed:\n${stderr}\n${stdout}`);
|
|
60
|
+
await compileNative({ generated, binary, dir: workdir });
|
|
78
61
|
|
|
79
62
|
const port = await start(binary, {});
|
|
80
63
|
const get = (path: string, headers: Record<string, string> = {}, p = port) =>
|
package/test/native-crypto.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
|
-
import { join
|
|
5
|
-
import {
|
|
6
|
-
import { bearsslEnv } from "./bearssl";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { compileNative } from "./native-compile";
|
|
7
6
|
import { preludePath, TRANSPORT_MARKER, transportPath, wrapNativeFetchHandler } from "../src/wrap";
|
|
8
7
|
|
|
9
8
|
// These values come from Bun WebCrypto and node:crypto, not from the runtime
|
|
@@ -68,10 +67,6 @@ function freePort(): number {
|
|
|
68
67
|
const workdir = await mkdtemp(join(tmpdir(), "sb-native-crypto-"));
|
|
69
68
|
let server: ReturnType<typeof Bun.spawn> | undefined;
|
|
70
69
|
try {
|
|
71
|
-
const porffor = await ensurePorffor({ cacheRoot: workdir });
|
|
72
|
-
const node = Bun.which("node");
|
|
73
|
-
if (!node) throw new Error("native crypto test needs node on PATH");
|
|
74
|
-
|
|
75
70
|
const [core, broker] = await Promise.all([readFile(preludePath, "utf8"), readFile(transportPath("broker"), "utf8")]);
|
|
76
71
|
assert(core.includes(TRANSPORT_MARKER), "runtime prelude is missing its transport marker");
|
|
77
72
|
const prelude = core.replace(TRANSPORT_MARKER, broker);
|
|
@@ -85,21 +80,7 @@ try {
|
|
|
85
80
|
await writeFile(generated, wrapNativeFetchHandler(handler, prelude));
|
|
86
81
|
await rm(binary, { force: true });
|
|
87
82
|
|
|
88
|
-
|
|
89
|
-
[node, resolve(porffor, "runtime/index.js"), "native", generated, "-o", binary, "-s", "-O0"],
|
|
90
|
-
{
|
|
91
|
-
cwd: workdir,
|
|
92
|
-
env: { ...process.env, ...(await bearsslEnv()) },
|
|
93
|
-
stdout: "pipe",
|
|
94
|
-
stderr: "pipe",
|
|
95
|
-
},
|
|
96
|
-
);
|
|
97
|
-
const [exitCode, stdout, stderr] = await Promise.all([
|
|
98
|
-
compile.exited,
|
|
99
|
-
new Response(compile.stdout).text(),
|
|
100
|
-
new Response(compile.stderr).text(),
|
|
101
|
-
]);
|
|
102
|
-
assert.equal(exitCode, 0, `Porffor native compile failed:\n${stderr}\n${stdout}`);
|
|
83
|
+
await compileNative({ generated, binary, dir: workdir, cacheRoot: workdir, bearssl: true });
|
|
103
84
|
|
|
104
85
|
const port = freePort();
|
|
105
86
|
server = Bun.spawn([binary], {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
|
-
import { join
|
|
5
|
-
import {
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { compileNative } from "./native-compile";
|
|
6
6
|
import { preludePath, TRANSPORT_MARKER, transportPath, wrapNativeFetchHandler } from "../src/wrap";
|
|
7
7
|
|
|
8
8
|
// DOMException in a compiled handler, compared with Bun. Porffor ships a stub global that
|
|
@@ -47,10 +47,6 @@ function freePort(): number {
|
|
|
47
47
|
const workdir = await mkdtemp(join(tmpdir(), "sb-native-domexception-"));
|
|
48
48
|
let server: ReturnType<typeof Bun.spawn> | undefined;
|
|
49
49
|
try {
|
|
50
|
-
const porffor = await ensurePorffor();
|
|
51
|
-
const node = Bun.which("node");
|
|
52
|
-
if (!node) throw new Error("native streams test needs node on PATH");
|
|
53
|
-
|
|
54
50
|
const [core, broker] = await Promise.all([readFile(preludePath, "utf8"), readFile(transportPath("broker"), "utf8")]);
|
|
55
51
|
assert(core.includes(TRANSPORT_MARKER), "runtime prelude is missing its transport marker");
|
|
56
52
|
const prelude = core.replace(TRANSPORT_MARKER, broker);
|
|
@@ -59,21 +55,7 @@ try {
|
|
|
59
55
|
await writeFile(generated, wrapNativeFetchHandler(handler, prelude));
|
|
60
56
|
await rm(binary, { force: true });
|
|
61
57
|
|
|
62
|
-
|
|
63
|
-
[node, resolve(porffor, "runtime/index.js"), "native", generated, "-o", binary, "-s", "-O0"],
|
|
64
|
-
{
|
|
65
|
-
cwd: workdir,
|
|
66
|
-
env: process.env,
|
|
67
|
-
stdout: "pipe",
|
|
68
|
-
stderr: "pipe",
|
|
69
|
-
},
|
|
70
|
-
);
|
|
71
|
-
const [exitCode, stdout, stderr] = await Promise.all([
|
|
72
|
-
compile.exited,
|
|
73
|
-
new Response(compile.stdout).text(),
|
|
74
|
-
new Response(compile.stderr).text(),
|
|
75
|
-
]);
|
|
76
|
-
assert.equal(exitCode, 0, `Porffor native compile failed:\n${stderr}\n${stdout}`);
|
|
58
|
+
await compileNative({ generated, binary, dir: workdir });
|
|
77
59
|
|
|
78
60
|
const port = freePort();
|
|
79
61
|
server = Bun.spawn([binary], {
|
package/test/native-errors.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
|
-
import { join
|
|
5
|
-
import {
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { compileNative } from "./native-compile";
|
|
6
6
|
import { preludePath, TRANSPORT_MARKER, transportPath, wrapNativeFetchHandler } from "../src/wrap";
|
|
7
7
|
|
|
8
8
|
// Error.stack as V8 starts it (Bun is the reference for the first line): Porffor keeps no
|
|
@@ -40,14 +40,7 @@ function freePort(): number {
|
|
|
40
40
|
const workdir = await mkdtemp(join(tmpdir(), "sb-native-errors-"));
|
|
41
41
|
let server: ReturnType<typeof Bun.spawn> | undefined;
|
|
42
42
|
try {
|
|
43
|
-
const
|
|
44
|
-
const node = Bun.which("node");
|
|
45
|
-
if (!node) throw new Error("native streams test needs node on PATH");
|
|
46
|
-
|
|
47
|
-
const [core, broker] = await Promise.all([
|
|
48
|
-
readFile(preludePath, "utf8"),
|
|
49
|
-
readFile(transportPath("broker"), "utf8"),
|
|
50
|
-
]);
|
|
43
|
+
const [core, broker] = await Promise.all([readFile(preludePath, "utf8"), readFile(transportPath("broker"), "utf8")]);
|
|
51
44
|
assert(core.includes(TRANSPORT_MARKER), "runtime prelude is missing its transport marker");
|
|
52
45
|
const prelude = core.replace(TRANSPORT_MARKER, broker);
|
|
53
46
|
const generated = join(workdir, "errors.generated.js");
|
|
@@ -55,18 +48,7 @@ try {
|
|
|
55
48
|
await writeFile(generated, wrapNativeFetchHandler(handler, prelude));
|
|
56
49
|
await rm(binary, { force: true });
|
|
57
50
|
|
|
58
|
-
|
|
59
|
-
cwd: workdir,
|
|
60
|
-
env: process.env,
|
|
61
|
-
stdout: "pipe",
|
|
62
|
-
stderr: "pipe",
|
|
63
|
-
});
|
|
64
|
-
const [exitCode, stdout, stderr] = await Promise.all([
|
|
65
|
-
compile.exited,
|
|
66
|
-
new Response(compile.stdout).text(),
|
|
67
|
-
new Response(compile.stderr).text(),
|
|
68
|
-
]);
|
|
69
|
-
assert.equal(exitCode, 0, `Porffor native compile failed:\n${stderr}\n${stdout}`);
|
|
51
|
+
await compileNative({ generated, binary, dir: workdir });
|
|
70
52
|
|
|
71
53
|
const port = freePort();
|
|
72
54
|
server = Bun.spawn([binary], {
|
|
@@ -89,8 +71,14 @@ try {
|
|
|
89
71
|
// SAFETY: the handler above is the only producer of this body: a JSON array with one
|
|
90
72
|
// string per case; the comparison below reports any entry that differs.
|
|
91
73
|
const got = JSON.parse(await (await fetch(`http://127.0.0.1:${port}/`)).text()) as string[];
|
|
92
|
-
const diffs = CASES.flatMap((c, i) =>
|
|
93
|
-
|
|
74
|
+
const diffs = CASES.flatMap((c, i) =>
|
|
75
|
+
got[i] === want[i] ? [] : [`${c}\n want ${JSON.stringify(want[i])}\n got ${JSON.stringify(got[i])}`],
|
|
76
|
+
);
|
|
77
|
+
assert.deepEqual(
|
|
78
|
+
diffs,
|
|
79
|
+
[],
|
|
80
|
+
`Error.stack differs from V8 in ${diffs.length} of ${CASES.length} cases:\n${diffs.join("\n")}`,
|
|
81
|
+
);
|
|
94
82
|
console.log(`Native Error.stack starts like V8 in all ${CASES.length} cases`);
|
|
95
83
|
} finally {
|
|
96
84
|
if (server) {
|
package/test/native-events.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
|
-
import { join
|
|
5
|
-
import {
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { compileNative } from "./native-compile";
|
|
6
6
|
import { preludePath, TRANSPORT_MARKER, transportPath, wrapNativeFetchHandler } from "../src/wrap";
|
|
7
7
|
|
|
8
8
|
// structuredClone, Event/EventTarget and AbortController in a compiled handler,
|
|
@@ -29,7 +29,15 @@ ${CASES.map((c) => ` () => (${c}),`).join("\n")}
|
|
|
29
29
|
}
|
|
30
30
|
return new Response(JSON.stringify(out));
|
|
31
31
|
}};`;
|
|
32
|
-
const want = await Promise.all(
|
|
32
|
+
const want = await Promise.all(
|
|
33
|
+
CASES.map(async (c) => {
|
|
34
|
+
try {
|
|
35
|
+
return String(await new Function("return " + c)());
|
|
36
|
+
} catch (e) {
|
|
37
|
+
return "THROWS " + (e instanceof Error ? e.message : String(e));
|
|
38
|
+
}
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
33
41
|
|
|
34
42
|
function freePort(): number {
|
|
35
43
|
const listener = Bun.listen({ hostname: "127.0.0.1", port: 0, socket: { data() {} } });
|
|
@@ -41,14 +49,7 @@ function freePort(): number {
|
|
|
41
49
|
const workdir = await mkdtemp(join(tmpdir(), "sb-native-events-"));
|
|
42
50
|
let server: ReturnType<typeof Bun.spawn> | undefined;
|
|
43
51
|
try {
|
|
44
|
-
const
|
|
45
|
-
const node = Bun.which("node");
|
|
46
|
-
if (!node) throw new Error("native streams test needs node on PATH");
|
|
47
|
-
|
|
48
|
-
const [core, broker] = await Promise.all([
|
|
49
|
-
readFile(preludePath, "utf8"),
|
|
50
|
-
readFile(transportPath("broker"), "utf8"),
|
|
51
|
-
]);
|
|
52
|
+
const [core, broker] = await Promise.all([readFile(preludePath, "utf8"), readFile(transportPath("broker"), "utf8")]);
|
|
52
53
|
assert(core.includes(TRANSPORT_MARKER), "runtime prelude is missing its transport marker");
|
|
53
54
|
const prelude = core.replace(TRANSPORT_MARKER, broker);
|
|
54
55
|
const generated = join(workdir, "events.generated.js");
|
|
@@ -56,18 +57,7 @@ try {
|
|
|
56
57
|
await writeFile(generated, wrapNativeFetchHandler(handler, prelude));
|
|
57
58
|
await rm(binary, { force: true });
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
cwd: workdir,
|
|
61
|
-
env: process.env,
|
|
62
|
-
stdout: "pipe",
|
|
63
|
-
stderr: "pipe",
|
|
64
|
-
});
|
|
65
|
-
const [exitCode, stdout, stderr] = await Promise.all([
|
|
66
|
-
compile.exited,
|
|
67
|
-
new Response(compile.stdout).text(),
|
|
68
|
-
new Response(compile.stderr).text(),
|
|
69
|
-
]);
|
|
70
|
-
assert.equal(exitCode, 0, `Porffor native compile failed:\n${stderr}\n${stdout}`);
|
|
60
|
+
await compileNative({ generated, binary, dir: workdir });
|
|
71
61
|
|
|
72
62
|
const port = freePort();
|
|
73
63
|
server = Bun.spawn([binary], {
|
|
@@ -90,8 +80,14 @@ try {
|
|
|
90
80
|
// SAFETY: the handler above is the only producer of this body: a JSON array with one
|
|
91
81
|
// string per case; the comparison below reports any entry that differs.
|
|
92
82
|
const got = JSON.parse(await (await fetch(`http://127.0.0.1:${port}/`)).text()) as string[];
|
|
93
|
-
const diffs = CASES.flatMap((c, i) =>
|
|
94
|
-
|
|
83
|
+
const diffs = CASES.flatMap((c, i) =>
|
|
84
|
+
got[i] === want[i] ? [] : [`${c}\n want ${JSON.stringify(want[i])}\n got ${JSON.stringify(got[i])}`],
|
|
85
|
+
);
|
|
86
|
+
assert.deepEqual(
|
|
87
|
+
diffs,
|
|
88
|
+
[],
|
|
89
|
+
`Event, abort and clone results differ from Bun in ${diffs.length} of ${CASES.length} cases:\n${diffs.join("\n")}`,
|
|
90
|
+
);
|
|
95
91
|
console.log(`Native events, abort and structuredClone match Bun in all ${CASES.length} cases`);
|
|
96
92
|
} finally {
|
|
97
93
|
if (server) {
|