mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 18:38:55 +00:00
208 lines
5.1 KiB
TypeScript
208 lines
5.1 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { bunEnv, bunExe, tls as COMMON_CERT, gc, isCI } from "harness";
|
|
import { once } from "node:events";
|
|
import { createServer } from "node:http";
|
|
import { join } from "node:path";
|
|
|
|
describe("fetch doesn't leak", () => {
|
|
test("fixture #1", async () => {
|
|
const body = new Blob(["some body in here!".repeat(100)]);
|
|
var count = 0;
|
|
using server = Bun.serve({
|
|
port: 0,
|
|
idleTimeout: 0,
|
|
fetch(req) {
|
|
count++;
|
|
return new Response(body);
|
|
},
|
|
});
|
|
|
|
await using proc = Bun.spawn({
|
|
env: {
|
|
...bunEnv,
|
|
SERVER: server.url.href,
|
|
COUNT: "200",
|
|
},
|
|
stderr: "inherit",
|
|
stdout: "inherit",
|
|
cmd: [bunExe(), "--smol", join(import.meta.dir, "fetch-leak-test-fixture.js")],
|
|
});
|
|
|
|
const exitCode = await proc.exited;
|
|
expect(exitCode).toBe(0);
|
|
expect(count).toBe(200);
|
|
});
|
|
|
|
// This tests for body leakage and Response object leakage.
|
|
async function runTest(compressed, name) {
|
|
const body = !compressed
|
|
? new Blob(["some body in here!".repeat(2000000)])
|
|
: new Blob([Bun.deflateSync(crypto.getRandomValues(new Buffer(65123)))]);
|
|
|
|
const tls = name.includes("tls");
|
|
const headers = {
|
|
"Content-Type": "application/octet-stream",
|
|
};
|
|
if (compressed) {
|
|
headers["Content-Encoding"] = "deflate";
|
|
}
|
|
|
|
const serveOptions = {
|
|
port: 0,
|
|
idleTimeout: 0,
|
|
fetch(req) {
|
|
return new Response(body, { headers });
|
|
},
|
|
};
|
|
|
|
if (tls) {
|
|
serveOptions.tls = { ...COMMON_CERT };
|
|
}
|
|
|
|
using server = Bun.serve(serveOptions);
|
|
|
|
const env = {
|
|
...bunEnv,
|
|
SERVER: server.url.href,
|
|
BUN_JSC_forceRAMSize: (1024 * 1024 * 64).toString(10),
|
|
NAME: name,
|
|
};
|
|
|
|
if (tls) {
|
|
env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
}
|
|
|
|
if (compressed) {
|
|
env.COUNT = "1000";
|
|
}
|
|
|
|
await using proc = Bun.spawn({
|
|
env,
|
|
stderr: "inherit",
|
|
stdout: "inherit",
|
|
cmd: [bunExe(), "--smol", join(import.meta.dir, "fetch-leak-test-fixture-2.js")],
|
|
});
|
|
|
|
const exitCode = await proc.exited;
|
|
expect(exitCode).toBe(0);
|
|
}
|
|
|
|
for (let compressed of [true, false]) {
|
|
describe(compressed ? "compressed" : "uncompressed", () => {
|
|
for (let name of ["tcp", "tls", "tls-with-client"]) {
|
|
describe(name, () => {
|
|
test("fixture #2", async () => {
|
|
await runTest(compressed, name);
|
|
}, 100000);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
describe.each(["FormData", "Blob", "Buffer", "String", "URLSearchParams", "stream", "iterator"])("Sending %s", type => {
|
|
test(
|
|
"does not leak",
|
|
async () => {
|
|
using server = Bun.serve({
|
|
port: 0,
|
|
idleTimeout: 0,
|
|
fetch(req) {
|
|
return new Response();
|
|
},
|
|
});
|
|
|
|
const rss = [];
|
|
|
|
await using process = Bun.spawn({
|
|
cmd: [
|
|
bunExe(),
|
|
"--smol",
|
|
join(import.meta.dir, "fetch-leak-test-fixture-5.js"),
|
|
server.url.href,
|
|
1024 * 1024 * 2 + "",
|
|
type,
|
|
],
|
|
stdin: "ignore",
|
|
stdout: "inherit",
|
|
stderr: "inherit",
|
|
env: {
|
|
...bunEnv,
|
|
},
|
|
ipc(message) {
|
|
rss.push(message.rss);
|
|
},
|
|
});
|
|
|
|
await process.exited;
|
|
|
|
const first = rss[0];
|
|
const last = rss[rss.length - 1];
|
|
if (!isCI || !(last < first * 10)) {
|
|
console.log({ rss, delta: (((last - first) / 1024 / 1024) | 0) + " MB" });
|
|
}
|
|
expect(last).toBeLessThan(first * 10);
|
|
},
|
|
20 * 1000,
|
|
);
|
|
});
|
|
|
|
test("do not leak", async () => {
|
|
await using server = createServer((req, res) => {
|
|
res.end();
|
|
}).listen(0);
|
|
await once(server, "listening");
|
|
|
|
let url;
|
|
let isDone = false;
|
|
server.listen(0, "127.0.0.1", function attack() {
|
|
if (isDone) {
|
|
return;
|
|
}
|
|
url ??= new URL(`http://127.0.0.1:${server.address().port}`);
|
|
const controller = new AbortController();
|
|
fetch(url, { signal: controller.signal })
|
|
.then(res => res.arrayBuffer())
|
|
.catch(() => {})
|
|
.then(attack);
|
|
});
|
|
|
|
let prev = Infinity;
|
|
let count = 0;
|
|
var interval = setInterval(() => {
|
|
isDone = true;
|
|
gc();
|
|
const next = process.memoryUsage().heapUsed;
|
|
if (next <= prev) {
|
|
expect(true).toBe(true);
|
|
clearInterval(interval);
|
|
} else if (count++ > 20) {
|
|
clearInterval(interval);
|
|
expect.unreachable();
|
|
} else {
|
|
prev = next;
|
|
}
|
|
}, 1e3);
|
|
});
|
|
|
|
test("should not leak using readable stream", async () => {
|
|
const buffer = Buffer.alloc(1024 * 128, "b");
|
|
using server = Bun.serve({
|
|
port: 0,
|
|
routes: { "/*": new Response(buffer) },
|
|
});
|
|
|
|
await using proc = Bun.spawn([bunExe(), join(import.meta.dir, "fetch-leak-test-fixture-6.js")], {
|
|
env: {
|
|
...bunEnv,
|
|
SERVER_URL: server.url.href,
|
|
MAX_MEMORY_INCREASE: "5", // in MB
|
|
},
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
const [exited, stdout, stderr] = await Promise.all([proc.exited, proc.stdout.text(), proc.stderr.text()]);
|
|
expect(stdout + stderr).toContain("done");
|
|
expect(exited).toBe(0);
|
|
});
|