Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
850db19
test: drop the other thread's verbose lines before reading the error …
dylan-conway Oct 18, 2026
fd67e3a
test: serve a tarball larger than one read in the github tarball test…
dylan-conway Oct 18, 2026
a67a122
test: name the pid file by its full path in the killed member test of…
dylan-conway Oct 18, 2026
0bc3566
test: wait for the content of the pid file in the killed member test …
dylan-conway Oct 18, 2026
2ed4e7b
test: check the size of the test runner only on Linux in harness-max-…
dylan-conway Oct 18, 2026
8acd09e
test: do not write to the stdin of a child that reads a FIFO in body-…
dylan-conway Oct 18, 2026
7a90a1e
test: count what is written after every send in the backpressure test…
dylan-conway Oct 18, 2026
3df7751
test: wait for the exit instead of 80 ms in spawn-stdin-destroy.test.ts
dylan-conway Oct 18, 2026
db3641e
test: wait for the closes that are still queued before counting fds i…
dylan-conway Oct 18, 2026
169da5e
test: do not check the TCP pool on Windows Server 2019 in the pooled …
dylan-conway Oct 18, 2026
317e42c
test: leave the TCP pool out only on Windows Server 2019 in fetch-kee…
dylan-conway Oct 18, 2026
281dddd
test: profile until the sampler has run in the profile test of bun-js…
dylan-conway Oct 18, 2026
96d1928
test: sign a payload with a fixed iat in jsonwebtoken/async_sign.test.js
dylan-conway Oct 18, 2026
cf8c3e4
test: request a local server instead of www.example.com in test-http-…
dylan-conway Oct 18, 2026
423a01b
test: wait for the killed remover to exit in transpiler-cache.test.ts
dylan-conway Oct 18, 2026
f3a84a8
test: dispose of the remover on every path in transpiler-cache.test.ts
dylan-conway Oct 18, 2026
74b0eb9
test: await the upload instead of expect().rejects in fetch-abort-str…
dylan-conway Oct 18, 2026
3a3c66d
test: wait for what is asserted in the two memory tests of gc-control…
dylan-conway Oct 18, 2026
f0a6377
test: drop the limit of one second from the tests of websocket-client…
dylan-conway Oct 18, 2026
e15c57f
test: wait for the server's handshake in the EISCONN tests of node-tl…
dylan-conway Oct 18, 2026
ab57995
test: take the refused port of tls-shutdown-before-handshake-fixture.…
dylan-conway Oct 18, 2026
a824414
test: write until the watcher hears it in the fs.watch case of module…
dylan-conway Oct 18, 2026
465eea4
test: hold the UDP port until the graph's socket is bound in module-g…
dylan-conway Oct 18, 2026
b903894
test: assert the reading that ended the wait in gc-controller-cadence…
dylan-conway Oct 18, 2026
b865fdf
test: leave killing the remover to await using in transpiler-cache.te…
dylan-conway Oct 18, 2026
10a3b31
test: share one deadline between the three profile() calls in bun-jsc…
dylan-conway Oct 18, 2026
d37793e
test: close the held UDP port when the import rejects too in module-g…
dylan-conway Oct 18, 2026
c74a35e
Merge branch 'main' into claude/flaky-test-fixes
dylan-conway Oct 18, 2026
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
9 changes: 5 additions & 4 deletions test/cli/install/bun-install-streaming-extract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1049,8 +1049,9 @@ test.concurrent.each([
const wideMode = Buffer.from([0x80, 0, 0, 0, 0x80, 0, 0x01, 0xed]);
const root = "owner-repo-abc1234";
const index = Buffer.from("module.exports = 'ok';\n");
// So the body spans many reads and streaming commits.
const bulk = incompressible(256 * 1024, 1);
// More than one read of the client holds (LIBUS_RECV_BUFFER_LENGTH): a body that is all there at the first read is
// not streamed.
const bulk = incompressible(1024 * 1024, 1);
const tgz = gzipSync(
Buffer.concat([
tarHeader(`${root}/`, 0, "5"),
Expand Down Expand Up @@ -1608,8 +1609,8 @@ async function installStreamPkg(
...(path === "buffered" ? { BUN_FEATURE_FLAG_DISABLE_STREAMING_INSTALL: "1" } : {}),
});
results.push({
// A line of the verbose log, from another thread, can come before the end of this one.
error: stderr.match(/^error: .*$/m)?.[0].replace(/\[stream-pkg\] Extract .*$/, ""),
// What another thread writes to the verbose log can come anywhere in this line.
error: stderr.replace(/\[stream-pkg\] Extract .*\n(libarchive error: .*\n)?/g, "").match(/^error: .*$/m)?.[0],
streamed: stderr.includes("Streamed "),
installed: existsSync(join(String(dir), "node_modules", "stream-pkg")),
cached: readdirSync(join(String(dir), ".cache")).filter(name => /^(stream-pkg@|@T@)/.test(name)).length,
Expand Down
6 changes: 2 additions & 4 deletions test/cli/run/transpiler-cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ describe("transpiler cache", () => {
writeFileSync(join(temp_dir, "a.js"), dummyFile(50 * 1024, "1", "b"));
writeFileSync(join(temp_dir, "b.js"), dummyFile(50 * 1024, "2", "b"));

const remover = Bun.spawn({
// Its working directory is temp_dir, which Windows does not let the next test remove while it is alive.
await using remover = Bun.spawn({
cmd: [bunExe(), join(import.meta.dir, "transpiler-cache-aggressive-remover.js"), cache_dir],
env,
cwd: temp_dir,
Comment thread
dylan-conway marked this conversation as resolved.
Resolved
Expand All @@ -158,7 +159,6 @@ describe("transpiler cache", () => {
if (exitCode != 0 && !killing) {
killing = true;
processes.forEach(x => x.kill(9));
remover.kill(9);
}
},
}),
Expand All @@ -169,8 +169,6 @@ describe("transpiler cache", () => {

expect(!killing).toBeTrue();

remover.kill(9);

for (const proc of processes) {
expect(proc.exitCode).toBe(0);
expect(await proc.stdout.text()).toBe("b\n");
Expand Down
8 changes: 5 additions & 3 deletions test/internal/harness-max-rss.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect, test } from "bun:test";
import { bunExe, runCommandMaxRSS } from "harness";
import { bunExe, isLinux, runCommandMaxRSS } from "harness";

const MiB = 1024 * 1024;

Expand All @@ -9,11 +9,13 @@ test("runCommandMaxRSS measures the command, not the test runner", async () => {
// stays that high even if the buffer is collected.
const ballast = Buffer.alloc(256 * MiB, 1);
const runnerRSS = process.memoryUsage.rss();
expect(runnerRSS).toBeGreaterThan(ballast.length);
// Elsewhere a child inherits nothing, and a busy macOS machine no longer
// keeps all of the buffer resident by now.
if (isLinux) expect(runnerRSS).toBeGreaterThan(ballast.length);

const idle = await runCommandMaxRSS({ cmd: [bunExe(), "-e", "0"] });
expect(idle).toMatchObject({ stdout: "", stderr: "", exitCode: 0, signalCode: null });
expect(idle.maxRSS).toBeLessThan(runnerRSS);
if (isLinux) expect(idle.maxRSS).toBeLessThan(runnerRSS);

const busy = await runCommandMaxRSS({
cmd: [bunExe(), "-e", `console.log(Buffer.alloc(${256 * MiB}, 1).length)`],
Expand Down
13 changes: 9 additions & 4 deletions test/js/bun/gc/gc-controller-cadence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,9 @@ describe("idle release", () => {
const full = resident();
const deadline = performance.now() + 8000;
const timer = setInterval(() => {
if (resident() > full - 80 && performance.now() < deadline) return;
console.log(JSON.stringify({ full, now: resident() }));
const now = resident();
if (now >= full - 80 && performance.now() < deadline) return;
console.log(JSON.stringify({ full, now }));
process.exit(0);
}, 100);
};
Expand Down Expand Up @@ -328,8 +329,12 @@ describe("idle release", () => {
const full = resident();
expect(full).toBeGreaterThan(330);
const deadline = performance.now() + 6000;
while (resident() > full - 250 && performance.now() < deadline) await Bun.sleep(100);
expect(resident()).toBeLessThan(full - 250);
let now = resident();
while (now >= full - 250 && performance.now() < deadline) {
await Bun.sleep(100);
now = resident();
}
expect(now).toBeLessThan(full - 250);
proc.stdin.write("exit\n");
await proc.stdin.flush();
expect(await proc.exited).toBe(0);
Expand Down
18 changes: 15 additions & 3 deletions test/js/bun/jsc/bun-jsc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,23 +282,35 @@ describe("bun:jsc", () => {
return acc;
};

// On a busy machine the sampler thread may still not get to run in that
// window, so profile again until it has. A profiler that no longer samples
// uses up the time limit, which has to be here: this never yields.
const deadline = performance.now() + 5000;
const profileUntilSampled = () => {
let result;
do {
result = profile(work, sampleInterval);
} while (result.stackTraces.traces.length === 0 && performance.now() < deadline);
return result;
};

// First profile call
const result1 = profile(work, sampleInterval);
const result1 = profileUntilSampled();
expect(result1).toBeDefined();
expect(result1.functions).toBeDefined();
expect(result1.stackTraces).toBeDefined();
expect(result1.stackTraces.traces.length).toBeGreaterThan(0);

// Second profile call - should work after first one completed
// This verifies that shutdown() -> pause() fix works
const result2 = profile(work, sampleInterval);
const result2 = profileUntilSampled();
expect(result2).toBeDefined();
expect(result2.functions).toBeDefined();
expect(result2.stackTraces).toBeDefined();
expect(result2.stackTraces.traces.length).toBeGreaterThan(0);

// Third profile call - verify profiler can be reused multiple times
const result3 = profile(work, sampleInterval);
const result3 = profileUntilSampled();
expect(result3).toBeDefined();
expect(result3.functions).toBeDefined();
expect(result3.stackTraces).toBeDefined();
Expand Down
20 changes: 12 additions & 8 deletions test/js/bun/module-graph/module-graph-io.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -885,12 +885,12 @@ describe.concurrent("ModuleGraph: an error in what a graph opened is the graph's
dropRedisConnections() {
for (const socket of host.redisConnections.splice(0)) socket.end();
},
async closedUdpPort() {
// Taken until close(): a socket made for sending to the port cannot be given that very port
// as its own, which would leave it hearing itself and never refused.
async udpPortToClose() {
const s = dgram.createSocket("udp4");
await new Promise(r => s.bind(0, "127.0.0.1", r));
const port = s.address().port;
await new Promise(r => s.close(r));
return port;
return { port: s.address().port, close: () => new Promise(r => s.close(r)) };
},
};
`;
Expand Down Expand Up @@ -968,8 +968,11 @@ describe.concurrent("ModuleGraph: an error in what a graph opened is the graph's
"fs.watch listener": boom => {
const dir = path.join(import.meta.dirname, "watched");
fs.mkdirSync(dir);
const w = fs.watch(dir, () => { w.close(); boom(); });
fs.writeFileSync(path.join(dir, "f"), "x");
const w = fs.watch(dir, () => { clearInterval(again); w.close(); boom(); });
// macOS starts watching on another thread after fs.watch() returns, and reports nothing written before that.
const write = () => fs.writeFileSync(path.join(dir, "f"), "x");
write();
const again = setInterval(write, 5);
},
"a read stream's 'data'": boom => { fs.createReadStream(import.meta.filename).on("data", boom); },
"a zlib stream's 'data'": boom => { const z = zlib.createGzip(); z.on("data", boom); z.end("hello"); },
Expand Down Expand Up @@ -1052,8 +1055,9 @@ describe.concurrent("ModuleGraph: an error in what a graph opened is the graph's
import { host } from "./host.mjs";
const { promise, resolve } = Promise.withResolvers();
process.on("uncaughtException", error => resolve("host: " + error.code));
const graph = new Bun.ModuleGraph({ globals: { closedPort: await host.closedUdpPort() }, onError: error => resolve("graph: " + error.code) });
const { stop } = await graph.import(import.meta.dir + "/tenant.mjs");
const { port, close } = await host.udpPortToClose();
const graph = new Bun.ModuleGraph({ globals: { closedPort: port }, onError: error => resolve("graph: " + error.code) });
const { stop } = await graph.import(import.meta.dir + "/tenant.mjs").finally(close);
console.log(await promise);
stop();
process.exit(0);
Expand Down
15 changes: 8 additions & 7 deletions test/js/bun/shell/yield.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,17 +330,18 @@ describe("yield", async () => {
test.concurrent("the killed member is gone after the rejection", async () => {
using dir = tempDir("shell-failed-pipeline-pid", {});
const bun = process.execPath;
const writePid = 'require("fs").writeFileSync("pid", String(process.pid)); setTimeout(() => {}, 100_000)';
const waitForPid =
'while (!require("fs").existsSync("pid")) Bun.sleepSync(5); process.stdout.write("--version")';
// By its full path: `dir` is nobody's working directory, which Windows does not let go of until some time
// after `kill(pid, 0)` stops finding the process.
const pidFile = join(String(dir), "pid");
const writePid = `require("fs").writeFileSync(${JSON.stringify(pidFile)}, String(process.pid)); setTimeout(() => {}, 100_000)`;
// For its content: the file is there before the pid is in it.
const waitForPid = `const pid = () => { try { return require("fs").readFileSync(${JSON.stringify(pidFile)}, "utf8"); } catch { return ""; } }; while (!pid()) Bun.sleepSync(5); process.stdout.write("--version")`;
expect(
await rejectionOf(
$`${bun} -e ${writePid} | ${bun} $(${bun} -e ${waitForPid}) > ${new Response("r")}`
.cwd(String(dir))
.quiet(),
$`${bun} -e ${writePid} | ${bun} $(${bun} -e ${waitForPid}) > ${new Response("r")}`.quiet(),
),
).toMatchObject({ message: "Blobs are immutable, and cannot be used for stdout/stderr" });
const pid = Number(await Bun.file(join(String(dir), "pid")).text());
const pid = Number(await Bun.file(pidFile).text());
expect(pid).toBeGreaterThan(0);
// The kill is sent before the promise rejects. The shell reaps the
// child when its exit reaches the event loop, so poll for that.
Expand Down
3 changes: 2 additions & 1 deletion test/js/bun/spawn/spawn-stdin-destroy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ test("stdin destroy after exit crash", async () => {
stdin: "pipe",
});

await Bun.sleep(80);
// The crash was in the first read of `child.stdin` after the exit.
await child.exited;
await child.stdin.write("dylan\n");
await child.stdin.write("999\n");
await child.stdin.flush();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { bunEnv, bunExe } from "harness";
import { createTest } from "node-harness";
import { once } from "node:events";
import { createServer } from "node:http";
import type { AddressInfo } from "node:net";
import * as path from "node:path";
const { expect } = createTest(import.meta.path);

const server = createServer((req, res) => res.end("hello")).listen(0, "127.0.0.1");
await once(server, "listening");
const { port } = server.address() as AddressInfo;

const { stdout, exited } = Bun.spawn({
cmd: [bunExe(), "run", path.join(import.meta.dir, "../../../node/http/fixtures/log-events.mjs")],
cmd: [bunExe(), "run", path.join(import.meta.dir, "../../../node/http/fixtures/log-events.mjs"), String(port)],
stdout: "pipe",
stdin: "ignore",
stderr: "inherit",
Expand All @@ -30,3 +37,4 @@ expect(
["res", "close"],
]);
expect(await exited).toBe(0);
server.close();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
17 changes: 9 additions & 8 deletions test/js/first_party/ws/ws-server-compat-close.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,17 +420,18 @@ describe.each(implementations)("ws server close ($name)", ({ ws: implementation,
describe.skipIf(isWindows)("under backpressure", () => {
const size = 64 * 1024;

// Sends until the kernel buffer is full, then twice more. The client runs on this thread, so it can't read yet.
// Sends until three messages in a row have left something unwritten. The client runs on this thread, so it can't
// read yet. The kernel can still make room between two sends, by moving what it took to the client's buffer, and a
// send() that finds room also writes what the ones before it left.
function fill(ws: WsSocket) {
shrinkBuffers({ sender: ws });
const results: Promise<string>[] = [];
const send = () => results.push(sendResult(ws, Buffer.alloc(size, results.length)));
while (ws.bufferedAmount === 0 && results.length < 256) send();
const written = results.length - 1;
const { bufferedAmount } = ws;
send();
send();
return { results, written, bufferedAmount };
let written = 0;
while (results.length - written < 3 && results.length < 256) {
results.push(sendResult(ws, Buffer.alloc(size, results.length)));
if (ws.bufferedAmount === 0) written = results.length;
}
return { results, written, bufferedAmount: ws.bufferedAmount };
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}

function fillAndClose(ws: WsSocket) {
Expand Down
4 changes: 2 additions & 2 deletions test/js/node/http/fixtures/log-events.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as http from "node:http";

const options = {
hostname: "www.example.com",
port: 80,
hostname: "127.0.0.1",
port: process.argv[2],
path: "/",
method: "GET",
headers: {},
Expand Down
3 changes: 2 additions & 1 deletion test/js/node/tls/node-tls-connect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5627,7 +5627,8 @@ describe("connect(port) on a TLSSocket that runs over a transport the caller han
const transport = proxied ? new SocketProxy(raw) : raw;
const client = wrap(transport);
try {
await once(client, established);
// The server's handshake ends after the client's: it has yet to read the client's Finished.
await Promise.all([once(client, established), once(server, "secureConnection")]);
// Not from inside the handshake callback.
await new Promise(resolve => setImmediate(resolve));
const errored = once(client, "error");
Expand Down
4 changes: 3 additions & 1 deletion test/js/node/tls/tls-shutdown-before-handshake-fixture.mjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions test/js/third_party/jsonwebtoken/async_sign.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ describe("signing a token asynchronously", function () {
var secret = "shhhhhh";

it("should return the same result as singing synchronously", function (done) {
jwt.sign({ foo: "bar" }, secret, { algorithm: "HS256" }, function (err, asyncToken) {
// Without an iat each sign() stamps the current second, and the two calls can land in different seconds.
var payload = { foo: "bar", iat: 1437018582 };
jwt.sign(payload, secret, { algorithm: "HS256" }, function (err, asyncToken) {
if (err) return done(err);
var syncToken = jwt.sign({ foo: "bar" }, secret, { algorithm: "HS256" });
var syncToken = jwt.sign(payload, secret, { algorithm: "HS256" });
expect(typeof asyncToken).toBe("string");
expect(asyncToken.split(".")).toHaveLength(3);
expect(asyncToken).toEqual(syncToken);
Expand Down
21 changes: 12 additions & 9 deletions test/js/web/fetch/body-clone.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1139,7 +1139,7 @@ describe("clone() of a body over an unread native stream keeps the Blob behind i
// them. A body over such a store is read as one stream and teed instead,
// whether it was given as a stream or as the Blob itself, and both bodies
// see the whole input.
async function cloneInChild(bodyExpr: string, args: string[] = []) {
async function cloneInChild(bodyExpr: string, fifo?: string) {
await using proc = Bun.spawn({
cmd: [
bunExe(),
Expand All @@ -1148,17 +1148,20 @@ describe("clone() of a body over an unread native stream keeps the Blob behind i
const c = r.clone();
const [a, b] = await Promise.all([r.text(), c.text()]);
console.log(JSON.stringify([a, b]));`,
...args,
...(fifo ? [fifo] : []),
],
env: bunEnv,
stdin: "pipe",
// A child that reads the FIFO never reads stdin and can exit before a write to it, which is EPIPE.
stdin: fifo ? "ignore" : "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("hello ");
await proc.stdin.flush();
proc.stdin.write("world");
await proc.stdin.end();
if (proc.stdin) {
proc.stdin.write("hello ");
await proc.stdin.flush();
proc.stdin.write("world");
await proc.stdin.end();
}
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
return { stdout: stdout.trim(), stderr, exitCode };
}
Expand Down Expand Up @@ -1193,7 +1196,7 @@ describe("clone() of a body over an unread native stream keeps the Blob behind i
stdout: "ignore",
stderr: "inherit",
});
expect(await cloneInChild("Bun.file(process.argv.at(-1)).stream()", [fifo])).toEqual(bothBodiesReadStdin);
expect(await cloneInChild("Bun.file(process.argv.at(-1)).stream()", fifo)).toEqual(bothBodiesReadStdin);
expect(await writer.exited).toBe(0);
});

Expand All @@ -1205,7 +1208,7 @@ describe("clone() of a body over an unread native stream keeps the Blob behind i
stdout: "ignore",
stderr: "inherit",
});
expect(await cloneInChild("Bun.file(process.argv.at(-1))", [fifo])).toEqual(bothBodiesReadStdin);
expect(await cloneInChild("Bun.file(process.argv.at(-1))", fifo)).toEqual(bothBodiesReadStdin);
expect(await writer.exited).toBe(0);
});

Expand Down
2 changes: 1 addition & 1 deletion test/js/web/fetch/fetch-abort-stream-body.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ describe("aborting mid-body fails every reader of res.body with signal.reason",
await uploading.promise;
controller.abort(reason);
}
await expect(upload).rejects.toBe(reason);
expect(await settle(upload)).toBe(reason);
Comment thread
dylan-conway marked this conversation as resolved.
Resolved
},
);

Expand Down
Loading
Loading