Skip to content
Merged
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
69 changes: 53 additions & 16 deletions tests/native-compose-adoption-binding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
beforeEach,
expect,
setDefaultTimeout,
setSystemTime,
spyOn,
test,
} from "bun:test";
Expand Down Expand Up @@ -60,13 +61,22 @@ const FIXTURE_PROBE_DEADLINE_MS = 3 * PROBE_DEADLINE_MS;
const FAKE_DOCKER_TEST_TIMEOUT_MS = 8 * FIXTURE_PROBE_DEADLINE_MS;
setDefaultTimeout(FAKE_DOCKER_TEST_TIMEOUT_MS);
/**
* How long a test waits for the hanging fake Docker child to publish its PID.
* It covers acquisition work before the first probe and a cold Bun start under
* load, and stays below {@link FIXTURE_PROBE_DEADLINE_MS}, so the child is
* still owned by its probe when the test cancels it.
* The largest probe deadline `createNativeComposeProbe` admits. The
* cancellation tests give their hanging probe this deadline so that only their
* abort ends it. They do not wait on their own clock for the child to start:
* {@link startedChild} returns when the child publishes its PID, and fails
* distinctly if the acquisition settles first (HACK-1319).
*/
const CHILD_START_WAIT_MS = PROBE_DEADLINE_MS;
const CANCELLED_CHILD_PROBE_DEADLINE_MS = 60_000;
/** How often {@link startedChild} checks for the published PID. */
const CHILD_START_POLL_MS = 10;
/**
* The deadline of the probe the timeout test lets expire. The test pins
* `Date.now()` while it acquires, so the probe arms this whole deadline as a
* real timer after it spawns its child, however long the host takes to get
* there (HACK-1319).
*/
const EXPIRING_PROBE_DEADLINE_MS = 150;
/**
* How many freshness checks the moving job snapshot case's ordered start makes.
* Each is one `assertFresh`, one fixture-deadline probe. The V7 job executor
Expand Down Expand Up @@ -436,17 +446,31 @@ function acquireFixtureBinding(opts: {
});
}
/**
* Waits, at most {@link CHILD_START_WAIT_MS}, for the hanging fake Docker child
* to publish its PID, and returns it. The fake publishes by rename, and a PID
* must be a positive integer above 1: `Number("")` is 0, and
* `process.kill(0, 0)` signals this test's own process group and succeeds.
* Waits for the hanging fake Docker child of `pending` to publish its PID, and
* returns it. This is a handshake, not a timed wait: it ends when the child
* publishes, or fails when `pending` settles first, since a settled operation
* owns no child left to cancel. Under load the acquisition alone took about
* 14 s before its first probe, so a fixed start-up wait expired (HACK-1319).
* The fake publishes by rename, and a PID must be a positive integer above 1:
* `Number("")` is 0, and `process.kill(0, 0)` signals this test's own process
* group and succeeds.
*/
async function startedChild(): Promise<number> {
async function startedChild(pending: Promise<unknown>): Promise<number> {
const started = join(root, "started");
const deadline = Date.now() + CHILD_START_WAIT_MS;
let settled = false;
pending.then(
() => {
settled = true;
},
() => {
settled = true;
}
);
while (!(await Bun.file(started).exists())) {
if (Date.now() >= deadline) {
throw new Error("Fake Docker child did not start within its budget.");
if (settled) {
throw new Error(
"The operation settled before its fake Docker child published a PID."
);
}
await Bun.sleep(CHILD_START_POLL_MS);
}
Expand Down Expand Up @@ -1490,9 +1514,10 @@ test("cancelled real child is reaped and abort reason stays private", async () =
const pending = acquireFixtureBinding({
projectRoot,
signal: controller.signal,
timeoutMs: CANCELLED_CHILD_PROBE_DEADLINE_MS,
});
try {
const pid = await startedChild();
const pid = await startedChild(pending);
controller.abort(CANARY);
await refusal(pending, "E_LEGACY_COMPOSE_BINDING_CANCELLED");
expectReaped(observed.children, pid);
Expand All @@ -1510,9 +1535,18 @@ test("timeout reaps real child without authorizing any mutation", async () => {
// case; observe the actual returned child without changing spawn or the timer.
await writeFile(binary, `#!${process.execPath}\nawait Bun.sleep(60_000);\n`);
const observed = observeDockerChildren();
// The probe reads its deadline from Date.now() when it is created and arms
// the remainder as a real timer when it spawns. A stall between the two once
// used up all 150 ms, so the probe refused before it spawned any child.
// Pinning the clock arms the whole deadline after the spawn; the timer itself
// still runs in real time (HACK-1319).
setSystemTime(Date.now());
try {
await refusal(
acquireFixtureBinding({ projectRoot, timeoutMs: 150 }),
acquireFixtureBinding({
projectRoot,
timeoutMs: EXPIRING_PROBE_DEADLINE_MS,
}),
"E_LEGACY_COMPOSE_BINDING_PROBE"
);
expect(observed.children.length).toBeGreaterThan(0);
Expand All @@ -1524,6 +1558,7 @@ test("timeout reaps real child without authorizing any mutation", async () => {
}
expect(await Bun.file(join(root, "started")).exists()).toBe(false);
} finally {
setSystemTime();
// Preserve the assertion failure while preventing a broken reaping path from
// leaving this exact synthetic child alive in the rest of the test suite.
await observed.release();
Expand Down Expand Up @@ -1664,9 +1699,11 @@ test("two independently named checkouts retain separate existing data bindings",
});
test("captured cancellation cannot be replaced by a later fresh signal", async () => {
const controller = new AbortController();
// The freshness check below reuses this deadline for its hanging probe.
const acquired = await acquireFixtureBinding({
projectRoot,
signal: controller.signal,
timeoutMs: CANCELLED_CHILD_PROBE_DEADLINE_MS,
});
fixture.mode = "hang";
await save();
Expand All @@ -1679,7 +1716,7 @@ test("captured cancellation cannot be replaced by a later fresh signal", async (
// Read the PID before the abort. This test once read it afterwards, from a
// "started" file a child killed between create and write left empty:
// Number("") is 0, and process.kill(0, 0) succeeded (HACK-1452).
const pid = await startedChild();
const pid = await startedChild(pending);
controller.abort(CANARY);
await refusal(pending, "E_LEGACY_COMPOSE_BINDING_CANCELLED");
// The probe joins its child before it refuses, so this holds at once.
Expand Down
Loading
Loading