Просмотр исходного кода

Migrate config .opencode file tests to instance fixtures (#28268)

Kit Langton 2 месяцев назад
Родитель
Сommit
996928b32a
2 измененных файлов с 130 добавлено и 176 удалено
  1. 105 152
      packages/opencode/test/config/config.test.ts
  2. 25 24
      perf/test-suite.md

+ 105 - 152
packages/opencode/test/config/config.test.ts

@@ -110,6 +110,8 @@ async function writeConfig(dir: string, config: object, name = "opencode.json")
 
 const writeConfigEffect = (dir: string, config: object, name = "opencode.json") =>
   Effect.promise(() => writeConfig(dir, config, name))
+const mkdirEffect = (dir: string) => Effect.promise(() => fs.mkdir(dir, { recursive: true }))
+const writeTextEffect = (file: string, content: string) => Effect.promise(() => Filesystem.write(file, content))
 
 function withProcessEnv<A, E, R>(key: string, value: string, effect: Effect.Effect<A, E, R>) {
   return Effect.acquireUseRelease(
@@ -671,205 +673,156 @@ it.instance("migrates mode field to agent field", () =>
   }),
 )
 
-test("loads config from .opencode directory", async () => {
-  await using tmp = await tmpdir({
-    init: async (dir) => {
-      const opencodeDir = path.join(dir, ".opencode")
-      await fs.mkdir(opencodeDir, { recursive: true })
-      const agentDir = path.join(opencodeDir, "agent")
-      await fs.mkdir(agentDir, { recursive: true })
-
-      await Filesystem.write(
-        path.join(agentDir, "test.md"),
-        `---
+it.instance("loads config from .opencode directory", () =>
+  Effect.gen(function* () {
+    const test = yield* TestInstance
+    yield* mkdirEffect(path.join(test.directory, ".opencode", "agent"))
+    yield* writeTextEffect(
+      path.join(test.directory, ".opencode", "agent", "test.md"),
+      `---
 model: test/model
 ---
 Test agent prompt`,
-      )
-    },
-  })
-  await withTestInstance({
-    directory: tmp.path,
-    fn: async (ctx) => {
-      const config = await load(ctx)
-      expect(config.agent?.["test"]).toEqual(
-        expect.objectContaining({
-          name: "test",
-          model: "test/model",
-          prompt: "Test agent prompt",
-        }),
-      )
-    },
-  })
-})
+    )
 
-test("agent markdown permission config preserves user key order", async () => {
-  await using tmp = await tmpdir({
-    init: async (dir) => {
-      const agentDir = path.join(dir, ".opencode", "agent")
-      await fs.mkdir(agentDir, { recursive: true })
+    const config = yield* Config.Service.use((svc) => svc.get())
+    expect(config.agent?.["test"]).toEqual(
+      expect.objectContaining({
+        name: "test",
+        model: "test/model",
+        prompt: "Test agent prompt",
+      }),
+    )
+  }),
+)
 
-      await Filesystem.write(
-        path.join(agentDir, "ordered.md"),
-        `---
+it.instance("agent markdown permission config preserves user key order", () =>
+  Effect.gen(function* () {
+    const test = yield* TestInstance
+    yield* mkdirEffect(path.join(test.directory, ".opencode", "agent"))
+    yield* writeTextEffect(
+      path.join(test.directory, ".opencode", "agent", "ordered.md"),
+      `---
 permission:
   bash: allow
   "*": deny
   edit: ask
 ---
 Ordered permissions`,
-      )
-    },
-  })
-  await withTestInstance({
-    directory: tmp.path,
-    fn: async (ctx) => {
-      const config = await load(ctx)
-      expect(Object.keys(config.agent?.ordered?.permission ?? {})).toEqual(["bash", "*", "edit"])
-    },
-  })
-})
-
-test("loads agents from .opencode/agents (plural)", async () => {
-  await using tmp = await tmpdir({
-    init: async (dir) => {
-      const opencodeDir = path.join(dir, ".opencode")
-      await fs.mkdir(opencodeDir, { recursive: true })
+    )
 
-      const agentsDir = path.join(opencodeDir, "agents")
-      await fs.mkdir(path.join(agentsDir, "nested"), { recursive: true })
+    const config = yield* Config.Service.use((svc) => svc.get())
+    expect(Object.keys(config.agent?.ordered?.permission ?? {})).toEqual(["bash", "*", "edit"])
+  }),
+)
 
-      await Filesystem.write(
-        path.join(agentsDir, "helper.md"),
-        `---
+it.instance("loads agents from .opencode/agents (plural)", () =>
+  Effect.gen(function* () {
+    const test = yield* TestInstance
+    yield* mkdirEffect(path.join(test.directory, ".opencode", "agents", "nested"))
+    yield* writeTextEffect(
+      path.join(test.directory, ".opencode", "agents", "helper.md"),
+      `---
 model: test/model
 mode: subagent
 ---
 Helper agent prompt`,
-      )
+    )
 
-      await Filesystem.write(
-        path.join(agentsDir, "nested", "child.md"),
-        `---
+    yield* writeTextEffect(
+      path.join(test.directory, ".opencode", "agents", "nested", "child.md"),
+      `---
 model: test/model
 mode: subagent
 ---
 Nested agent prompt`,
-      )
-    },
-  })
-
-  await withTestInstance({
-    directory: tmp.path,
-    fn: async (ctx) => {
-      const config = await load(ctx)
-
-      expect(config.agent?.["helper"]).toMatchObject({
-        name: "helper",
-        model: "test/model",
-        mode: "subagent",
-        prompt: "Helper agent prompt",
-      })
+    )
 
-      expect(config.agent?.["nested/child"]).toMatchObject({
-        name: "nested/child",
-        model: "test/model",
-        mode: "subagent",
-        prompt: "Nested agent prompt",
-      })
-    },
-  })
-})
+    const config = yield* Config.Service.use((svc) => svc.get())
 
-test("loads commands from .opencode/command (singular)", async () => {
-  await using tmp = await tmpdir({
-    init: async (dir) => {
-      const opencodeDir = path.join(dir, ".opencode")
-      await fs.mkdir(opencodeDir, { recursive: true })
+    expect(config.agent?.["helper"]).toMatchObject({
+      name: "helper",
+      model: "test/model",
+      mode: "subagent",
+      prompt: "Helper agent prompt",
+    })
 
-      const commandDir = path.join(opencodeDir, "command")
-      await fs.mkdir(path.join(commandDir, "nested"), { recursive: true })
+    expect(config.agent?.["nested/child"]).toMatchObject({
+      name: "nested/child",
+      model: "test/model",
+      mode: "subagent",
+      prompt: "Nested agent prompt",
+    })
+  }),
+)
 
-      await Filesystem.write(
-        path.join(commandDir, "hello.md"),
-        `---
+it.instance("loads commands from .opencode/command (singular)", () =>
+  Effect.gen(function* () {
+    const test = yield* TestInstance
+    yield* mkdirEffect(path.join(test.directory, ".opencode", "command", "nested"))
+    yield* writeTextEffect(
+      path.join(test.directory, ".opencode", "command", "hello.md"),
+      `---
 description: Test command
 ---
 Hello from singular command`,
-      )
+    )
 
-      await Filesystem.write(
-        path.join(commandDir, "nested", "child.md"),
-        `---
+    yield* writeTextEffect(
+      path.join(test.directory, ".opencode", "command", "nested", "child.md"),
+      `---
 description: Nested command
 ---
 Nested command template`,
-      )
-    },
-  })
-
-  await withTestInstance({
-    directory: tmp.path,
-    fn: async (ctx) => {
-      const config = await load(ctx)
-
-      expect(config.command?.["hello"]).toEqual({
-        description: "Test command",
-        template: "Hello from singular command",
-      })
+    )
 
-      expect(config.command?.["nested/child"]).toEqual({
-        description: "Nested command",
-        template: "Nested command template",
-      })
-    },
-  })
-})
+    const config = yield* Config.Service.use((svc) => svc.get())
 
-test("loads commands from .opencode/commands (plural)", async () => {
-  await using tmp = await tmpdir({
-    init: async (dir) => {
-      const opencodeDir = path.join(dir, ".opencode")
-      await fs.mkdir(opencodeDir, { recursive: true })
+    expect(config.command?.["hello"]).toEqual({
+      description: "Test command",
+      template: "Hello from singular command",
+    })
 
-      const commandsDir = path.join(opencodeDir, "commands")
-      await fs.mkdir(path.join(commandsDir, "nested"), { recursive: true })
+    expect(config.command?.["nested/child"]).toEqual({
+      description: "Nested command",
+      template: "Nested command template",
+    })
+  }),
+)
 
-      await Filesystem.write(
-        path.join(commandsDir, "hello.md"),
-        `---
+it.instance("loads commands from .opencode/commands (plural)", () =>
+  Effect.gen(function* () {
+    const test = yield* TestInstance
+    yield* mkdirEffect(path.join(test.directory, ".opencode", "commands", "nested"))
+    yield* writeTextEffect(
+      path.join(test.directory, ".opencode", "commands", "hello.md"),
+      `---
 description: Test command
 ---
 Hello from plural commands`,
-      )
+    )
 
-      await Filesystem.write(
-        path.join(commandsDir, "nested", "child.md"),
-        `---
+    yield* writeTextEffect(
+      path.join(test.directory, ".opencode", "commands", "nested", "child.md"),
+      `---
 description: Nested command
 ---
 Nested command template`,
-      )
-    },
-  })
+    )
 
-  await withTestInstance({
-    directory: tmp.path,
-    fn: async (ctx) => {
-      const config = await load(ctx)
+    const config = yield* Config.Service.use((svc) => svc.get())
 
-      expect(config.command?.["hello"]).toEqual({
-        description: "Test command",
-        template: "Hello from plural commands",
-      })
+    expect(config.command?.["hello"]).toEqual({
+      description: "Test command",
+      template: "Hello from plural commands",
+    })
 
-      expect(config.command?.["nested/child"]).toEqual({
-        description: "Nested command",
-        template: "Nested command template",
-      })
-    },
-  })
-})
+    expect(config.command?.["nested/child"]).toEqual({
+      description: "Nested command",
+      template: "Nested command template",
+    })
+  }),
+)
 
 it.instance("updates config and writes to file", () =>
   Effect.gen(function* () {

+ 25 - 24
perf/test-suite.md

@@ -51,30 +51,31 @@ Repeated setup work, long sleeps/timeouts, serial integration tests, filesystem/
 
 ## Hypothesis Loop
 
-| Hypothesis                                                                                                | Change                                                                                         | Before    | After   | Decision | Notes                                                                                                                        |
-| --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | --------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
-| Repeated full-suite runs are too expensive for discovery                                                  | Switched full-suite benchmark to one run and added per-file profiler                           | ~250s/run | pending | keep     | Bun has no slowest-test reporter in this version; profile files directly.                                                    |
-| Plugin install concurrency test spends time spawning more workers than needed to exercise lock contention | Reduced worker counts from 12/10/8 to 6/6/5; kept `holdMs: 30`                                 | 7.800s    | 6.204s  | keep     | Median from 3 targeted runs; still covers concurrent cross-process writes to server, server+tui, and existing json config.   |
-| `httpapi-listen` PTY route tests pay for git repositories they do not assert on                           | Removed `git: true` from temp dirs while keeping config setup                                  | 10.554s   | 7.818s  | keep     | Median from 3 targeted runs; HTTP routes, tickets, websocket upgrade, restart, and no-auth paths still pass.                 |
-| `workspace.waitForSync` timeout test waits the full production timeout                                    | Added optional timeout parameter defaulting to production timeout; timeout test uses 25ms      | 12.949s   | 8.305s  | keep     | Median from 3 targeted runs; production callers keep the 5000ms default.                                                     |
-| `config.test` waits after dependencies even though `.gitignore` is written synchronously                  | Removed obsolete 1000ms sleep from writable `OPENCODE_CONFIG_DIR` test                         | 10.270s   | 9.433s  | keep     | Median from 5 targeted runs because one run was noisy; simpler test and no fixed sleep.                                      |
-| SDK parity helpers create git repos for tests that only need files/config/session state                   | Changed `withProject` default to no git; explicit git init test still opts into no-git fixture | 8.011s    | 5.180s  | keep     | Median from 5 targeted runs because first run was cold/noisy.                                                                |
-| Provider plugin filter test waits on plugin dependency readiness setup                                    | Marked local plugin dependencies ready using the existing fixture helper                       | 7.543s    | 6.366s  | keep     | Median from 3 targeted runs; matches neighboring plugin provider test setup.                                                 |
-| HTTP provider tests generate local plugins without dependency-ready fixture state                         | Marked generated `.opencode` plugin fixtures dependency-ready                                  | 7.905s    | 2.980s  | keep     | Median from 3 targeted runs; avoids unrelated plugin dependency setup in route tests.                                        |
-| TUI plugin lifecycle timeout coverage waits the full production cleanup timeout                           | Added optional runtime dispose timeout override and used 25ms in the timeout test              | 7.330s    | 1.507s  | keep     | Median from 3 targeted runs; production default remains 5000ms.                                                              |
-| Skill tool test initializes git even though it only reads local skill files                               | Removed `git: true` from the temporary directory fixture                                       | 2.320s    | 1.425s  | keep     | Single targeted rerun; still exercises skill discovery, permission request, and bundled file output.                         |
-| Prompt shell semantics tests initialize git though they only assert shell/session behavior                | Removed `git: true` from shell-focused prompt fixtures while preserving config setup           | 26.930s   | 23.400s | keep     | Three targeted reruns passed after the change: 23.80s, 23.55s, 23.40s.                                                       |
-| Remaining prompt behavior tests mostly do not require repository state                                    | Removed git setup from safe loop/reference/error fixtures; restored shell queue/cancel cases   | 23.400s   | 19.610s | keep     | Safety review found shell runner readiness depends on git-backed setup in several tests; current single rerun passes.        |
-| Session processor effect tests do not require repository state                                            | Removed git setup from all processor-effect temp server fixtures                               | 12.500s   | 9.230s  | keep     | Two targeted reruns passed after the change: 9.61s, 9.23s.                                                                   |
-| HTTP listen PTY ticket tests restart the same listener topology twice                                     | Folded directory-scoped ticket regression into the broader unsafe-ticket test                  | 7.051s    | 6.170s  | keep     | Two targeted reruns passed after the change: 6.76s, 6.17s; still covers mint failure and successful same-directory upgrade.  |
-| File watcher readiness can write before async native subscriptions are active                             | Retried short readiness writes and accepted symlink-realpath HEAD events                       | failed    | 4.62s   | keep     | Three sequential focused watcher runs passed: 4.62s, 4.57s, 4.64s; full suite no longer failed in `watcher.test.ts`.         |
-| First provider config/env/filtering block can use Effect-aware instance fixtures                          | Migrated six `tmpdir` + `withTestInstance` cases to `it.instance`                              | 6.06s     | 6.07s   | keep     | Neutral timing, but removes manual config file writes and instance plumbing; use as the pattern for later provider slices.   |
-| Custom provider/model config cases can use Effect-aware instance fixtures                                 | Migrated three more config-heavy provider cases to `it.instance`                               | 6.07s     | 6.12s   | keep     | Neutral timing within noise, but continues removing manual config file writes on top of the first provider fixture PR.       |
-| Provider env precedence and model lookup cases can use Effect-aware instance fixtures                     | Migrated four more provider lookup/default-model cases to `it.instance`                        | 6.12s     | 6.36s   | keep     | Noisy 5-run median; kept as a small stacked cleanup slice but do not claim speedup from this migration.                      |
-| Simple config load cases can use Effect-aware instance fixtures                                           | Migrated JSON, shell, formatter, and lsp config load cases to `it.instance`                    | 14.18s    | 3.93s   | keep     | Three-run medians before/after; removes manual `tmpdir` + `withTestInstance` setup from the first simple config block.       |
-| Config template, file include, and simple agent cases can use Effect-aware instance fixtures              | Migrated JSONC, env/file substitution, invalid config, and agent config cases to `it.instance` | 1.87s     | 1.90s   | keep     | Stacked on the first config slice; neutral timing but removes more manual `tmpdir` + instance plumbing.                      |
-| Agent option, command, and legacy migration config cases can use Effect-aware instance fixtures           | Migrated agent variant, command, autoshare, and mode migration cases to `it.instance`          | 1.90s     | 1.83s   | keep     | Stacked on the config template slice; small neutral-to-positive timing and less manual setup.                                |
-| Local config update and directory cases can use Effect-aware instance fixtures                            | Migrated local `update` and `directories` cases to `it.instance`                               | 1.77s     | 1.71s   | keep     | Three-run medians; small positive/neutral timing, removes manual instance plumbing, and eliminates one existing unsafe cast. |
+| Hypothesis                                                                                                | Change                                                                                         | Before    | After   | Decision | Notes                                                                                                                          |
+| --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | --------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
+| Repeated full-suite runs are too expensive for discovery                                                  | Switched full-suite benchmark to one run and added per-file profiler                           | ~250s/run | pending | keep     | Bun has no slowest-test reporter in this version; profile files directly.                                                      |
+| Plugin install concurrency test spends time spawning more workers than needed to exercise lock contention | Reduced worker counts from 12/10/8 to 6/6/5; kept `holdMs: 30`                                 | 7.800s    | 6.204s  | keep     | Median from 3 targeted runs; still covers concurrent cross-process writes to server, server+tui, and existing json config.     |
+| `httpapi-listen` PTY route tests pay for git repositories they do not assert on                           | Removed `git: true` from temp dirs while keeping config setup                                  | 10.554s   | 7.818s  | keep     | Median from 3 targeted runs; HTTP routes, tickets, websocket upgrade, restart, and no-auth paths still pass.                   |
+| `workspace.waitForSync` timeout test waits the full production timeout                                    | Added optional timeout parameter defaulting to production timeout; timeout test uses 25ms      | 12.949s   | 8.305s  | keep     | Median from 3 targeted runs; production callers keep the 5000ms default.                                                       |
+| `config.test` waits after dependencies even though `.gitignore` is written synchronously                  | Removed obsolete 1000ms sleep from writable `OPENCODE_CONFIG_DIR` test                         | 10.270s   | 9.433s  | keep     | Median from 5 targeted runs because one run was noisy; simpler test and no fixed sleep.                                        |
+| SDK parity helpers create git repos for tests that only need files/config/session state                   | Changed `withProject` default to no git; explicit git init test still opts into no-git fixture | 8.011s    | 5.180s  | keep     | Median from 5 targeted runs because first run was cold/noisy.                                                                  |
+| Provider plugin filter test waits on plugin dependency readiness setup                                    | Marked local plugin dependencies ready using the existing fixture helper                       | 7.543s    | 6.366s  | keep     | Median from 3 targeted runs; matches neighboring plugin provider test setup.                                                   |
+| HTTP provider tests generate local plugins without dependency-ready fixture state                         | Marked generated `.opencode` plugin fixtures dependency-ready                                  | 7.905s    | 2.980s  | keep     | Median from 3 targeted runs; avoids unrelated plugin dependency setup in route tests.                                          |
+| TUI plugin lifecycle timeout coverage waits the full production cleanup timeout                           | Added optional runtime dispose timeout override and used 25ms in the timeout test              | 7.330s    | 1.507s  | keep     | Median from 3 targeted runs; production default remains 5000ms.                                                                |
+| Skill tool test initializes git even though it only reads local skill files                               | Removed `git: true` from the temporary directory fixture                                       | 2.320s    | 1.425s  | keep     | Single targeted rerun; still exercises skill discovery, permission request, and bundled file output.                           |
+| Prompt shell semantics tests initialize git though they only assert shell/session behavior                | Removed `git: true` from shell-focused prompt fixtures while preserving config setup           | 26.930s   | 23.400s | keep     | Three targeted reruns passed after the change: 23.80s, 23.55s, 23.40s.                                                         |
+| Remaining prompt behavior tests mostly do not require repository state                                    | Removed git setup from safe loop/reference/error fixtures; restored shell queue/cancel cases   | 23.400s   | 19.610s | keep     | Safety review found shell runner readiness depends on git-backed setup in several tests; current single rerun passes.          |
+| Session processor effect tests do not require repository state                                            | Removed git setup from all processor-effect temp server fixtures                               | 12.500s   | 9.230s  | keep     | Two targeted reruns passed after the change: 9.61s, 9.23s.                                                                     |
+| HTTP listen PTY ticket tests restart the same listener topology twice                                     | Folded directory-scoped ticket regression into the broader unsafe-ticket test                  | 7.051s    | 6.170s  | keep     | Two targeted reruns passed after the change: 6.76s, 6.17s; still covers mint failure and successful same-directory upgrade.    |
+| File watcher readiness can write before async native subscriptions are active                             | Retried short readiness writes and accepted symlink-realpath HEAD events                       | failed    | 4.62s   | keep     | Three sequential focused watcher runs passed: 4.62s, 4.57s, 4.64s; full suite no longer failed in `watcher.test.ts`.           |
+| First provider config/env/filtering block can use Effect-aware instance fixtures                          | Migrated six `tmpdir` + `withTestInstance` cases to `it.instance`                              | 6.06s     | 6.07s   | keep     | Neutral timing, but removes manual config file writes and instance plumbing; use as the pattern for later provider slices.     |
+| Custom provider/model config cases can use Effect-aware instance fixtures                                 | Migrated three more config-heavy provider cases to `it.instance`                               | 6.07s     | 6.12s   | keep     | Neutral timing within noise, but continues removing manual config file writes on top of the first provider fixture PR.         |
+| Provider env precedence and model lookup cases can use Effect-aware instance fixtures                     | Migrated four more provider lookup/default-model cases to `it.instance`                        | 6.12s     | 6.36s   | keep     | Noisy 5-run median; kept as a small stacked cleanup slice but do not claim speedup from this migration.                        |
+| Simple config load cases can use Effect-aware instance fixtures                                           | Migrated JSON, shell, formatter, and lsp config load cases to `it.instance`                    | 14.18s    | 3.93s   | keep     | Three-run medians before/after; removes manual `tmpdir` + `withTestInstance` setup from the first simple config block.         |
+| Config template, file include, and simple agent cases can use Effect-aware instance fixtures              | Migrated JSONC, env/file substitution, invalid config, and agent config cases to `it.instance` | 1.87s     | 1.90s   | keep     | Stacked on the first config slice; neutral timing but removes more manual `tmpdir` + instance plumbing.                        |
+| Agent option, command, and legacy migration config cases can use Effect-aware instance fixtures           | Migrated agent variant, command, autoshare, and mode migration cases to `it.instance`          | 1.90s     | 1.83s   | keep     | Stacked on the config template slice; small neutral-to-positive timing and less manual setup.                                  |
+| Local config update and directory cases can use Effect-aware instance fixtures                            | Migrated local `update` and `directories` cases to `it.instance`                               | 1.77s     | 1.71s   | keep     | Three-run medians; small positive/neutral timing, removes manual instance plumbing, and eliminates one existing unsafe cast.   |
+| `.opencode` agent and command file-loading cases can use Effect-aware instance fixtures                   | Migrated singular/plural agent and command markdown fixture cases to `it.instance`             | 7.21s     | 1.87s   | keep     | Parent baseline was noisy (7.42, 7.21, 2.83); after runs were stable at 1.87, 1.98, 1.83. Keep as cleanup with no broad claim. |
 
 ## Profiling Results