From 1168e3a51fc5cf8cc0683d5d5ad08842c7636268 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 28 Apr 2026 10:48:21 +0900 Subject: [PATCH] test(perf): add plugin init team-mode resume-defer benchmark --- ...plugin-init-team-mode-resume-defer.test.ts | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 src/__tests__/perf/plugin-init-team-mode-resume-defer.test.ts diff --git a/src/__tests__/perf/plugin-init-team-mode-resume-defer.test.ts b/src/__tests__/perf/plugin-init-team-mode-resume-defer.test.ts new file mode 100644 index 000000000..8f5d490a4 --- /dev/null +++ b/src/__tests__/perf/plugin-init-team-mode-resume-defer.test.ts @@ -0,0 +1,133 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs" +import { tmpdir } from "node:os" +import { join } from "node:path" + +import type { PluginInput } from "@opencode-ai/plugin" +import { describe, expect, it } from "bun:test" + +const HUNG_LEAD_SESSION_ID = "ses_999999999fffeeRegrTestHang0" + +function makeHangingClient(): { + hangCount: { value: number } + client: PluginInput["client"] +} { + const hangCount = { value: 0 } + const sessionGet = (..._unusedArgs: unknown[]): Promise => { + hangCount.value += 1 + return new Promise(() => {}) + } + const client = { + session: { + get: sessionGet, + }, + } as unknown as PluginInput["client"] + return { hangCount, client } +} + +function createPluginInput(directory: string, client: PluginInput["client"]): PluginInput { + return { + client, + project: { + id: `regr-${Date.now()}`, + worktree: directory, + time: { created: Date.now() }, + }, + directory, + worktree: directory, + serverUrl: new URL("http://localhost"), + $: Bun.$, + } +} + +async function importFreshPluginModule(): Promise<(typeof import("../../index"))["default"]> { + const token = `${Date.now()}-${Math.random()}` + return (await import(`../../index?regr=${token}`)).default +} + +function seedStaleActiveRuntime(omoBaseDir: string): void { + const teamRunId = "11111111-2222-3333-4444-555555555555" + const runtimeDir = join(omoBaseDir, "runtime", teamRunId) + mkdirSync(runtimeDir, { recursive: true }) + const runtimeState = { + version: 1, + teamRunId, + teamName: "regression-stale-active", + specSource: "user", + createdAt: Date.now(), + status: "active", + leadSessionId: HUNG_LEAD_SESSION_ID, + members: [ + { + name: "lead", + sessionId: HUNG_LEAD_SESSION_ID, + agentType: "leader", + status: "running", + pendingInjectedMessageIds: [], + }, + ], + shutdownRequests: [], + bounds: { + maxMembers: 8, + maxParallelMembers: 4, + maxMessagesPerRun: 10000, + maxWallClockMinutes: 120, + maxMemberTurns: 500, + }, + } + writeFileSync(join(runtimeDir, "state.json"), `${JSON.stringify(runtimeState, null, 2)}\n`) +} + +function seedTeamModeConfig(configDir: string, omoBaseDir: string): void { + mkdirSync(configDir, { recursive: true }) + const config = { + team_mode: { + enabled: true, + tmux_visualization: false, + base_dir: omoBaseDir, + }, + } + writeFileSync(join(configDir, "oh-my-openagent.json"), JSON.stringify(config, null, 2)) +} + +describe("plugin init defers team-mode resume", () => { + it("returns within budget even when session.get hangs forever", async () => { + // given a stale active team runtime that triggers resumeAllTeams -> session.get + const rootDirectory = mkdtempSync(join(tmpdir(), "regr-team-defer-")) + const projectDirectory = join(rootDirectory, "project") + const configDirectory = join(rootDirectory, "opencode-config") + const omoBaseDirectory = join(rootDirectory, "omo") + const previousConfigDirectory = process.env.OPENCODE_CONFIG_DIR + + mkdirSync(projectDirectory, { recursive: true }) + seedTeamModeConfig(configDirectory, omoBaseDirectory) + seedStaleActiveRuntime(omoBaseDirectory) + process.env.OPENCODE_CONFIG_DIR = configDirectory + + try { + const pluginModule = await importFreshPluginModule() + const { hangCount, client } = makeHangingClient() + const input = createPluginInput(projectDirectory, client) + + // when serverPlugin is called with a hanging session.get + const start = performance.now() + const initPromise = pluginModule.server(input, {}) + const timeoutPromise = new Promise<"timeout">((resolve) => { + globalThis.setTimeout(() => resolve("timeout"), 3000) + }) + const result = await Promise.race([initPromise, timeoutPromise]) + const elapsedMs = performance.now() - start + + // then plugin init completes; resume call (if it fired) is a deferred no-op against the hang + expect(result).not.toBe("timeout") + expect(elapsedMs).toBeLessThan(2000) + expect(hangCount.value).toBe(0) + } finally { + if (previousConfigDirectory === undefined) { + delete process.env.OPENCODE_CONFIG_DIR + } else { + process.env.OPENCODE_CONFIG_DIR = previousConfigDirectory + } + rmSync(rootDirectory, { recursive: true, force: true }) + } + }) +})