186 lines · 7.0 KB · TypeScript
RawDownload
import * as NodeServices from "@effect/platform-node/NodeServices";import { assert, describe, it } from "@effect/vitest";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Option from "effect/Option";import * as Path from "effect/Path"; import { findRunningServerPid, findRunningServerPidSync } from "./server-runtime-probe.ts"; /** A pid that cannot belong to a live process (above the usual pid_max). */const DEAD_PID = 0x7ffffffe; const writeRuntimeState = Effect.fn(function* (input: {  readonly baseDir: string;  readonly stateDir: "dev" | "userdata";  readonly contents: string;}) {  const fileSystem = yield* FileSystem.FileSystem;  const path = yield* Path.Path;  const directory = path.join(input.baseDir, input.stateDir);  yield* fileSystem.makeDirectory(directory, { recursive: true });  yield* fileSystem.writeFileString(path.join(directory, "server-runtime.json"), input.contents);}); const runtimeStateJson = (pid: number) =>  JSON.stringify({    version: 1,    pid,    port: 16_602,    origin: "http://127.0.0.1:16602",    startedAt: "2026-07-20T00:00:00.000Z",  }); const makeBaseDir = Effect.fnUntraced(function* () {  const fileSystem = yield* FileSystem.FileSystem;  return yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-runtime-probe-test-" });}); describe("findRunningServerPid", () => {  it.effect("reports the pid of a live server in the dev state directory", () =>    Effect.gen(function* () {      const baseDir = yield* makeBaseDir();      yield* writeRuntimeState({        baseDir,        stateDir: "dev",        contents: runtimeStateJson(process.pid),      });       assert.equal(Option.getOrThrow(yield* findRunningServerPid(baseDir)), process.pid);    }).pipe(Effect.provide(NodeServices.layer)),  );   // An explicit --home-dir puts runtime state in userdata instead, and the  // caller of a seed has no reason to know which of the two was used.  it.effect("also finds a live server in the userdata state directory", () =>    Effect.gen(function* () {      const baseDir = yield* makeBaseDir();      yield* writeRuntimeState({        baseDir,        stateDir: "userdata",        contents: runtimeStateJson(process.pid),      });       assert.equal(Option.getOrThrow(yield* findRunningServerPid(baseDir)), process.pid);    }).pipe(Effect.provide(NodeServices.layer)),  );   // A server killed with SIGKILL never clears its state file. Treating that as  // "running" would block seeding for good, with nothing to clear it.  it.effect("ignores a state file whose process is gone", () =>    Effect.gen(function* () {      const baseDir = yield* makeBaseDir();      yield* writeRuntimeState({ baseDir, stateDir: "dev", contents: runtimeStateJson(DEAD_PID) });       assert.isTrue(Option.isNone(yield* findRunningServerPid(baseDir)));    }).pipe(Effect.provide(NodeServices.layer)),  );   // A half-written file says nothing about liveness either way.  it.effect("ignores a state file it cannot parse", () =>    Effect.gen(function* () {      const baseDir = yield* makeBaseDir();      yield* writeRuntimeState({ baseDir, stateDir: "dev", contents: "{not json" });       assert.isTrue(Option.isNone(yield* findRunningServerPid(baseDir)));    }).pipe(Effect.provide(NodeServices.layer)),  );   // `process.kill` reads these as signal-group targets, not process lookups, so  // neither throws — a naive probe calls both "live" and blocks seeding forever  // on a file no server ever wrote.  for (const pid of [0, -1]) {    it.effect(`ignores a state file recording pid ${String(pid)}`, () =>      Effect.gen(function* () {        const baseDir = yield* makeBaseDir();        yield* writeRuntimeState({ baseDir, stateDir: "dev", contents: runtimeStateJson(pid) });         assert.isTrue(Option.isNone(yield* findRunningServerPid(baseDir)));      }).pipe(Effect.provide(NodeServices.layer)),    );  }   it.effect("reports nothing when no state files exist", () =>    Effect.gen(function* () {      const baseDir = yield* makeBaseDir();      assert.isTrue(Option.isNone(yield* findRunningServerPid(baseDir)));    }).pipe(Effect.provide(NodeServices.layer)),  );}); /** * The sync probe exists only because `seedDevDatabase` holds an open SQLite * transaction and cannot await an Effect. Two implementations of one rule drift * apart silently, so every case is asserted against both rather than trusting * them to agree. */describe("findRunningServerPidSync agrees with the Effect probe", () => {  const cases = [    { name: "a live server in dev", stateDir: "dev", contents: runtimeStateJson(process.pid) },    {      name: "a live server in userdata",      stateDir: "userdata",      contents: runtimeStateJson(process.pid),    },    { name: "a dead pid", stateDir: "dev", contents: runtimeStateJson(DEAD_PID) },    { name: "pid 0", stateDir: "dev", contents: runtimeStateJson(0) },    { name: "pid -1", stateDir: "dev", contents: runtimeStateJson(-1) },    { name: "unparseable json", stateDir: "dev", contents: "{not json" },    { name: "json without a pid", stateDir: "dev", contents: JSON.stringify({ version: 1 }) },  ] as const;   for (const testCase of cases) {    it.effect(testCase.name, () =>      Effect.gen(function* () {        const baseDir = yield* makeBaseDir();        yield* writeRuntimeState({          baseDir,          stateDir: testCase.stateDir,          contents: testCase.contents,        });         const viaEffect = Option.getOrUndefined(yield* findRunningServerPid(baseDir));        assert.equal(findRunningServerPidSync(baseDir), viaEffect);      }).pipe(Effect.provide(NodeServices.layer)),    );  }   it.effect("both report nothing when no state files exist", () =>    Effect.gen(function* () {      const baseDir = yield* makeBaseDir();      assert.isTrue(Option.isNone(yield* findRunningServerPid(baseDir)));      assert.isUndefined(findRunningServerPidSync(baseDir));    }).pipe(Effect.provide(NodeServices.layer)),  );}); // A caller that has resolved which database it is about to touch must not be// blocked by a server using only the other one.describe("scoping to one state directory", () => {  it.effect("ignores a live server in the other directory", () =>    Effect.gen(function* () {      const baseDir = yield* makeBaseDir();      yield* writeRuntimeState({        baseDir,        stateDir: "dev",        contents: runtimeStateJson(process.pid),      });       assert.isUndefined(findRunningServerPidSync(baseDir, "userdata"));      assert.isTrue(Option.isNone(yield* findRunningServerPid(baseDir, "userdata")));    }).pipe(Effect.provide(NodeServices.layer)),  );   it.effect("still reports a live server in the named directory", () =>    Effect.gen(function* () {      const baseDir = yield* makeBaseDir();      yield* writeRuntimeState({        baseDir,        stateDir: "dev",        contents: runtimeStateJson(process.pid),      });       assert.equal(findRunningServerPidSync(baseDir, "dev"), process.pid);    }).pipe(Effect.provide(NodeServices.layer)),  );});