test/scripts/process.test.ts
Rama main · réplica de lectura del repositorio original.
import { expect } from "@std/expect";
import { describe, it as test } from "@std/testing/bdd";
import {
INHERIT_STDIO,
removeTree,
runDeno,
stopProcess,
stopProcessNow,
} from "#scripts/process.ts";
import { stubChildProcess } from "#test-utils/command-capture.ts";
import { tempDir } from "#test-utils/files.ts";
const stopped = (code = 0): Deno.CommandStatus => ({
code,
signal: null,
success: code === 0,
});
const fakeChild = (
onKill: (
signal: Deno.Signal | undefined,
finish: (status?: Deno.CommandStatus) => void,
fail: (error?: Error) => void,
) => void,
) => {
let refed = false;
const calls: (Deno.Signal | undefined)[] = [];
let resolveStatus: (status: Deno.CommandStatus) => void = () => {};
let rejectStatus: (error: Error) => void = () => {};
const status = new Promise<Deno.CommandStatus>((resolve, reject) => {
resolveStatus = resolve;
rejectStatus = reject;
});
const finish = (status = stopped()) => resolveStatus(status);
const fail = (error = new Error("stopped")) => rejectStatus(error);
const child = stubChildProcess({
kill: (signal?: Deno.Signal) => {
calls.push(signal);
onKill(signal, finish, fail);
},
ref: () => {
refed = true;
},
status,
});
return { calls, child, refed: () => refed };
};
describe("script process helpers", () => {
test("shares the terminal with the child on all three streams", () => {
expect(INHERIT_STDIO).toEqual({
stderr: "inherit",
stdin: "inherit",
stdout: "inherit",
});
});
test("removes a folder that still has things in it", async () => {
using dir = tempDir();
Deno.mkdirSync(`${dir.path}/nested`);
Deno.writeTextFileSync(`${dir.path}/nested/file.txt`, "bye");
await removeTree(`${dir.path}/nested`);
expect(() => Deno.statSync(`${dir.path}/nested`)).toThrow(
Deno.errors.NotFound,
);
});
test("runs the current Deno executable", async () => {
const result = await runDeno(["eval", "Deno.exit(7)"], Deno.cwd());
expect(result.code).toBe(7);
expect(result.success).toBe(false);
});
test("stops a child process gracefully and closes resources", async () => {
let closed = false;
const process = fakeChild((_signal, finish) => finish());
await stopProcess(process.child, 50, () => {
closed = true;
return Promise.resolve();
});
expect(process.refed()).toBe(true);
expect(closed).toBe(true);
expect(process.calls).toEqual([undefined]);
});
test("force-stops a child process that ignores the first signal", async () => {
const process = fakeChild((signal, _finish, fail) => {
if (signal === "SIGKILL") fail();
});
await stopProcess(process.child, 1);
expect(process.calls).toEqual([undefined, "SIGKILL"]);
});
test("waits for a force-stopped child to actually end", async () => {
const order: string[] = [];
const killed = Promise.withResolvers<() => void>();
const process = fakeChild((signal, finish) => {
if (signal === "SIGKILL") {
killed.resolve(() => {
order.push("child ended");
finish();
});
}
});
const stopping = stopProcess(process.child, 1, () => {
order.push("cleaned up");
return Promise.resolve();
});
// Wait for the force-stop to actually be asked for, then end the child.
const endKilled = await killed.promise;
endKilled();
await stopping;
expect(order).toEqual(["child ended", "cleaned up"]);
});
test("still closes resources when the graceful signal fails", async () => {
let closed = false;
const child = stubChildProcess({
kill: () => {
throw new Error("already stopped");
},
ref: () => {},
status: Promise.resolve(stopped()),
});
await stopProcess(child, 50, () => {
closed = true;
return Promise.resolve();
});
expect(closed).toBe(true);
});
test("ignores already-stopped children in immediate stop", () => {
const child = stubChildProcess({
kill: () => {
throw new Error("already stopped");
},
});
expect(() => stopProcessNow(child)).not.toThrow();
});
test("fails loudly when a killed child never exits", async () => {
const process = fakeChild((_signal, _finish) => {});
await expect(stopProcess(process.child, 1)).rejects.toThrow(
"child process did not exit after SIGKILL within 1ms",
);
expect(process.calls).toEqual([undefined, "SIGKILL"]);
});
});