app-runtime-logger.test.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. import { expect } from "bun:test"
  2. import { Context, Deferred, Effect, Fiber, Layer, Logger } from "effect"
  3. import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
  4. import { AppLayer } from "../../src/effect/app-runtime"
  5. import { EffectBridge } from "@/effect/bridge"
  6. import { InstanceRef } from "../../src/effect/instance-ref"
  7. import * as Observability from "@opencode-ai/core/observability"
  8. import { attach } from "../../src/effect/run-service"
  9. import { TestInstance } from "../fixture/fixture"
  10. import { testEffect } from "../lib/effect"
  11. const it = testEffect(CrossSpawnSpawner.defaultLayer)
  12. function check(loggers: ReadonlySet<Logger.Logger<unknown, any>>) {
  13. return {
  14. tracerLogger: loggers.has(Logger.tracerLogger),
  15. size: loggers.size,
  16. }
  17. }
  18. it.live("makeRuntime installs the observability logger", () =>
  19. Effect.gen(function* () {
  20. class Dummy extends Context.Service<Dummy, { readonly current: () => Effect.Effect<ReturnType<typeof check>> }>()(
  21. "@test/Dummy",
  22. ) {}
  23. const layer = Layer.effect(
  24. Dummy,
  25. Effect.gen(function* () {
  26. return Dummy.of({
  27. current: () => Effect.map(Effect.service(Logger.CurrentLoggers), check),
  28. })
  29. }),
  30. )
  31. const current = yield* Dummy.use((svc) => svc.current()).pipe(
  32. Effect.provide(Layer.provideMerge(layer, Observability.layer)),
  33. )
  34. expect(current.size).toBeGreaterThan(0)
  35. }),
  36. )
  37. it.live("AppLayer also installs the observability logger", () =>
  38. Effect.gen(function* () {
  39. const current = yield* Effect.map(Effect.service(Logger.CurrentLoggers), check).pipe(Effect.provide(AppLayer))
  40. expect(current.size).toBeGreaterThan(0)
  41. }),
  42. )
  43. it.instance(
  44. "attach preserves InstanceRef from the current fiber context",
  45. () =>
  46. Effect.gen(function* () {
  47. const test = yield* TestInstance
  48. const current = yield* attach(
  49. Effect.gen(function* () {
  50. return (yield* InstanceRef)?.directory
  51. }),
  52. )
  53. expect(current).toBe(test.directory)
  54. }),
  55. { git: true },
  56. )
  57. it.instance(
  58. "EffectBridge preserves logger and instance context across async boundaries",
  59. () =>
  60. Effect.gen(function* () {
  61. const test = yield* TestInstance
  62. const bridge = yield* EffectBridge.make()
  63. const started = yield* Deferred.make<void>()
  64. const fiber = yield* Effect.gen(function* () {
  65. yield* Deferred.succeed(started, undefined)
  66. return yield* Effect.promise(() =>
  67. Promise.resolve().then(() =>
  68. bridge.promise(
  69. Effect.gen(function* () {
  70. return {
  71. directory: (yield* InstanceRef)?.directory,
  72. ...check(yield* Effect.service(Logger.CurrentLoggers)),
  73. }
  74. }),
  75. ),
  76. ),
  77. )
  78. }).pipe(Effect.forkScoped)
  79. yield* Deferred.await(started)
  80. const result = yield* Fiber.join(fiber)
  81. expect(result.directory).toBe(test.directory)
  82. expect(result.size).toBeGreaterThan(0)
  83. }).pipe(Effect.provide(Observability.layer)),
  84. { git: true },
  85. )