fix flaky plugin tests (no mock.module for bun) (#19445)

pull/19449/head^2
Sebastian 2026-03-27 20:55:03 +01:00 committed by GitHub
parent e973bbf54a
commit ff13524a53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 60 additions and 80 deletions

View File

@ -1,7 +1,15 @@
import { 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,81 +17,43 @@ 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
},
})
spyOn(Rpc, "client").mockImplementation(() => ({
call: async () => ({ url: "http://127.0.0.1" }) as never,
on: () => () => {},
}))
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: <T>(input: Promise<T>) => input,
}))
mock.module("@/cli/network", () => ({
withNetworkOptions: <T>(input: T) => input,
resolveNetworkOptions: async () => ({
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: () => ({}),
},
}))
mock.module("@/project/instance", () => ({
Instance: {
provide: async (input: { directory: string; fn: () => Promise<unknown> | unknown }) => {
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", () => {
afterEach(() => {
mock.restore()
})
async function call(project?: string) {
const { TuiThreadCommand } = await import("../../../src/cli/cmd/tui/thread")
const args: Parameters<NonNullable<typeof TuiThreadCommand.handler>>[0] = {
@ -107,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

View File

@ -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) => {
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,6 +886,9 @@ test("serializes config dependency installs across dirs", async () => {
const online = spyOn(Network, "online").mockReturnValue(false)
const run = spyOn(BunProc, "run").mockImplementation(async (_cmd, opts) => {
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)
@ -890,13 +896,16 @@ test("serializes config dependency installs across dirs", async () => {
start()
await gate
}
}
const mod = path.join(opts?.cwd ?? "", "node_modules", "@opencode-ai", "plugin")
await fs.mkdir(mod, { recursive: true })
await Filesystem.write(
path.join(mod, "package.json"),
JSON.stringify({ name: "@opencode-ai/plugin", version: "1.0.0" }),
)
if (hit) {
open -= 1
}
return {
code: 0,
stdout: Buffer.alloc(0),