t3-code-android-nightly/.repos/alchemy-effect/packages/alchemy/test/Cli/shutdownFeedback.test.ts
Julius Marminge 6f9cea00ae
chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 13:22:30 -07:00

119 lines
3.7 KiB
TypeScript

import { PlatformServices } from "@/Util/PlatformServices.ts";
import { describe, expect, it } from "alchemy-test";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as Schedule from "effect/Schedule";
import * as Stream from "effect/Stream";
import * as ChildProcess from "effect/process/ChildProcess";
import { fileURLToPath } from "node:url";
const FIXTURE = fileURLToPath(
new URL("./fixtures/shutdown-feedback-fixture.ts", import.meta.url),
);
/**
* Spawn the fixture (piped stdio, so the feedback takes the non-TTY log-line
* branch), wait for its ready line, and expose accumulated stderr.
*/
const spawnFixture = (env: Record<string, string> = {}) =>
Effect.gen(function* () {
const handle = yield* ChildProcess.make("bun", [FIXTURE], {
env,
extendEnv: true,
stdin: "ignore",
stdout: "pipe",
stderr: "pipe",
killSignal: "SIGTERM",
forceKillAfter: "1 second",
});
const chunks: string[] = [];
yield* handle.stderr.pipe(
Stream.decodeText,
Stream.runForEach((chunk) =>
Effect.sync(() => {
chunks.push(chunk);
}),
),
Effect.ignore,
Effect.forkScoped,
);
yield* handle.stdout.pipe(
Stream.decodeText,
Stream.splitLines,
Stream.filter((line) => line.includes("ready")),
Stream.take(1),
Stream.runDrain,
Effect.timeout(Duration.seconds(10)),
);
return {
handle,
sigint: Effect.sync(() => process.kill(handle.pid, "SIGINT")),
stderr: () => chunks.join(""),
};
});
const waitForStderr = (read: () => string, text: string) =>
Effect.sync(read).pipe(
Effect.repeat({
schedule: Schedule.spaced(Duration.millis(50)),
until: (output) => output.includes(text),
times: 100,
}),
Effect.flatMap((output) =>
output.includes(text)
? Effect.void
: Effect.fail(
new Error(
`stderr never contained ${JSON.stringify(text)}: ${output}`,
),
),
),
);
describe("shutdown feedback", { tags: ["unit", "local"] }, () => {
it.live(
"a slow shutdown prints one delayed status line",
() =>
Effect.gen(function* () {
const fixture = yield* spawnFixture({
SHUTDOWN_FIXTURE_EXIT_AFTER_MS: "800",
});
yield* fixture.sigint;
yield* Effect.sleep(Duration.millis(300));
expect(fixture.stderr()).not.toContain("Shutting down");
yield* waitForStderr(fixture.stderr, "Shutting down");
expect(yield* fixture.handle.exitCode).toBe(0);
expect(fixture.stderr().match(/Shutting down/g)).toHaveLength(1);
}).pipe(Effect.scoped, Effect.provide(PlatformServices)),
{ timeout: 30_000 },
);
it.live(
"a shutdown finishing within the delay stays silent",
() =>
Effect.gen(function* () {
const fixture = yield* spawnFixture({
SHUTDOWN_FIXTURE_EXIT_AFTER_MS: "50",
});
yield* fixture.sigint;
expect(yield* fixture.handle.exitCode).toBe(0);
expect(fixture.stderr()).not.toContain("Shutting down");
}).pipe(Effect.scoped, Effect.provide(PlatformServices)),
{ timeout: 30_000 },
);
it.live(
"a suppressed process stays silent",
() =>
Effect.gen(function* () {
const fixture = yield* spawnFixture({
SHUTDOWN_FIXTURE_SUPPRESS: "1",
SHUTDOWN_FIXTURE_EXIT_AFTER_MS: "800",
});
yield* fixture.sigint;
expect(yield* fixture.handle.exitCode).toBe(0);
expect(fixture.stderr()).not.toContain("Shutting down");
}).pipe(Effect.scoped, Effect.provide(PlatformServices)),
{ timeout: 30_000 },
);
});