Unified · Split
| @@ -0,0 +1,45 @@ | ||
| 1 | +import { assert, describe, it } from "@effect/vitest"; | |
| 2 | +import * as Effect from "effect/Effect"; | |
| 3 | +import * as PlatformError from "effect/PlatformError"; | |
| 4 | +import { ChildProcessSpawner } from "effect/unstable/process"; | |
| 5 | + | |
| 6 | +import * as AcpError from "../errors.ts"; | |
| 7 | +import { makeTerminationError } from "./stdio.ts"; | |
| 8 | + | |
| 9 | +describe("ACP child process termination", () => { | |
| 10 | + it.effect("retains the process identifier with the exit code", () => | |
| 11 | + Effect.gen(function* () { | |
| 12 | + const error = yield* makeTerminationError({ | |
| 13 | + pid: ChildProcessSpawner.ProcessId(41), | |
| 14 | + exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(7)), | |
| 15 | + }); | |
| 16 | + | |
| 17 | + assert.instanceOf(error, AcpError.AcpProcessExitedError); | |
| 18 | + assert.equal(error.pid, 41); | |
| 19 | + assert.equal(error.code, 7); | |
| 20 | + assert.equal(error.message, "ACP process exited with code 7"); | |
| 21 | + }), | |
| 22 | + ); | |
| 23 | + | |
| 24 | + it.effect("retains the process identifier and exact exit-status cause", () => | |
| 25 | + Effect.gen(function* () { | |
| 26 | + const rootCause = new Error("private process diagnostics"); | |
| 27 | + const cause = PlatformError.systemError({ | |
| 28 | + _tag: "Unknown", | |
| 29 | + module: "ChildProcess", | |
| 30 | + method: "exitCode", | |
| 31 | + cause: rootCause, | |
| 32 | + }); | |
| 33 | + const error = yield* makeTerminationError({ | |
| 34 | + pid: ChildProcessSpawner.ProcessId(42), | |
| 35 | + exitCode: Effect.fail(cause), | |
| 36 | + }); | |
| 37 | + | |
| 38 | + assert.instanceOf(error, AcpError.AcpTransportError); | |
| 39 | + assert.equal(error.pid, 42); | |
| 40 | + assert.strictEqual(error.cause, cause); | |
| 41 | + assert.equal(error.message, "ACP transport operation read-process-exit-status failed."); | |
| 42 | + assert.notInclude(error.message, rootCause.message); | |
| 43 | + }), | |
| 44 | + ); | |
| 45 | +}); | |
| @@ -44,14 +44,20 @@ export const makeInMemoryStdio = Effect.fn("makeInMemoryStdio")(function* () { · 43 unmodified lines | ||
| 44 | 44 | }; |
| 45 | 45 | }); |
| 46 | 46 | |
| 47 | +type ChildProcessTerminationHandle = Pick< | |
| 48 | + ChildProcessSpawner.ChildProcessHandle, | |
| 49 | + "exitCode" | "pid" | |
| 50 | +>; | |
| 51 | + | |
| 47 | 52 | export const makeTerminationError = ( |
| 48 | − handle: ChildProcessSpawner.ChildProcessHandle, | |
| 53 | + handle: ChildProcessTerminationHandle, | |
| 49 | 54 | ): Effect.Effect<AcpError.AcpError> => |
| 50 | 55 | Effect.match(handle.exitCode, { |
| 51 | 56 | onFailure: (cause) => |
| 52 | 57 | new AcpError.AcpTransportError({ |
| 53 | 58 | operation: "read-process-exit-status", |
| 59 | + pid: handle.pid, | |
| 54 | 60 | cause, |
| 55 | 61 | }), |
| 56 | − onSuccess: (code) => new AcpError.AcpProcessExitedError({ code }), | |
| 62 | + onSuccess: (code) => new AcpError.AcpProcessExitedError({ code, pid: handle.pid }), | |
| 57 | 63 | }); |
| @@ -91,6 +91,7 @@ export class AcpProcessExitedError extends Schema.TaggedErrorClass<AcpProcessExi · 90 unmodified lines | ||
| 91 | 91 | "AcpProcessExitedError", |
| 92 | 92 | { |
| 93 | 93 | code: Schema.optional(Schema.Number), |
| 94 | + pid: Schema.optionalKey(Schema.Int), | |
| 94 | 95 | cause: Schema.optional(Schema.Defect()), |
| 95 | 96 | }, |
| 96 | 97 | ) { |
| @@ -160,6 +161,7 @@ export class AcpTransportError extends Schema.TaggedErrorClass<AcpTransportError · 63 unmodified lines | ||
| 160 | 161 | ), |
| 161 | 162 | method: Schema.optional(Schema.String), |
| 162 | 163 | detail: Schema.optional(Schema.String), |
| 164 | + pid: Schema.optionalKey(Schema.Int), | |
| 163 | 165 | cause: Schema.Defect(), |
| 164 | 166 | }, |
| 165 | 167 | ) { |
| @@ -0,0 +1,48 @@ | ||
| 1 | +import { assert, describe, it } from "@effect/vitest"; | |
| 2 | +import * as Effect from "effect/Effect"; | |
| 3 | +import * as PlatformError from "effect/PlatformError"; | |
| 4 | +import { ChildProcessSpawner } from "effect/unstable/process"; | |
| 5 | + | |
| 6 | +import * as CodexError from "../errors.ts"; | |
| 7 | +import { makeTerminationError } from "./stdio.ts"; | |
| 8 | + | |
| 9 | +describe("Codex App Server child process termination", () => { | |
| 10 | + it.effect("retains the process identifier with the exit code", () => | |
| 11 | + Effect.gen(function* () { | |
| 12 | + const error = yield* makeTerminationError({ | |
| 13 | + pid: ChildProcessSpawner.ProcessId(51), | |
| 14 | + exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(9)), | |
| 15 | + }); | |
| 16 | + | |
| 17 | + assert.instanceOf(error, CodexError.CodexAppServerProcessExitedError); | |
| 18 | + assert.equal(error.pid, 51); | |
| 19 | + assert.equal(error.code, 9); | |
| 20 | + assert.equal(error.message, "Codex App Server process exited with code 9"); | |
| 21 | + }), | |
| 22 | + ); | |
| 23 | + | |
| 24 | + it.effect("retains the process identifier and exact exit-status cause", () => | |
| 25 | + Effect.gen(function* () { | |
| 26 | + const rootCause = new Error("private process diagnostics"); | |
| 27 | + const cause = PlatformError.systemError({ | |
| 28 | + _tag: "Unknown", | |
| 29 | + module: "ChildProcess", | |
| 30 | + method: "exitCode", | |
| 31 | + cause: rootCause, | |
| 32 | + }); | |
| 33 | + const error = yield* makeTerminationError({ | |
| 34 | + pid: ChildProcessSpawner.ProcessId(52), | |
| 35 | + exitCode: Effect.fail(cause), | |
| 36 | + }); | |
| 37 | + | |
| 38 | + assert.instanceOf(error, CodexError.CodexAppServerTransportError); | |
| 39 | + assert.equal(error.pid, 52); | |
| 40 | + assert.strictEqual(error.cause, cause); | |
| 41 | + assert.equal( | |
| 42 | + error.message, | |
| 43 | + "Codex App Server transport operation 'read-process-exit-status' failed.", | |
| 44 | + ); | |
| 45 | + assert.notInclude(error.message, rootCause.message); | |
| 46 | + }), | |
| 47 | + ); | |
| 48 | +}); | |
| @@ -44,14 +44,20 @@ export const makeInMemoryStdio = Effect.fn("makeInMemoryStdio")(function* () { · 43 unmodified lines | ||
| 44 | 44 | }; |
| 45 | 45 | }); |
| 46 | 46 | |
| 47 | +type ChildProcessTerminationHandle = Pick< | |
| 48 | + ChildProcessSpawner.ChildProcessHandle, | |
| 49 | + "exitCode" | "pid" | |
| 50 | +>; | |
| 51 | + | |
| 47 | 52 | export const makeTerminationError = ( |
| 48 | − handle: ChildProcessSpawner.ChildProcessHandle, | |
| 53 | + handle: ChildProcessTerminationHandle, | |
| 49 | 54 | ): Effect.Effect<CodexError.CodexAppServerError> => |
| 50 | 55 | Effect.match(handle.exitCode, { |
| 51 | 56 | onFailure: (cause) => |
| 52 | 57 | new CodexError.CodexAppServerTransportError({ |
| 53 | 58 | operation: "read-process-exit-status", |
| 59 | + pid: handle.pid, | |
| 54 | 60 | cause, |
| 55 | 61 | }), |
| 56 | − onSuccess: (code) => new CodexError.CodexAppServerProcessExitedError({ code }), | |
| 62 | + onSuccess: (code) => new CodexError.CodexAppServerProcessExitedError({ code, pid: handle.pid }), | |
| 57 | 63 | }); |
| @@ -146,6 +146,7 @@ export class CodexAppServerProcessExitedError extends Schema.TaggedErrorClass<Co · 145 unmodified lines | ||
| 146 | 146 | "CodexAppServerProcessExitedError", |
| 147 | 147 | { |
| 148 | 148 | code: Schema.optional(Schema.Number), |
| 149 | + pid: Schema.optionalKey(Schema.Int), | |
| 149 | 150 | cause: Schema.optional(Schema.Defect()), |
| 150 | 151 | }, |
| 151 | 152 | ) { |
| @@ -233,6 +234,7 @@ export class CodexAppServerTransportError extends Schema.TaggedErrorClass<CodexA · 81 unmodified lines | ||
| 233 | 234 | "CodexAppServerTransportError", |
| 234 | 235 | { |
| 235 | 236 | operation: CodexAppServerTransportOperation, |
| 237 | + pid: Schema.optionalKey(Schema.Int), | |
| 236 | 238 | cause: Schema.Defect(), |
| 237 | 239 | }, |
| 238 | 240 | ) { |