diff --git a/test/js/bun/test/test-timers-date-header-fixture.ts b/test/js/bun/test/test-timers-date-header-fixture.ts new file mode 100644 index 00000000000..e50c0498767 --- /dev/null +++ b/test/js/bun/test/test-timers-date-header-fixture.ts @@ -0,0 +1,20 @@ +import { jest, test } from "bun:test"; + +test("runAllTimers returns with a keep-alive connection open and no user timers", async () => { + using server = Bun.serve({ port: 0, hostname: "127.0.0.1", fetch: () => new Response("hi") }); + + jest.useFakeTimers(); + try { + // Accepting the connection arms Bun.serve's internal 1s Date header timer, + // and the keep-alive connection makes it re-arm itself every time it fires. + // Before oven-sh/bun#37946 it was enrolled in the fake heap, so runAllTimers() + // popped it, it re-armed at mocked now + 1s, and the drain never ended. + const res = await fetch(`http://127.0.0.1:${server.port}/`); + await res.text(); + + jest.runAllTimers(); + } finally { + jest.useRealTimers(); + } + console.log("RUN_ALL_OK"); +}); diff --git a/test/js/bun/test/test-timers.test.ts b/test/js/bun/test/test-timers.test.ts index 8d40f210634..dcca4852c64 100644 --- a/test/js/bun/test/test-timers.test.ts +++ b/test/js/bun/test/test-timers.test.ts @@ -114,3 +114,36 @@ test("real timer heap is ticked against the real clock under useFakeTimers", asy expect(proc.signalCode).toBeNull(); expect(exitCode).toBe(0); }); + +test("Bun.serve's Date header timer is not enrolled in fake timers", async () => { + using server = Bun.serve({ port: 0, hostname: "127.0.0.1", fetch: () => new Response("hi") }); + + jest.useFakeTimers(); + try { + // Accepting the connection arms the internal 1s Date header timer. + const res = await fetch(`http://127.0.0.1:${server.port}/`); + await res.text(); + + expect(jest.getTimerCount()).toBe(0); + } finally { + jest.useRealTimers(); + } +}); + +test("runAllTimers returns while Bun.serve holds a keep-alive connection", async () => { + await using proc = Bun.spawn({ + cmd: [bunExe(), "test", path.join(import.meta.dir, "test-timers-date-header-fixture.ts")], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + // On a regressed build runAllTimers() never returns (the Date header timer + // re-arms itself into the fake heap on every pop), so bound the child. + timeout: 20_000, + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + if (exitCode !== 0) console.error(stderr); + expect(stdout).toContain("RUN_ALL_OK"); + // null => exited on its own; non-null => killed by the spawn timeout (spun). + expect(proc.signalCode).toBeNull(); + expect(exitCode).toBe(0); +});