From 5b27740ac581c13dd555979a15ec427ce3937667 Mon Sep 17 00:00:00 2001 From: Sebastian Herrlinger Date: Fri, 27 Mar 2026 19:30:56 +0100 Subject: [PATCH 1/4] attempt --- packages/opencode/test/cli/tui/thread.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/opencode/test/cli/tui/thread.test.ts b/packages/opencode/test/cli/tui/thread.test.ts index d3de7c3183d3..8ba535aae523 100644 --- a/packages/opencode/test/cli/tui/thread.test.ts +++ b/packages/opencode/test/cli/tui/thread.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, mock, test } from "bun:test" +import { afterAll, describe, expect, mock, test } from "bun:test" import fs from "fs/promises" import path from "path" import { tmpdir } from "../../fixture/fixture" @@ -84,6 +84,10 @@ mock.module("@/project/instance", () => ({ })) describe("tui thread", () => { + afterAll(() => { + mock.restore() + }) + async function call(project?: string) { const { TuiThreadCommand } = await import("../../../src/cli/cmd/tui/thread") const args: Parameters>[0] = { From 6895999b5817298c8390fdd42e3cc9b04d3625a4 Mon Sep 17 00:00:00 2001 From: Sebastian Herrlinger Date: Fri, 27 Mar 2026 19:39:13 +0100 Subject: [PATCH 2/4] meh --- packages/opencode/test/cli/tui/thread.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/opencode/test/cli/tui/thread.test.ts b/packages/opencode/test/cli/tui/thread.test.ts index 8ba535aae523..237073db0136 100644 --- a/packages/opencode/test/cli/tui/thread.test.ts +++ b/packages/opencode/test/cli/tui/thread.test.ts @@ -71,6 +71,7 @@ mock.module("../../../src/cli/cmd/tui/win32", () => ({ mock.module("@/config/tui", () => ({ TuiConfig: { get: () => ({}), + waitForDependencies: async () => {}, }, })) From 91ff7e939409afcb308a7d2c0bd0e75d16c1728a Mon Sep 17 00:00:00 2001 From: Sebastian Herrlinger Date: Fri, 27 Mar 2026 19:47:31 +0100 Subject: [PATCH 3/4] avoid mock.module() --- packages/opencode/test/cli/tui/thread.test.ts | 110 ++++++------------ 1 file changed, 38 insertions(+), 72 deletions(-) diff --git a/packages/opencode/test/cli/tui/thread.test.ts b/packages/opencode/test/cli/tui/thread.test.ts index 237073db0136..176c2575a308 100644 --- a/packages/opencode/test/cli/tui/thread.test.ts +++ b/packages/opencode/test/cli/tui/thread.test.ts @@ -1,7 +1,15 @@ -import { afterAll, describe, expect, mock, test } from "bun:test" +import { afterEach, describe, expect, mock, spyOn, test } from "bun:test" import fs from "fs/promises" import path from "path" import { tmpdir } from "../../fixture/fixture" +import * as App from "../../../src/cli/cmd/tui/app" +import { Rpc } from "../../../src/util/rpc" +import { UI } from "../../../src/cli/ui" +import * as Timeout from "../../../src/util/timeout" +import * as Network from "../../../src/cli/network" +import * as Win32 from "../../../src/cli/cmd/tui/win32" +import { TuiConfig } from "../../../src/config/tui" +import { Instance } from "../../../src/project/instance" const stop = new Error("stop") const seen = { @@ -9,83 +17,40 @@ const seen = { inst: [] as string[], } -mock.module("../../../src/cli/cmd/tui/app", () => ({ - tui: async (input: { directory: string }) => { - seen.tui.push(input.directory) +function setup() { + // Intentionally avoid mock.module() here: Bun keeps module overrides in cache + // and mock.restore() does not reset mock.module values. If this switches back + // to module mocks, later suites can see mocked @/config/tui and fail (e.g. + // plugin-loader tests expecting real TuiConfig.waitForDependencies). See: + // https://github.com/oven-sh/bun/issues/7823 and #12823. + spyOn(App, "tui").mockImplementation(async (input) => { + if (input.directory) seen.tui.push(input.directory) throw stop - }, -})) - -mock.module("@/util/rpc", () => ({ - Rpc: { - client: () => ({ - call: async () => ({ url: "http://127.0.0.1" }), - on: () => {}, - }), - }, -})) - -mock.module("@/cli/ui", () => ({ - UI: { - error: () => {}, - }, -})) - -mock.module("@/util/log", () => ({ - Log: { - init: async () => {}, - create: () => ({ - error: () => {}, - info: () => {}, - warn: () => {}, - debug: () => {}, - time: () => ({ stop: () => {} }), - }), - Default: { - error: () => {}, - info: () => {}, - warn: () => {}, - debug: () => {}, - }, - }, -})) - -mock.module("@/util/timeout", () => ({ - withTimeout: (input: Promise) => input, -})) - -mock.module("@/cli/network", () => ({ - withNetworkOptions: (input: T) => input, - resolveNetworkOptions: async () => ({ + }) + spyOn(Rpc, "client").mockImplementation(() => ({ + call: async () => ({ url: "http://127.0.0.1" }) as never, + on: () => () => {}, + })) + spyOn(UI, "error").mockImplementation(() => {}) + spyOn(Timeout, "withTimeout").mockImplementation((input) => input) + spyOn(Network, "resolveNetworkOptions").mockResolvedValue({ mdns: false, port: 0, hostname: "127.0.0.1", - }), -})) - -mock.module("../../../src/cli/cmd/tui/win32", () => ({ - win32DisableProcessedInput: () => {}, - win32InstallCtrlCGuard: () => undefined, -})) - -mock.module("@/config/tui", () => ({ - TuiConfig: { - get: () => ({}), - waitForDependencies: async () => {}, - }, -})) - -mock.module("@/project/instance", () => ({ - Instance: { - provide: async (input: { directory: string; fn: () => Promise | unknown }) => { - seen.inst.push(input.directory) - return input.fn() - }, - }, -})) + mdnsDomain: "opencode.local", + cors: [], + }) + spyOn(Win32, "win32DisableProcessedInput").mockImplementation(() => {}) + spyOn(Win32, "win32InstallCtrlCGuard").mockReturnValue(undefined) + spyOn(TuiConfig, "get").mockResolvedValue({}) + spyOn(Instance, "provide").mockImplementation(async (input) => { + seen.inst.push(input.directory) + return input.fn() + }) +} describe("tui thread", () => { - afterAll(() => { + afterEach(() => { mock.restore() }) @@ -112,6 +77,7 @@ describe("tui thread", () => { } async function check(project?: string) { + setup() await using tmp = await tmpdir({ git: true }) const cwd = process.cwd() const pwd = process.env.PWD From e0ed74d72f760b7d4cb3d9cb1372d1f441829cb9 Mon Sep 17 00:00:00 2001 From: Sebastian Herrlinger Date: Fri, 27 Mar 2026 20:45:32 +0100 Subject: [PATCH 4/4] maybe fix another --- packages/opencode/test/config/config.test.ts | 29 +++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index aa49aa4bd50f..ea0a54520004 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -821,9 +821,12 @@ test("dedupes concurrent config dependency installs for the same dir", async () }) const online = spyOn(Network, "online").mockReturnValue(false) const run = spyOn(BunProc, "run").mockImplementation(async (_cmd, opts) => { - calls += 1 - start() - await gate + const hit = path.normalize(opts?.cwd ?? "") === path.normalize(dir) + if (hit) { + calls += 1 + start() + await gate + } const mod = path.join(opts?.cwd ?? "", "node_modules", "@opencode-ai", "plugin") await fs.mkdir(mod, { recursive: true }) await Filesystem.write( @@ -883,12 +886,16 @@ test("serializes config dependency installs across dirs", async () => { const online = spyOn(Network, "online").mockReturnValue(false) const run = spyOn(BunProc, "run").mockImplementation(async (_cmd, opts) => { - calls += 1 - open += 1 - peak = Math.max(peak, open) - if (calls === 1) { - start() - await gate + const cwd = path.normalize(opts?.cwd ?? "") + const hit = cwd === path.normalize(a) || cwd === path.normalize(b) + if (hit) { + calls += 1 + open += 1 + peak = Math.max(peak, open) + if (calls === 1) { + start() + await gate + } } const mod = path.join(opts?.cwd ?? "", "node_modules", "@opencode-ai", "plugin") await fs.mkdir(mod, { recursive: true }) @@ -896,7 +903,9 @@ test("serializes config dependency installs across dirs", async () => { path.join(mod, "package.json"), JSON.stringify({ name: "@opencode-ai/plugin", version: "1.0.0" }), ) - open -= 1 + if (hit) { + open -= 1 + } return { code: 0, stdout: Buffer.alloc(0),