Skip to content

Commit ff13524

Browse files
authored
fix flaky plugin tests (no mock.module for bun) (anomalyco#19445)
1 parent e973bbf commit ff13524

2 files changed

Lines changed: 60 additions & 80 deletions

File tree

packages/opencode/test/cli/tui/thread.test.ts

Lines changed: 41 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,59 @@
1-
import { describe, expect, mock, test } from "bun:test"
1+
import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"
22
import fs from "fs/promises"
33
import path from "path"
44
import { tmpdir } from "../../fixture/fixture"
5+
import * as App from "../../../src/cli/cmd/tui/app"
6+
import { Rpc } from "../../../src/util/rpc"
7+
import { UI } from "../../../src/cli/ui"
8+
import * as Timeout from "../../../src/util/timeout"
9+
import * as Network from "../../../src/cli/network"
10+
import * as Win32 from "../../../src/cli/cmd/tui/win32"
11+
import { TuiConfig } from "../../../src/config/tui"
12+
import { Instance } from "../../../src/project/instance"
513

614
const stop = new Error("stop")
715
const seen = {
816
tui: [] as string[],
917
inst: [] as string[],
1018
}
1119

12-
mock.module("../../../src/cli/cmd/tui/app", () => ({
13-
tui: async (input: { directory: string }) => {
14-
seen.tui.push(input.directory)
20+
function setup() {
21+
// Intentionally avoid mock.module() here: Bun keeps module overrides in cache
22+
// and mock.restore() does not reset mock.module values. If this switches back
23+
// to module mocks, later suites can see mocked @/config/tui and fail (e.g.
24+
// plugin-loader tests expecting real TuiConfig.waitForDependencies). See:
25+
// https://github.com/oven-sh/bun/issues/7823 and #12823.
26+
spyOn(App, "tui").mockImplementation(async (input) => {
27+
if (input.directory) seen.tui.push(input.directory)
1528
throw stop
16-
},
17-
}))
18-
19-
mock.module("@/util/rpc", () => ({
20-
Rpc: {
21-
client: () => ({
22-
call: async () => ({ url: "http://127.0.0.1" }),
23-
on: () => {},
24-
}),
25-
},
26-
}))
27-
28-
mock.module("@/cli/ui", () => ({
29-
UI: {
30-
error: () => {},
31-
},
32-
}))
33-
34-
mock.module("@/util/log", () => ({
35-
Log: {
36-
init: async () => {},
37-
create: () => ({
38-
error: () => {},
39-
info: () => {},
40-
warn: () => {},
41-
debug: () => {},
42-
time: () => ({ stop: () => {} }),
43-
}),
44-
Default: {
45-
error: () => {},
46-
info: () => {},
47-
warn: () => {},
48-
debug: () => {},
49-
},
50-
},
51-
}))
52-
53-
mock.module("@/util/timeout", () => ({
54-
withTimeout: <T>(input: Promise<T>) => input,
55-
}))
56-
57-
mock.module("@/cli/network", () => ({
58-
withNetworkOptions: <T>(input: T) => input,
59-
resolveNetworkOptions: async () => ({
29+
})
30+
spyOn(Rpc, "client").mockImplementation(() => ({
31+
call: async () => ({ url: "http://127.0.0.1" }) as never,
32+
on: () => () => {},
33+
}))
34+
spyOn(UI, "error").mockImplementation(() => {})
35+
spyOn(Timeout, "withTimeout").mockImplementation((input) => input)
36+
spyOn(Network, "resolveNetworkOptions").mockResolvedValue({
6037
mdns: false,
6138
port: 0,
6239
hostname: "127.0.0.1",
63-
}),
64-
}))
65-
66-
mock.module("../../../src/cli/cmd/tui/win32", () => ({
67-
win32DisableProcessedInput: () => {},
68-
win32InstallCtrlCGuard: () => undefined,
69-
}))
70-
71-
mock.module("@/config/tui", () => ({
72-
TuiConfig: {
73-
get: () => ({}),
74-
},
75-
}))
76-
77-
mock.module("@/project/instance", () => ({
78-
Instance: {
79-
provide: async (input: { directory: string; fn: () => Promise<unknown> | unknown }) => {
80-
seen.inst.push(input.directory)
81-
return input.fn()
82-
},
83-
},
84-
}))
40+
mdnsDomain: "opencode.local",
41+
cors: [],
42+
})
43+
spyOn(Win32, "win32DisableProcessedInput").mockImplementation(() => {})
44+
spyOn(Win32, "win32InstallCtrlCGuard").mockReturnValue(undefined)
45+
spyOn(TuiConfig, "get").mockResolvedValue({})
46+
spyOn(Instance, "provide").mockImplementation(async (input) => {
47+
seen.inst.push(input.directory)
48+
return input.fn()
49+
})
50+
}
8551

8652
describe("tui thread", () => {
53+
afterEach(() => {
54+
mock.restore()
55+
})
56+
8757
async function call(project?: string) {
8858
const { TuiThreadCommand } = await import("../../../src/cli/cmd/tui/thread")
8959
const args: Parameters<NonNullable<typeof TuiThreadCommand.handler>>[0] = {
@@ -107,6 +77,7 @@ describe("tui thread", () => {
10777
}
10878

10979
async function check(project?: string) {
80+
setup()
11081
await using tmp = await tmpdir({ git: true })
11182
const cwd = process.cwd()
11283
const pwd = process.env.PWD

packages/opencode/test/config/config.test.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -821,9 +821,12 @@ test("dedupes concurrent config dependency installs for the same dir", async ()
821821
})
822822
const online = spyOn(Network, "online").mockReturnValue(false)
823823
const run = spyOn(BunProc, "run").mockImplementation(async (_cmd, opts) => {
824-
calls += 1
825-
start()
826-
await gate
824+
const hit = path.normalize(opts?.cwd ?? "") === path.normalize(dir)
825+
if (hit) {
826+
calls += 1
827+
start()
828+
await gate
829+
}
827830
const mod = path.join(opts?.cwd ?? "", "node_modules", "@opencode-ai", "plugin")
828831
await fs.mkdir(mod, { recursive: true })
829832
await Filesystem.write(
@@ -883,20 +886,26 @@ test("serializes config dependency installs across dirs", async () => {
883886

884887
const online = spyOn(Network, "online").mockReturnValue(false)
885888
const run = spyOn(BunProc, "run").mockImplementation(async (_cmd, opts) => {
886-
calls += 1
887-
open += 1
888-
peak = Math.max(peak, open)
889-
if (calls === 1) {
890-
start()
891-
await gate
889+
const cwd = path.normalize(opts?.cwd ?? "")
890+
const hit = cwd === path.normalize(a) || cwd === path.normalize(b)
891+
if (hit) {
892+
calls += 1
893+
open += 1
894+
peak = Math.max(peak, open)
895+
if (calls === 1) {
896+
start()
897+
await gate
898+
}
892899
}
893900
const mod = path.join(opts?.cwd ?? "", "node_modules", "@opencode-ai", "plugin")
894901
await fs.mkdir(mod, { recursive: true })
895902
await Filesystem.write(
896903
path.join(mod, "package.json"),
897904
JSON.stringify({ name: "@opencode-ai/plugin", version: "1.0.0" }),
898905
)
899-
open -= 1
906+
if (hit) {
907+
open -= 1
908+
}
900909
return {
901910
code: 0,
902911
stdout: Buffer.alloc(0),

0 commit comments

Comments
 (0)