MCPcopy Index your code
hub / github.com/anomalyco/opencode / withCliFixture

Function withCliFixture

packages/opencode/test/lib/cli-process.ts:189–481  ·  view source on GitHub ↗
(
  fn: (input: CliFixture) => Effect.Effect<A, E, Scope.Scope | HttpClient.HttpClient>,
)

Source from the content-addressed store, hash-verified

187// the caller doesn't need to wire it up — the fixture's lifetime is tied to
188// the surrounding Scope.
189export function withCliFixture<A, E>(
190 fn: (input: CliFixture) => Effect.Effect<A, E, Scope.Scope | HttpClient.HttpClient>,
191): Effect.Effect<A, E | unknown, Scope.Scope> {
192 return Effect.gen(function* () {
193 const llm = yield* TestLLMServer
194 const fs = yield* FSUtil.Service
195 const appProc = yield* AppProcess.Service
196
197 const home = yield* fs.makeTempDirectory({ prefix: "oc-cli-" })
198 yield* Effect.addFinalizer(() =>
199 fs
200 .remove(home, { recursive: true })
201 .pipe(Effect.retry(Schedule.spaced("50 millis").pipe(Schedule.both(Schedule.recurs(20)))), Effect.ignore),
202 )
203
204 const configJson = JSON.stringify(testProviderConfig(llm.url))
205 const env = isolatedEnv(home, configJson)
206
207 const spawn = Effect.fn("opencode.spawn")(function* (args: string[], opts?: SpawnOpts) {
208 const start = Date.now()
209 const timeoutMs = opts?.timeoutMs ?? 30_000
210 // stdin: "ignore" so the child doesn't see a piped stdin and block
211 // on `Bun.stdin.text()` (see src/cli/cmd/run.ts — non-TTY stdin is
212 // consumed as the prompt). The old Process.run wrapper defaulted to
213 // ignore; ChildProcess.make defaults to pipe, so we set it explicitly.
214 const command = ChildProcess.make("bun", ["run", "--conditions=browser", cliEntry, ...args], {
215 cwd: home,
216 env: { ...env, ...opts?.env },
217 extendEnv: true,
218 stdin: "ignore",
219 })
220 // Pass timeout to appProc.run rather than wrapping with
221 // Effect.timeoutOrElse externally: AppProcess.run is itself scoped, so
222 // its built-in timeout triggers the acquireRelease kill finalizer
223 // inside cross-spawn-spawner *before* surfacing the AppProcessError —
224 // guaranteeing the child is dead by the time the test continues.
225 // External timeoutOrElse interrupts the run fiber but races the
226 // scope close, which can leak the child past the test boundary.
227 //
228 // Catch AppProcessError (timeout OR spawn failure) and synthesize a
229 // non-zero result so the test sees it via the usual `expectExit`
230 // path rather than as an unhandled Effect failure.
231 const result = yield* appProc.run(command, { timeout: Duration.millis(timeoutMs) }).pipe(
232 Effect.catchTag("AppProcessError", (err) =>
233 Effect.succeed({
234 command: err.command,
235 exitCode: err.exitCode ?? -1,
236 stdout: Buffer.alloc(0),
237 stderr: Buffer.from((err.stderr ?? String(err.cause ?? err.message)) + "\n"),
238 stdoutTruncated: false,
239 stderrTruncated: false,
240 } satisfies AppProcess.RunResult),
241 ),
242 )
243 return {
244 exitCode: result.exitCode,
245 stdout: normalizeLines(result.stdout.toString()),
246 stderr: normalizeLines(result.stderr.toString()),

Callers 1

cli-process.tsFile · 0.85

Calls 15

testProviderConfigFunction · 0.90
isolatedEnvFunction · 0.85
normalizeLinesFunction · 0.85
runOptsFunction · 0.85
runArgsFunction · 0.85
forkStderrDrainFunction · 0.85
fromBunStreamFunction · 0.85
syncMethod · 0.80
spawnMethod · 0.80
pushMethod · 0.80
writeMethod · 0.80
removeMethod · 0.65

Tested by

no test coverage detected