fix flaky plugin tests (no mock.module for bun) (#19445)
parent
e973bbf54a
commit
ff13524a53
|
|
@ -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 fs from "fs/promises"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { tmpdir } from "../../fixture/fixture"
|
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 stop = new Error("stop")
|
||||||
const seen = {
|
const seen = {
|
||||||
|
|
@ -9,81 +17,43 @@ const seen = {
|
||||||
inst: [] as string[],
|
inst: [] as string[],
|
||||||
}
|
}
|
||||||
|
|
||||||
mock.module("../../../src/cli/cmd/tui/app", () => ({
|
function setup() {
|
||||||
tui: async (input: { directory: string }) => {
|
// Intentionally avoid mock.module() here: Bun keeps module overrides in cache
|
||||||
seen.tui.push(input.directory)
|
// 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
|
throw stop
|
||||||
},
|
})
|
||||||
}))
|
spyOn(Rpc, "client").mockImplementation(() => ({
|
||||||
|
call: async () => ({ url: "http://127.0.0.1" }) as never,
|
||||||
mock.module("@/util/rpc", () => ({
|
on: () => () => {},
|
||||||
Rpc: {
|
}))
|
||||||
client: () => ({
|
spyOn(UI, "error").mockImplementation(() => {})
|
||||||
call: async () => ({ url: "http://127.0.0.1" }),
|
spyOn(Timeout, "withTimeout").mockImplementation((input) => input)
|
||||||
on: () => {},
|
spyOn(Network, "resolveNetworkOptions").mockResolvedValue({
|
||||||
}),
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
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 () => ({
|
|
||||||
mdns: false,
|
mdns: false,
|
||||||
port: 0,
|
port: 0,
|
||||||
hostname: "127.0.0.1",
|
hostname: "127.0.0.1",
|
||||||
}),
|
mdnsDomain: "opencode.local",
|
||||||
}))
|
cors: [],
|
||||||
|
})
|
||||||
mock.module("../../../src/cli/cmd/tui/win32", () => ({
|
spyOn(Win32, "win32DisableProcessedInput").mockImplementation(() => {})
|
||||||
win32DisableProcessedInput: () => {},
|
spyOn(Win32, "win32InstallCtrlCGuard").mockReturnValue(undefined)
|
||||||
win32InstallCtrlCGuard: () => undefined,
|
spyOn(TuiConfig, "get").mockResolvedValue({})
|
||||||
}))
|
spyOn(Instance, "provide").mockImplementation(async (input) => {
|
||||||
|
seen.inst.push(input.directory)
|
||||||
mock.module("@/config/tui", () => ({
|
return input.fn()
|
||||||
TuiConfig: {
|
})
|
||||||
get: () => ({}),
|
}
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
mock.module("@/project/instance", () => ({
|
|
||||||
Instance: {
|
|
||||||
provide: async (input: { directory: string; fn: () => Promise<unknown> | unknown }) => {
|
|
||||||
seen.inst.push(input.directory)
|
|
||||||
return input.fn()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe("tui thread", () => {
|
describe("tui thread", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
mock.restore()
|
||||||
|
})
|
||||||
|
|
||||||
async function call(project?: string) {
|
async function call(project?: string) {
|
||||||
const { TuiThreadCommand } = await import("../../../src/cli/cmd/tui/thread")
|
const { TuiThreadCommand } = await import("../../../src/cli/cmd/tui/thread")
|
||||||
const args: Parameters<NonNullable<typeof TuiThreadCommand.handler>>[0] = {
|
const args: Parameters<NonNullable<typeof TuiThreadCommand.handler>>[0] = {
|
||||||
|
|
@ -107,6 +77,7 @@ describe("tui thread", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function check(project?: string) {
|
async function check(project?: string) {
|
||||||
|
setup()
|
||||||
await using tmp = await tmpdir({ git: true })
|
await using tmp = await tmpdir({ git: true })
|
||||||
const cwd = process.cwd()
|
const cwd = process.cwd()
|
||||||
const pwd = process.env.PWD
|
const pwd = process.env.PWD
|
||||||
|
|
|
||||||
|
|
@ -821,9 +821,12 @@ test("dedupes concurrent config dependency installs for the same dir", async ()
|
||||||
})
|
})
|
||||||
const online = spyOn(Network, "online").mockReturnValue(false)
|
const online = spyOn(Network, "online").mockReturnValue(false)
|
||||||
const run = spyOn(BunProc, "run").mockImplementation(async (_cmd, opts) => {
|
const run = spyOn(BunProc, "run").mockImplementation(async (_cmd, opts) => {
|
||||||
calls += 1
|
const hit = path.normalize(opts?.cwd ?? "") === path.normalize(dir)
|
||||||
start()
|
if (hit) {
|
||||||
await gate
|
calls += 1
|
||||||
|
start()
|
||||||
|
await gate
|
||||||
|
}
|
||||||
const mod = path.join(opts?.cwd ?? "", "node_modules", "@opencode-ai", "plugin")
|
const mod = path.join(opts?.cwd ?? "", "node_modules", "@opencode-ai", "plugin")
|
||||||
await fs.mkdir(mod, { recursive: true })
|
await fs.mkdir(mod, { recursive: true })
|
||||||
await Filesystem.write(
|
await Filesystem.write(
|
||||||
|
|
@ -883,12 +886,16 @@ test("serializes config dependency installs across dirs", async () => {
|
||||||
|
|
||||||
const online = spyOn(Network, "online").mockReturnValue(false)
|
const online = spyOn(Network, "online").mockReturnValue(false)
|
||||||
const run = spyOn(BunProc, "run").mockImplementation(async (_cmd, opts) => {
|
const run = spyOn(BunProc, "run").mockImplementation(async (_cmd, opts) => {
|
||||||
calls += 1
|
const cwd = path.normalize(opts?.cwd ?? "")
|
||||||
open += 1
|
const hit = cwd === path.normalize(a) || cwd === path.normalize(b)
|
||||||
peak = Math.max(peak, open)
|
if (hit) {
|
||||||
if (calls === 1) {
|
calls += 1
|
||||||
start()
|
open += 1
|
||||||
await gate
|
peak = Math.max(peak, open)
|
||||||
|
if (calls === 1) {
|
||||||
|
start()
|
||||||
|
await gate
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const mod = path.join(opts?.cwd ?? "", "node_modules", "@opencode-ai", "plugin")
|
const mod = path.join(opts?.cwd ?? "", "node_modules", "@opencode-ai", "plugin")
|
||||||
await fs.mkdir(mod, { recursive: true })
|
await fs.mkdir(mod, { recursive: true })
|
||||||
|
|
@ -896,7 +903,9 @@ test("serializes config dependency installs across dirs", async () => {
|
||||||
path.join(mod, "package.json"),
|
path.join(mod, "package.json"),
|
||||||
JSON.stringify({ name: "@opencode-ai/plugin", version: "1.0.0" }),
|
JSON.stringify({ name: "@opencode-ai/plugin", version: "1.0.0" }),
|
||||||
)
|
)
|
||||||
open -= 1
|
if (hit) {
|
||||||
|
open -= 1
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
code: 0,
|
code: 0,
|
||||||
stdout: Buffer.alloc(0),
|
stdout: Buffer.alloc(0),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue