UNPKG

@moonwall/cli

Version:

Testing framework for the Moon family of projects

136 lines (131 loc) 4.42 kB
// src/internal/effect/__tests__/PortDiscoveryService.test.ts import { describe, it, expect } from "vitest"; import { Effect as Effect2, Exit } from "effect"; // src/internal/effect/PortDiscoveryService.ts import { Effect, Context, Layer, Schedule } from "effect"; import { exec } from "child_process"; import { promisify } from "util"; // src/internal/effect/errors.ts import { Data } from "effect"; var PortDiscoveryError = class extends Data.TaggedError("PortDiscoveryError") { }; var NodeLaunchError = class extends Data.TaggedError("NodeLaunchError") { }; var NodeReadinessError = class extends Data.TaggedError("NodeReadinessError") { }; var ProcessError = class extends Data.TaggedError("ProcessError") { }; var StartupCacheError = class extends Data.TaggedError("StartupCacheError") { }; var FileLockError = class extends Data.TaggedError("FileLockError") { }; // src/internal/effect/PortDiscoveryService.ts var execAsync = promisify(exec); var PortDiscoveryService = class extends Context.Tag("PortDiscoveryService")() { }; var parsePortsFromLsof = (stdout) => { const ports = []; const lines = stdout.split("\n"); for (const line of lines) { const regex = /(?:.+):(\d+)/; const match = line.match(regex); if (match) { ports.push(Number.parseInt(match[1], 10)); } } return ports; }; var attemptPortDiscovery = (pid) => Effect.tryPromise({ try: () => execAsync(`lsof -p ${pid} -n -P | grep LISTEN`), catch: (cause) => new PortDiscoveryError({ cause, pid, attempts: 1 }) }).pipe( Effect.flatMap(({ stdout }) => { const ports = parsePortsFromLsof(stdout); if (ports.length === 0) { return Effect.fail( new PortDiscoveryError({ cause: new Error("No ports found in lsof output"), pid, attempts: 1 }) ); } const rpcPort = ports.find((p) => p !== 30333 && p !== 9615); if (!rpcPort) { if (ports.length === 1) { return Effect.succeed(ports[0]); } return Effect.fail( new PortDiscoveryError({ cause: new Error( `No RPC port found in range 9000-20000 and multiple ports detected (found ports: ${ports.join(", ")})` ), pid, attempts: 1 }) ); } return Effect.succeed(rpcPort); }) ); var discoverPortWithRetry = (pid, maxAttempts = 1200) => attemptPortDiscovery(pid).pipe( Effect.retry( Schedule.fixed("50 millis").pipe(Schedule.compose(Schedule.recurs(maxAttempts - 1))) ), Effect.catchAll( (error) => Effect.fail( new PortDiscoveryError({ cause: error, pid, attempts: maxAttempts }) ) ) ); var PortDiscoveryServiceLive = Layer.succeed(PortDiscoveryService, { discoverPort: discoverPortWithRetry }); // src/internal/effect/__tests__/PortDiscoveryService.test.ts describe("PortDiscoveryService", () => { it("should fail with PortDiscoveryError for invalid PID", async () => { const program = PortDiscoveryService.pipe( Effect2.flatMap((service) => service.discoverPort(99999999, 3)), // Invalid PID, 3 attempts Effect2.provide(PortDiscoveryServiceLive) ); const exit = await Effect2.runPromiseExit(program); expect(Exit.isFailure(exit)).toBe(true); if (Exit.isFailure(exit)) { expect(exit.cause._tag).toBe("Fail"); if (exit.cause._tag === "Fail") { expect(exit.cause.error).toBeInstanceOf(PortDiscoveryError); expect(exit.cause.error.pid).toBe(99999999); } } }); it("should discover port for current process", async () => { const currentPid = process.pid; const program = PortDiscoveryService.pipe( Effect2.flatMap((service) => service.discoverPort(currentPid, 5)), // Fewer attempts for speed Effect2.provide(PortDiscoveryServiceLive) ); const exit = await Effect2.runPromiseExit(program); expect(exit._tag).toMatch(/Success|Failure/); }); it("should include attempt count in error", async () => { const maxAttempts = 3; const program = PortDiscoveryService.pipe( Effect2.flatMap((service) => service.discoverPort(99999999, maxAttempts)), Effect2.provide(PortDiscoveryServiceLive) ); const exit = await Effect2.runPromiseExit(program); if (Exit.isFailure(exit) && exit.cause._tag === "Fail") { expect(exit.cause.error.attempts).toBe(maxAttempts); } }); });