diff --git a/packages/opencode/test/effect/instance-state.test.ts b/packages/opencode/test/effect/instance-state.test.ts index 23cd51d7f3..b47b740cd4 100644 --- a/packages/opencode/test/effect/instance-state.test.ts +++ b/packages/opencode/test/effect/instance-state.test.ts @@ -1,15 +1,21 @@ -import { afterEach, expect } from "bun:test" +import { expect } from "bun:test" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { $ } from "bun" import { Context, Deferred, Duration, Effect, Exit, Fiber, Layer } from "effect" import { InstanceState } from "@/effect/instance-state" -import { disposeAllInstances, provideInstance, reloadTestInstance, tmpdirScoped } from "../fixture/fixture" +import { + disposeAllInstancesEffect, + provideInstanceEffect, + reloadInstance, + testInstanceStoreLayer, + tmpdirScoped, +} from "../fixture/fixture" import { testEffect } from "../lib/effect" -const it = testEffect(CrossSpawnSpawner.defaultLayer) +const it = testEffect(Layer.mergeAll(CrossSpawnSpawner.defaultLayer, testInstanceStoreLayer)) const access = (state: InstanceState.InstanceState, dir: string) => - InstanceState.get(state).pipe(provideInstance(dir)) + InstanceState.get(state).pipe(provideInstanceEffect(dir)) const tmpdirGitScoped = Effect.gen(function* () { const dir = yield* tmpdirScoped({ git: true }) @@ -17,10 +23,6 @@ const tmpdirGitScoped = Effect.gen(function* () { return dir }) -afterEach(async () => { - await disposeAllInstances() -}) - it.live("InstanceState caches values per directory", () => Effect.gen(function* () { const dir = yield* tmpdirScoped() @@ -68,7 +70,7 @@ it.live("InstanceState invalidates on reload", () => ) const a = yield* access(state, dir) - yield* Effect.promise(() => reloadTestInstance({ directory: dir })) + yield* reloadInstance({ directory: dir }) const b = yield* access(state, dir) expect(a).not.toBe(b) @@ -93,7 +95,7 @@ it.live("InstanceState invalidates on disposeAll", () => yield* access(state, one) yield* access(state, two) - yield* Effect.promise(disposeAllInstances) + yield* disposeAllInstancesEffect expect(seen.sort()).toEqual([one, two].sort()) }), @@ -125,8 +127,8 @@ it.live("InstanceState.get reads the current directory lazily", () => } yield* Effect.gen(function* () { - const a = yield* Test.use((svc) => svc.get()).pipe(provideInstance(one)) - const b = yield* Test.use((svc) => svc.get()).pipe(provideInstance(two)) + const a = yield* Test.use((svc) => svc.get()).pipe(provideInstanceEffect(one)) + const b = yield* Test.use((svc) => svc.get()).pipe(provideInstanceEffect(two)) expect(a).toBe(one) expect(b).toBe(two) @@ -177,7 +179,7 @@ it.live("InstanceState preserves directory across async boundaries", () => yield* Effect.gen(function* () { const [a, b, c] = yield* Effect.all( - [one, two, three].map((dir) => Test.use((svc) => svc.get()).pipe(provideInstance(dir))), + [one, two, three].map((dir) => Test.use((svc) => svc.get()).pipe(provideInstanceEffect(dir))), { concurrency: "unbounded" }, ) @@ -224,7 +226,7 @@ it.live("InstanceState survives high-contention concurrent access", () => yield* Effect.gen(function* () { const results = yield* Effect.all( - dirs.map((dir) => Test.use((svc) => svc.get()).pipe(provideInstance(dir))), + dirs.map((dir) => Test.use((svc) => svc.get()).pipe(provideInstanceEffect(dir))), { concurrency: "unbounded" }, ) @@ -263,19 +265,19 @@ it.live("InstanceState correct after interleaved init and dispose", () => } yield* Effect.gen(function* () { - const a = yield* Test.use((svc) => svc.get()).pipe(provideInstance(one)) + const a = yield* Test.use((svc) => svc.get()).pipe(provideInstanceEffect(one)) expect(a).toBe(one) const [, b] = yield* Effect.all( [ - Effect.promise(() => reloadTestInstance({ directory: one })), - Test.use((svc) => svc.get()).pipe(provideInstance(two)), + reloadInstance({ directory: one }), + Test.use((svc) => svc.get()).pipe(provideInstanceEffect(two)), ], { concurrency: "unbounded" }, ) expect(b).toBe(two) - const c = yield* Test.use((svc) => svc.get()).pipe(provideInstance(one)) + const c = yield* Test.use((svc) => svc.get()).pipe(provideInstanceEffect(one)) expect(c).toBe(one) }).pipe(Effect.provide(Test.layer)) }), @@ -343,9 +345,9 @@ it.live("InstanceState survives deferred resume from the same instance context", yield* Effect.gen(function* () { const gate = yield* Deferred.make() - const fiber = yield* Test.use((svc) => svc.get(gate)).pipe(provideInstance(dir), Effect.forkScoped) + const fiber = yield* Test.use((svc) => svc.get(gate)).pipe(provideInstanceEffect(dir), Effect.forkScoped) - yield* Deferred.succeed(gate, undefined).pipe(provideInstance(dir)) + yield* Deferred.succeed(gate, undefined).pipe(provideInstanceEffect(dir)) const exit = yield* Fiber.await(fiber) expect(Exit.isSuccess(exit)).toBe(true) @@ -380,7 +382,7 @@ it.live("InstanceState survives deferred resume outside ALS when InstanceRef is yield* Effect.gen(function* () { const gate = yield* Deferred.make() - const fiber = yield* Test.use((svc) => svc.get(gate)).pipe(provideInstance(dir), Effect.forkScoped) + const fiber = yield* Test.use((svc) => svc.get(gate)).pipe(provideInstanceEffect(dir), Effect.forkScoped) yield* Deferred.succeed(gate, undefined) const exit = yield* Fiber.await(fiber) diff --git a/packages/opencode/test/fixture/fixture.ts b/packages/opencode/test/fixture/fixture.ts index 51b6294975..9653e5888d 100644 --- a/packages/opencode/test/fixture/fixture.ts +++ b/packages/opencode/test/fixture/fixture.ts @@ -17,8 +17,9 @@ import { InstanceStore } from "../../src/project/instance-store" import { TestLLMServer } from "../lib/llm-server" const noopBootstrap = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void })) +export const testInstanceStoreLayer = InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrap)) const testInstanceRuntime = ManagedRuntime.make( - InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrap), Layer.provideMerge(Observability.layer)), + testInstanceStoreLayer.pipe(Layer.provideMerge(Observability.layer)), ) const runTestInstanceStore = (fn: (store: InstanceStore.Interface) => Effect.Effect) => @@ -165,6 +166,16 @@ export const provideInstance = }), ) +export const provideInstanceEffect = + (directory: string) => + (self: Effect.Effect): Effect.Effect => + InstanceStore.Service.use((store) => store.provide({ directory }, self)) + +export const reloadInstance = (input: InstanceStore.LoadInput) => + InstanceStore.Service.use((store) => store.reload(input)) + +export const disposeAllInstancesEffect = InstanceStore.Service.use((store) => store.disposeAll()) + export function provideTmpdirInstance( self: (path: string) => Effect.Effect, options?: { git?: boolean; config?: Partial }, @@ -195,11 +206,9 @@ export const withTmpdirInstance = (self: Effect.Effect) => Effect.gen(function* () { const directory = yield* tmpdirScoped(options) - return yield* InstanceStore.Service.use((store) => - store.provide({ directory }, self.pipe(Effect.provideService(TestInstance, { directory }))), - ) + return yield* self.pipe(Effect.provideService(TestInstance, { directory }), provideInstanceEffect(directory)) }).pipe( - Effect.provide(InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrap))), + Effect.provide(testInstanceStoreLayer), Effect.provide(CrossSpawnSpawner.defaultLayer), )