fix full-suite isolation regressions

This commit is contained in:
YeonGyu-Kim
2026-05-07 17:47:53 +09:00
parent 102b5f96e7
commit ee938aa097
62 changed files with 1238 additions and 899 deletions
@@ -1,14 +1,11 @@
/// <reference types="bun-types" />
import { beforeEach, describe, expect, it, mock } from "bun:test"
import type { TmuxConfig } from "../../../config/schema"
import type { TmuxCommandResult } from "../runner"
const paneSpawnSpecifier = import.meta.resolve("./pane-spawn")
const environmentSpecifier = import.meta.resolve("./environment")
const loggerSpecifier = import.meta.resolve("../../logger")
const runnerSpecifier = import.meta.resolve("../runner")
const serverHealthSpecifier = import.meta.resolve("./server-health")
const tmuxPathResolverSpecifier = import.meta.resolve("../../../tools/interactive-bash/tmux-path-resolver")
const enabledTmuxConfig = {
enabled: true,
@@ -28,7 +25,7 @@ const runTmuxCommandMock = mock(async (): Promise<TmuxCommandResult> => ({
}))
const isInsideTmuxMock = mock((): boolean => true)
const isServerRunningMock = mock(async (): Promise<boolean> => true)
const getTmuxPathMock = mock(async (): Promise<string | undefined> => "sh")
const getTmuxPathMock = mock(async (): Promise<string | null> => "sh")
const logMock = mock(() => undefined)
function toStringArray(value: unknown): string[] {
@@ -64,23 +61,24 @@ function getSplitWindowCommand(): string {
return splitCommand
}
function createDeps(): NonNullable<Parameters<typeof import("./pane-spawn").spawnTmuxPane>[7]> {
return {
log: logMock,
runTmuxCommand: runTmuxCommandMock,
isInsideTmux: isInsideTmuxMock,
isServerRunning: isServerRunningMock,
getTmuxPath: getTmuxPathMock,
}
}
async function loadSpawnTmuxPane(): Promise<typeof import("./pane-spawn").spawnTmuxPane> {
const module = await import(`${paneSpawnSpecifier}?test=${crypto.randomUUID()}`)
return module.spawnTmuxPane
}
function registerModuleMocks(): void {
mock.module(environmentSpecifier, () => ({ isInsideTmux: isInsideTmuxMock }))
mock.module(loggerSpecifier, () => ({ log: logMock }))
mock.module(runnerSpecifier, () => ({ runTmuxCommand: runTmuxCommandMock }))
mock.module(serverHealthSpecifier, () => ({ isServerRunning: isServerRunningMock }))
mock.module(tmuxPathResolverSpecifier, () => ({ getTmuxPath: getTmuxPathMock }))
}
describe("spawnTmuxPane runner integration", () => {
beforeEach(() => {
mock.restore()
registerModuleMocks()
runTmuxCommandMock.mockClear()
isInsideTmuxMock.mockClear()
isServerRunningMock.mockClear()
@@ -109,7 +107,7 @@ describe("spawnTmuxPane runner integration", () => {
const directory = "/tmp/omo-project/(pane)"
// when
const result = await spawnTmuxPane("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", directory, "%0")
const result = await spawnTmuxPane("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", directory, "%0", "-h", createDeps())
// then
const firstCall = getRunTmuxCommandCall(0)
@@ -125,7 +123,7 @@ describe("spawnTmuxPane runner integration", () => {
const spawnTmuxPane = await loadSpawnTmuxPane()
// when
await spawnTmuxPane("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path with spaces/here", "%0")
await spawnTmuxPane("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path with spaces/here", "%0", "-h", createDeps())
// then
expect(getSplitWindowCommand()).toContain("--dir '/path with spaces/here'")
@@ -136,7 +134,7 @@ describe("spawnTmuxPane runner integration", () => {
const spawnTmuxPane = await loadSpawnTmuxPane()
// when
await spawnTmuxPane("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "", "%0")
await spawnTmuxPane("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "", "%0", "-h", createDeps())
// then
expect(getSplitWindowCommand()).toContain(`--dir '${process.cwd()}'`)
@@ -147,7 +145,7 @@ describe("spawnTmuxPane runner integration", () => {
const spawnTmuxPane = await loadSpawnTmuxPane()
// when
await spawnTmuxPane("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path/with'quote", "%0")
await spawnTmuxPane("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path/with'quote", "%0", "-h", createDeps())
// then
expect(getSplitWindowCommand()).toContain("--dir '/path/with'\\''quote'")
+31 -7
View File
@@ -1,11 +1,36 @@
import type { TmuxConfig } from "../../../config/schema"
import { getTmuxPath } from "../../../tools/interactive-bash/tmux-path-resolver"
import type { SpawnPaneResult } from "../types"
import type { runTmuxCommand as RunTmuxCommand } from "../runner"
import type { SplitDirection } from "./environment"
import { isInsideTmux } from "./environment"
import { isServerRunning } from "./server-health"
import { shellSingleQuote } from "../../shell-env"
type SpawnTmuxPaneDeps = {
log: (message: string, data?: unknown) => void
runTmuxCommand: typeof RunTmuxCommand
isInsideTmux: typeof isInsideTmux
isServerRunning: typeof isServerRunning
getTmuxPath: typeof getTmuxPath
}
async function resolveSpawnTmuxPaneDeps(deps?: Partial<SpawnTmuxPaneDeps>): Promise<SpawnTmuxPaneDeps> {
const [{ log }, { runTmuxCommand }] = await Promise.all([
import("../../logger"),
import("../runner"),
])
return {
log,
runTmuxCommand,
isInsideTmux,
isServerRunning,
getTmuxPath,
...deps,
}
}
export async function spawnTmuxPane(
sessionId: string,
description: string,
@@ -14,11 +39,10 @@ export async function spawnTmuxPane(
directory: string,
targetPaneId?: string,
splitDirection: SplitDirection = "-h",
depsInput?: Partial<SpawnTmuxPaneDeps>,
): Promise<SpawnPaneResult> {
const [{ log }, { runTmuxCommand }] = await Promise.all([
import("../../logger"),
import("../runner"),
])
const deps = await resolveSpawnTmuxPaneDeps(depsInput)
const { log, runTmuxCommand } = deps
log("[spawnTmuxPane] called", {
sessionId,
@@ -33,18 +57,18 @@ export async function spawnTmuxPane(
log("[spawnTmuxPane] SKIP: config.enabled is false")
return { success: false }
}
if (!isInsideTmux()) {
if (!deps.isInsideTmux()) {
log("[spawnTmuxPane] SKIP: not inside tmux", { TMUX: process.env.TMUX })
return { success: false }
}
const serverRunning = await isServerRunning(serverUrl)
const serverRunning = await deps.isServerRunning(serverUrl)
if (!serverRunning) {
log("[spawnTmuxPane] SKIP: server not running", { serverUrl })
return { success: false }
}
const tmux = await getTmuxPath()
const tmux = await deps.getTmuxPath()
if (!tmux) {
log("[spawnTmuxPane] SKIP: tmux not found")
return { success: false }
@@ -4,11 +4,6 @@ import type { TmuxConfig } from "../../../config/schema"
import type { TmuxCommandResult } from "../runner"
const sessionSpawnSpecifier = import.meta.resolve("./session-spawn")
const environmentSpecifier = import.meta.resolve("./environment")
const loggerSpecifier = import.meta.resolve("../../logger")
const runnerSpecifier = import.meta.resolve("../runner")
const serverHealthSpecifier = import.meta.resolve("./server-health")
const tmuxPathResolverSpecifier = import.meta.resolve("../../../tools/interactive-bash/tmux-path-resolver")
const enabledTmuxConfig = {
enabled: true,
@@ -28,7 +23,7 @@ const runTmuxCommandMock = mock(async (): Promise<TmuxCommandResult> => ({
}))
const isInsideTmuxMock = mock((): boolean => true)
const isServerRunningMock = mock(async (): Promise<boolean> => true)
const getTmuxPathMock = mock(async (): Promise<string | undefined> => "sh")
const getTmuxPathMock = mock(async (): Promise<string | null> => "sh")
const logMock = mock(() => undefined)
function toStringArray(value: unknown): string[] {
@@ -64,23 +59,24 @@ function getSpawnCommand(): string {
return newSessionCommand
}
function createDeps(): NonNullable<Parameters<typeof import("./session-spawn").spawnTmuxSession>[6]> {
return {
log: logMock,
runTmuxCommand: runTmuxCommandMock,
isInsideTmux: isInsideTmuxMock,
isServerRunning: isServerRunningMock,
getTmuxPath: getTmuxPathMock,
}
}
async function loadSpawnTmuxSession(): Promise<typeof import("./session-spawn").spawnTmuxSession> {
const module = await import(`${sessionSpawnSpecifier}?test=${crypto.randomUUID()}`)
return module.spawnTmuxSession
}
function registerModuleMocks(): void {
mock.module(environmentSpecifier, () => ({ isInsideTmux: isInsideTmuxMock }))
mock.module(loggerSpecifier, () => ({ log: logMock }))
mock.module(runnerSpecifier, () => ({ runTmuxCommand: runTmuxCommandMock }))
mock.module(serverHealthSpecifier, () => ({ isServerRunning: isServerRunningMock }))
mock.module(tmuxPathResolverSpecifier, () => ({ getTmuxPath: getTmuxPathMock }))
}
describe("spawnTmuxSession runner integration", () => {
beforeEach(() => {
mock.restore()
registerModuleMocks()
runTmuxCommandMock.mockClear()
isInsideTmuxMock.mockClear()
isServerRunningMock.mockClear()
@@ -111,7 +107,7 @@ describe("spawnTmuxSession runner integration", () => {
const directory = "/tmp/omo-project/(session)"
// when
const result = await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", directory, "%0")
const result = await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", directory, "%0", createDeps())
// then
const displayCall = getRunTmuxCommandCall(0)
@@ -134,7 +130,7 @@ describe("spawnTmuxSession runner integration", () => {
const spawnTmuxSession = await loadSpawnTmuxSession()
// when
await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path with spaces/here", "%0")
await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path with spaces/here", "%0", createDeps())
// then
expect(getSpawnCommand()).toContain("--dir '/path with spaces/here'")
@@ -145,7 +141,7 @@ describe("spawnTmuxSession runner integration", () => {
const spawnTmuxSession = await loadSpawnTmuxSession()
// when
await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "", "%0")
await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "", "%0", createDeps())
// then
expect(getSpawnCommand()).toContain(`--dir '${process.cwd()}'`)
@@ -156,7 +152,7 @@ describe("spawnTmuxSession runner integration", () => {
const spawnTmuxSession = await loadSpawnTmuxSession()
// when
await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path/with'quote", "%0")
await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path/with'quote", "%0", createDeps())
// then
expect(getSpawnCommand()).toContain("--dir '/path/with'\\''quote'")
+30 -7
View File
@@ -8,6 +8,30 @@ import { shellSingleQuote } from "../../shell-env"
const ISOLATED_SESSION_NAME_PREFIX = "omo-agents"
type SpawnTmuxSessionDeps = {
log: (message: string, data?: unknown) => void
runTmuxCommand: typeof RunTmuxCommand
isInsideTmux: typeof isInsideTmux
isServerRunning: typeof isServerRunning
getTmuxPath: typeof getTmuxPath
}
async function resolveSpawnTmuxSessionDeps(deps?: Partial<SpawnTmuxSessionDeps>): Promise<SpawnTmuxSessionDeps> {
const [{ log }, { runTmuxCommand }] = await Promise.all([
import("../../logger"),
import("../runner"),
])
return {
log,
runTmuxCommand,
isInsideTmux,
isServerRunning,
getTmuxPath,
...deps,
}
}
export function getIsolatedSessionName(pid: number = process.pid): string {
return `${ISOLATED_SESSION_NAME_PREFIX}-${pid}`
}
@@ -39,11 +63,10 @@ export async function spawnTmuxSession(
serverUrl: string,
directory: string,
sourcePaneId?: string,
depsInput?: Partial<SpawnTmuxSessionDeps>,
): Promise<SpawnPaneResult> {
const [{ log }, { runTmuxCommand }] = await Promise.all([
import("../../logger"),
import("../runner"),
])
const deps = await resolveSpawnTmuxSessionDeps(depsInput)
const { log, runTmuxCommand } = deps
log("[spawnTmuxSession] called", {
sessionId,
@@ -56,18 +79,18 @@ export async function spawnTmuxSession(
log("[spawnTmuxSession] SKIP: config.enabled is false")
return { success: false }
}
if (!isInsideTmux()) {
if (!deps.isInsideTmux()) {
log("[spawnTmuxSession] SKIP: not inside tmux", { TMUX: process.env.TMUX })
return { success: false }
}
const serverRunning = await isServerRunning(serverUrl)
const serverRunning = await deps.isServerRunning(serverUrl)
if (!serverRunning) {
log("[spawnTmuxSession] SKIP: server not running", { serverUrl })
return { success: false }
}
const tmux = await getTmuxPath()
const tmux = await deps.getTmuxPath()
if (!tmux) {
log("[spawnTmuxSession] SKIP: tmux not found")
return { success: false }
+15 -19
View File
@@ -4,11 +4,6 @@ import type { TmuxConfig } from "../../../config/schema"
import type { TmuxCommandResult } from "../runner"
const windowSpawnSpecifier = import.meta.resolve("./window-spawn")
const environmentSpecifier = import.meta.resolve("./environment")
const loggerSpecifier = import.meta.resolve("../../logger")
const runnerSpecifier = import.meta.resolve("../runner")
const serverHealthSpecifier = import.meta.resolve("./server-health")
const tmuxPathResolverSpecifier = import.meta.resolve("../../../tools/interactive-bash/tmux-path-resolver")
const enabledTmuxConfig = {
enabled: true,
@@ -28,7 +23,7 @@ const runTmuxCommandMock = mock(async (): Promise<TmuxCommandResult> => ({
}))
const isInsideTmuxMock = mock((): boolean => true)
const isServerRunningMock = mock(async (): Promise<boolean> => true)
const getTmuxPathMock = mock(async (): Promise<string | undefined> => "sh")
const getTmuxPathMock = mock(async (): Promise<string | null> => "sh")
const logMock = mock(() => undefined)
function toStringArray(value: unknown): string[] {
@@ -64,23 +59,24 @@ function getNewWindowCommand(): string {
return newWindowCommand
}
function createDeps(): NonNullable<Parameters<typeof import("./window-spawn").spawnTmuxWindow>[5]> {
return {
log: logMock,
runTmuxCommand: runTmuxCommandMock,
isInsideTmux: isInsideTmuxMock,
isServerRunning: isServerRunningMock,
getTmuxPath: getTmuxPathMock,
}
}
async function loadSpawnTmuxWindow(): Promise<typeof import("./window-spawn").spawnTmuxWindow> {
const module = await import(`${windowSpawnSpecifier}?test=${crypto.randomUUID()}`)
return module.spawnTmuxWindow
}
function registerModuleMocks(): void {
mock.module(environmentSpecifier, () => ({ isInsideTmux: isInsideTmuxMock }))
mock.module(loggerSpecifier, () => ({ log: logMock }))
mock.module(runnerSpecifier, () => ({ runTmuxCommand: runTmuxCommandMock }))
mock.module(serverHealthSpecifier, () => ({ isServerRunning: isServerRunningMock }))
mock.module(tmuxPathResolverSpecifier, () => ({ getTmuxPath: getTmuxPathMock }))
}
describe("spawnTmuxWindow runner integration", () => {
beforeEach(() => {
mock.restore()
registerModuleMocks()
runTmuxCommandMock.mockClear()
isInsideTmuxMock.mockClear()
isServerRunningMock.mockClear()
@@ -109,7 +105,7 @@ describe("spawnTmuxWindow runner integration", () => {
const directory = "/tmp/omo-project/(window)"
// when
const result = await spawnTmuxWindow("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", directory)
const result = await spawnTmuxWindow("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", directory, createDeps())
// then
const firstCall = getRunTmuxCommandCall(0)
@@ -125,7 +121,7 @@ describe("spawnTmuxWindow runner integration", () => {
const spawnTmuxWindow = await loadSpawnTmuxWindow()
// when
await spawnTmuxWindow("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path with spaces/here")
await spawnTmuxWindow("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path with spaces/here", createDeps())
// then
expect(getNewWindowCommand()).toContain("--dir '/path with spaces/here'")
@@ -136,7 +132,7 @@ describe("spawnTmuxWindow runner integration", () => {
const spawnTmuxWindow = await loadSpawnTmuxWindow()
// when
await spawnTmuxWindow("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "")
await spawnTmuxWindow("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "", createDeps())
// then
expect(getNewWindowCommand()).toContain(`--dir '${process.cwd()}'`)
@@ -147,7 +143,7 @@ describe("spawnTmuxWindow runner integration", () => {
const spawnTmuxWindow = await loadSpawnTmuxWindow()
// when
await spawnTmuxWindow("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path/with'quote")
await spawnTmuxWindow("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path/with'quote", createDeps())
// then
expect(getNewWindowCommand()).toContain("--dir '/path/with'\\''quote'")
+31 -7
View File
@@ -4,20 +4,44 @@ import type { SpawnPaneResult } from "../types"
import { isInsideTmux } from "./environment"
import { isServerRunning } from "./server-health"
import { shellSingleQuote } from "../../shell-env"
import type { runTmuxCommand as RunTmuxCommand } from "../runner"
const ISOLATED_WINDOW_NAME = "omo-agents"
type SpawnTmuxWindowDeps = {
log: (message: string, data?: unknown) => void
runTmuxCommand: typeof RunTmuxCommand
isInsideTmux: typeof isInsideTmux
isServerRunning: typeof isServerRunning
getTmuxPath: typeof getTmuxPath
}
async function resolveSpawnTmuxWindowDeps(deps?: Partial<SpawnTmuxWindowDeps>): Promise<SpawnTmuxWindowDeps> {
const [{ log }, { runTmuxCommand }] = await Promise.all([
import("../../logger"),
import("../runner"),
])
return {
log,
runTmuxCommand,
isInsideTmux,
isServerRunning,
getTmuxPath,
...deps,
}
}
export async function spawnTmuxWindow(
sessionId: string,
description: string,
config: TmuxConfig,
serverUrl: string,
directory: string,
depsInput?: Partial<SpawnTmuxWindowDeps>,
): Promise<SpawnPaneResult> {
const [{ log }, { runTmuxCommand }] = await Promise.all([
import("../../logger"),
import("../runner"),
])
const deps = await resolveSpawnTmuxWindowDeps(depsInput)
const { log, runTmuxCommand } = deps
log("[spawnTmuxWindow] called", {
sessionId,
@@ -30,18 +54,18 @@ export async function spawnTmuxWindow(
log("[spawnTmuxWindow] SKIP: config.enabled is false")
return { success: false }
}
if (!isInsideTmux()) {
if (!deps.isInsideTmux()) {
log("[spawnTmuxWindow] SKIP: not inside tmux", { TMUX: process.env.TMUX })
return { success: false }
}
const serverRunning = await isServerRunning(serverUrl)
const serverRunning = await deps.isServerRunning(serverUrl)
if (!serverRunning) {
log("[spawnTmuxWindow] SKIP: server not running", { serverUrl })
return { success: false }
}
const tmux = await getTmuxPath()
const tmux = await deps.getTmuxPath()
if (!tmux) {
log("[spawnTmuxWindow] SKIP: tmux not found")
return { success: false }