import { afterAll, describe, expect, test } from "bun:test";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Cause, Effect } from "effect";
import type {
HandleReference,
HostProfile,
InstanceRecord,
LaunchFailure,
ServingOptions,
} from "../src/modules/compute/contracts";
import { toEvent, toHttp } from "../src/modules/compute/failures";
import { makeInstanceStore } from "../src/modules/compute/instances/store";
import type { Launcher } from "../src/modules/compute/launchers/launcher";
import {
makeComputeService,
type ComputeLaunchInput,
type ComputeService,
} from "../src/modules/compute/lifecycle";
const run = (effect: Effect.Effect): Promise => Effect.runPromise(effect);
const runExit = (effect: Effect.Effect) => Effect.runPromiseExit(effect);
const roots: string[] = [];
const freshRoot = (): string => {
const root = mkdtempSync(join(tmpdir(), "compute-test-"));
roots.push(root);
return root;
};
afterAll(() => {
for (const root of roots) rmSync(root, { recursive: true, force: true });
});
const linuxHost: HostProfile = {
nodeId: "self",
platform: "linux",
arch: "x64",
accelerator: "cuda",
unifiedMemory: false,
wsl: false,
docker: false,
dockerGpu: false,
deviceCount: 2,
};
const options: ServingOptions = {
tensorParallel: 1,
pipelineParallel: 1,
maxContextLength: 4096,
memoryFraction: 0.9,
maxConcurrentRequests: 32,
kvCacheDtype: null,
dtype: null,
quantization: null,
trustRemoteCode: false,
toolCallParser: null,
reasoningParser: null,
};
const input = (overrides: Partial = {}): ComputeLaunchInput => ({
name: "test-model",
engine: "llamacpp",
recipeId: "r1",
runtime: "process",
deviceCount: 1,
modelPath: "/models/test.gguf",
servedModelName: "test",
options,
extraArgs: [],
env: {},
binary: "llama-server",
...overrides,
});
/** Launcher whose world is a mutable script: which handles are alive, what start does. */
interface FakeWorld {
alivePids: Set;
started: InstanceRecord[];
stopped: HandleReference[];
nextPid: number;
startFailure: LaunchFailure | null;
healthyPorts: Set;
}
const fakeLauncher = (world: FakeWorld): Launcher => ({
start: (_plan, record) => {
if (world.startFailure) return Effect.fail(world.startFailure);
const pid = world.nextPid++;
world.alivePids.add(pid);
world.started.push(record);
return Effect.succeed({ kind: "process", pid, startToken: null });
},
alive: (ref) => Effect.succeed(ref.kind === "process" && world.alivePids.has(ref.pid)),
owns: (ref) => Effect.succeed(ref.kind === "process" && world.alivePids.has(ref.pid)),
stop: (ref) =>
Effect.sync(() => {
if (ref.kind === "process") world.alivePids.delete(ref.pid);
world.stopped.push(ref);
}),
logTail: () => Effect.succeed("fake log tail"),
});
const makeWorld = (): FakeWorld => ({
alivePids: new Set(),
started: [],
stopped: [],
nextPid: 1000,
startFailure: null,
healthyPorts: new Set(),
});
const makeService = (
world: FakeWorld,
devices: readonly string[] = ["GPU-a", "GPU-b"],
): { compute: ComputeService; store: ReturnType; events: string[] } => {
const store = makeInstanceStore(freshRoot());
const events: string[] = [];
const compute = makeComputeService({
store,
launcherFor: () => fakeLauncher(world),
host: () => Effect.succeed(linuxHost),
freeDevices: () => Effect.succeed(devices),
onEvent: (name, stage) => Effect.sync(() => void events.push(`${name}:${stage}`)),
});
return { compute, store, events };
};
// The fake never serves HTTP, so "healthy" can only come from a real server. For the
// ready-path test we stand up a real Bun server on the allocated port instead.
describe("reserve is the lease", () => {
test("devices of a live record are held; dropping the record frees them", async () => {
const world = makeWorld();
const { store } = makeService(world);
const alive = () => Effect.succeed(true);
const first = await run(
store.reserve(
{
name: "a",
nodeId: "self",
engine: "llamacpp",
recipeId: "r",
runtime: "process",
candidates: ["GPU-a", "GPU-b"],
need: 2,
basePort: 8081,
readyDeadlineMs: 1000,
shareable: false,
},
alive,
),
);
expect(first.devices).toEqual(["GPU-a", "GPU-b"]);
// Everything is now held — a second reservation must fail with no-capacity.
const second = await runExit(
store.reserve(
{
name: "b",
nodeId: "self",
engine: "llamacpp",
recipeId: "r",
runtime: "process",
candidates: ["GPU-a", "GPU-b"],
need: 1,
basePort: 8081,
readyDeadlineMs: 1000,
shareable: false,
},
alive,
),
);
expect(second._tag).toBe("Failure");
// Drop the record: capacity comes back with no release call anywhere.
store.drop("a");
const third = await run(
store.reserve(
{
name: "c",
nodeId: "self",
engine: "llamacpp",
recipeId: "r",
runtime: "process",
candidates: ["GPU-a", "GPU-b"],
need: 1,
basePort: 8081,
readyDeadlineMs: 1000,
shareable: false,
},
alive,
),
);
expect(third.devices).toEqual(["GPU-a"]);
});
test("a dead record's devices are not held", async () => {
const world = makeWorld();
const { store } = makeService(world);
// Simulate a record whose process died: alive() answers false.
const dead = () => Effect.succeed(false);
await run(
store.reserve(
{
name: "a",
nodeId: "self",
engine: "llamacpp",
recipeId: "r",
runtime: "process",
candidates: ["GPU-a"],
need: 1,
basePort: 8081,
readyDeadlineMs: 1000,
shareable: false,
},
dead,
),
);
const record = store.read("a");
expect(record).not.toBeNull();
if (!record) return;
store.write({ ...record, ref: { kind: "process", pid: 999999, startToken: null } });
const held = await run(store.heldDevices(dead));
expect(held.size).toBe(0);
});
test("concurrent reservations under the placement lock never double-book", async () => {
const world = makeWorld();
const { store } = makeService(world);
const alive = () => Effect.succeed(true);
const reserveOne = (name: string) =>
store.reserve(
{
name,
nodeId: "self",
engine: "llamacpp",
recipeId: "r",
runtime: "process",
candidates: ["GPU-a", "GPU-b"],
need: 1,
basePort: 8081,
readyDeadlineMs: 1000,
shareable: false,
},
alive,
);
const results = await run(
Effect.all([reserveOne("one"), reserveOne("two")], { concurrency: "unbounded" }).pipe(
Effect.map((records) => records.map((record) => record.devices).flat()),
),
);
// Two reservations, two distinct devices — never the same one twice.
expect(new Set(results).size).toBe(2);
});
test("ports allocate upward from the engine base without collision", async () => {
const world = makeWorld();
const { store } = makeService(world);
const alive = () => Effect.succeed(true);
// High base so real listeners on this machine cannot interfere with the assertion.
const base = 42_081;
const reserveNamed = (name: string) =>
run(
store.reserve(
{
name,
nodeId: "self",
engine: "llamacpp",
recipeId: "r",
runtime: "process",
candidates: [`GPU-${name}`],
need: 1,
basePort: base,
readyDeadlineMs: 1000,
shareable: false,
},
alive,
),
);
const one = await reserveNamed("one");
const two = await reserveNamed("two");
expect(one.port).toBeGreaterThanOrEqual(base);
expect(two.port).toBeGreaterThan(one.port);
});
test("allocatePort refuses a port an unrelated process already holds", () => {
const world = makeWorld();
const { store } = makeService(world);
const squatter = Bun.serve({ port: 0, fetch: () => new Response("ok") });
try {
const taken = squatter.port as number;
expect(store.allocatePort(taken)).toBeGreaterThan(taken);
} finally {
squatter.stop(true);
}
});
});
describe("launch failure paths", () => {
test("unsupported engine/host fails before touching devices", async () => {
const world = makeWorld();
const { compute, store } = makeService(world);
const exit = await runExit(compute.launch(input({ engine: "mlx" })));
expect(exit._tag).toBe("Failure");
expect(store.all()).toHaveLength(0);
expect(world.started).toHaveLength(0);
});
test("spawn failure drops the record and frees the devices", async () => {
const world = makeWorld();
world.startFailure = { kind: "spawn-failed", detail: "binary not found" };
const { compute, store, events } = makeService(world);
const exit = await runExit(compute.launch(input()));
expect(exit._tag).toBe("Failure");
// The reservation must not leak: record gone, devices free.
expect(store.all()).toHaveLength(0);
expect(events).toContain("test-model:error");
});
test("exited-early: daemon dies while waiting -> record dropped, log tail captured", async () => {
const world = makeWorld();
const { compute, store } = makeService(world);
// Kill the "process" the moment it starts.
const original = fakeLauncher(world).start;
void original;
const launchEffect = compute.launch(input());
const exitPromise = runExit(launchEffect);
// Give the launch a moment to spawn, then kill the pid.
await new Promise((resolve) => setTimeout(resolve, 150));
world.alivePids.clear();
const exit = await exitPromise;
expect(exit._tag).toBe("Failure");
expect(store.all()).toHaveLength(0);
});
test("cancel mid-wait -> cancelled failure, record dropped", async () => {
const world = makeWorld();
const { compute, store } = makeService(world);
const exitPromise = runExit(compute.launch(input()));
await new Promise((resolve) => setTimeout(resolve, 150));
await run(compute.cancel("test-model"));
const exit = await exitPromise;
expect(exit._tag).toBe("Failure");
if (exit._tag === "Failure") {
const failure = Cause.findErrorOption(exit.cause);
expect(failure._tag).toBe("Some");
if (failure._tag === "Some") {
expect((failure.value as LaunchFailure).kind).toBe("cancelled");
}
}
expect(store.all()).toHaveLength(0);
});
test("second launch under the same name while starting -> already-running", async () => {
const world = makeWorld();
const { compute } = makeService(world);
const firstPromise = runExit(compute.launch(input()));
await new Promise((resolve) => setTimeout(resolve, 150));
const second = await runExit(compute.launch(input()));
expect(second._tag).toBe("Failure");
if (second._tag === "Failure") {
const failure = Cause.findErrorOption(second.cause);
if (failure._tag === "Some") {
expect((failure.value as LaunchFailure).kind).toBe("already-running");
}
}
await run(compute.cancel("test-model"));
await firstPromise;
});
});
describe("ready path with a real health endpoint", () => {
test("launch succeeds once /health answers 200", async () => {
const world = makeWorld();
const { compute, store, events } = makeService(world);
// The port is allocated inside launch; discover it from the reservation record
// (written before spawn) and only then stand up the health endpoint on it.
const launchPromise = run(compute.launch(input()));
let port: number | null = null;
for (let attempt = 0; attempt < 100 && port === null; attempt += 1) {
port = store.read("test-model")?.port ?? null;
if (port === null) await new Promise((resolve) => setTimeout(resolve, 20));
}
expect(port).not.toBeNull();
if (port === null) return;
const server = Bun.serve({
port,
fetch: (request) =>
new URL(request.url).pathname === "/health"
? new Response("ok")
: new Response("no", { status: 404 }),
});
try {
const record = await launchPromise;
expect(record.ref?.kind).toBe("process");
expect(record.port).toBe(port);
expect(events).toContain("test-model:ready");
const views = await run(compute.instances());
expect(views).toHaveLength(1);
expect(views[0]?.state).toBe("ready");
// stop() tears down and frees.
expect(await run(compute.stop("test-model"))).toBe(true);
expect(store.all()).toHaveLength(0);
expect(world.stopped).toHaveLength(1);
} finally {
server.stop(true);
}
});
});
describe("supervisor", () => {
test("reaps records whose handle is gone; leaves live ones alone", async () => {
const world = makeWorld();
const { compute, store } = makeService(world);
const liveRef: HandleReference = { kind: "process", pid: 2000, startToken: null };
world.alivePids.add(2000);
const base: InstanceRecord = {
name: "live",
nodeId: "self",
engine: "llamacpp",
recipeId: "r",
runtime: "process",
ref: liveRef,
port: 8081,
devices: ["GPU-a"],
nonce: "n1",
startedAt: new Date().toISOString(),
readyDeadlineAt: new Date(Date.now() + 60_000).toISOString(),
};
store.write(base);
store.write({
...base,
name: "dead",
ref: { kind: "process", pid: 3000, startToken: null },
port: 8082,
devices: ["GPU-b"],
});
const reaped = await run(compute.superviseOnce());
expect(reaped).toBe(1);
expect(store.read("live")).not.toBeNull();
expect(store.read("dead")).toBeNull();
});
test("stale reservations (no handle) are reaped after the grace window", async () => {
const world = makeWorld();
const { compute, store } = makeService(world);
store.write({
name: "crashed-before-spawn",
nodeId: "self",
engine: "llamacpp",
recipeId: "r",
runtime: "process",
ref: null,
port: 8081,
devices: ["GPU-a"],
nonce: "n",
startedAt: new Date(Date.now() - 120_000).toISOString(),
readyDeadlineAt: new Date(Date.now() - 60_000).toISOString(),
});
expect(await run(compute.superviseOnce())).toBe(1);
expect(store.all()).toHaveLength(0);
});
});
describe("failure mappers", () => {
test("every failure kind maps to exactly one status and one stage", () => {
const failures: LaunchFailure[] = [
{ kind: "unsupported", engine: "vllm", reason: "no Metal backend" },
{ kind: "already-running", name: "x" },
{ kind: "no-capacity", need: 2, free: 0 },
{ kind: "install-failed", engine: "vllm", detail: "pip failed" },
{ kind: "spawn-failed", detail: "ENOENT" },
{ kind: "exited-early", exitCode: 1, signal: null, logTail: "CUDA OOM" },
{ kind: "unhealthy-timeout", waitedMs: 60_000, logTail: "loading..." },
{ kind: "cancelled" },
];
const statuses = failures.map((failure) => toHttp(failure).status);
expect(statuses).toEqual([422, 409, 409, 503, 503, 503, 503, 400]);
// Cancellation is a stage, not a substring match on the message.
expect(toEvent({ kind: "cancelled" }).stage).toBe("cancelled");
expect(toEvent({ kind: "spawn-failed", detail: "operation cancelled by user" }).stage).toBe(
"error",
);
// The crash log rides along at a single truncation length.
expect(toHttp(failures[5] as LaunchFailure).detail).toContain("CUDA OOM");
});
});