Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions tests/ipcMain/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type { ToolPolicy } from "../../src/utils/tools/toolPolicy";
export const INIT_HOOK_WAIT_MS = 1500; // Wait for async init hook completion (local runtime)
export const SSH_INIT_WAIT_MS = 7000; // SSH init includes sync + checkout + hook, takes longer
export const HAIKU_MODEL = "anthropic:claude-haiku-4-5"; // Fast model for tests
export const GPT_5_MINI_MODEL = "openai:gpt-5-mini"; // Fastest model for performance-critical tests
export const TEST_TIMEOUT_LOCAL_MS = 25000; // Recommended timeout for local runtime tests
export const TEST_TIMEOUT_SSH_MS = 60000; // Recommended timeout for SSH runtime tests
export const STREAM_TIMEOUT_LOCAL_MS = 15000; // Stream timeout for local runtime
Expand Down Expand Up @@ -200,6 +201,8 @@ export async function sendMessageAndWait(
{
model,
toolPolicy,
thinkingLevel: "off", // Disable reasoning for fast test execution
mode: "exec", // Execute commands directly, don't propose plans
}
);

Expand Down
4 changes: 0 additions & 4 deletions tests/ipcMain/initWorkspace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
validateApiKeys,
getApiKey,
setupProviders,
preloadTestModules,
type TestEnvironment,
} from "./setup";
import { IPC_CHANNELS, getChatChannel } from "../../src/constants/ipc-constants";
Expand Down Expand Up @@ -460,9 +459,6 @@ let sshConfig: SSHServerConfig | undefined;

describeIntegration("Init Queue - Runtime Matrix", () => {
beforeAll(async () => {
// Preload AI SDK providers and tokenizers
await preloadTestModules();

// Only start SSH server if Docker is available
if (await isDockerAvailable()) {
console.log("Starting SSH server container for init queue tests...");
Expand Down
3 changes: 0 additions & 3 deletions tests/ipcMain/removeWorkspace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
createTestEnvironment,
cleanupTestEnvironment,
shouldRunIntegrationTests,
preloadTestModules,
type TestEnvironment,
} from "./setup";
import { IPC_CHANNELS } from "../../src/constants/ipc-constants";
Expand Down Expand Up @@ -104,8 +103,6 @@ async function makeWorkspaceDirty(env: TestEnvironment, workspaceId: string): Pr

describeIntegration("Workspace deletion integration tests", () => {
beforeAll(async () => {
await preloadTestModules();

// Check if Docker is available (required for SSH tests)
if (!(await isDockerAvailable())) {
throw new Error(
Expand Down
44 changes: 22 additions & 22 deletions tests/ipcMain/runtimeExecuteBash.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
createWorkspaceWithInit,
sendMessageAndWait,
extractTextFromEvents,
HAIKU_MODEL,
GPT_5_MINI_MODEL,
TEST_TIMEOUT_LOCAL_MS,
TEST_TIMEOUT_SSH_MS,
} from "./helpers";
Expand All @@ -46,7 +46,7 @@ const describeIntegration = shouldRunIntegrationTests() ? describe : describe.sk

// Validate API keys before running tests
if (shouldRunIntegrationTests()) {
validateApiKeys(["ANTHROPIC_API_KEY"]);
validateApiKeys(["OPENAI_API_KEY"]);
}

// SSH server config (shared across all SSH tests)
Expand Down Expand Up @@ -101,8 +101,8 @@ describeIntegration("Runtime Bash Execution", () => {
try {
// Setup provider
await setupProviders(env.mockIpcRenderer, {
anthropic: {
apiKey: getApiKey("ANTHROPIC_API_KEY"),
openai: {
apiKey: getApiKey("OPENAI_API_KEY"),
},
});

Expand All @@ -124,7 +124,7 @@ describeIntegration("Runtime Bash Execution", () => {
env,
workspaceId,
'Run the bash command "echo Hello World"',
HAIKU_MODEL,
GPT_5_MINI_MODEL,
BASH_ONLY
);

Expand Down Expand Up @@ -159,8 +159,8 @@ describeIntegration("Runtime Bash Execution", () => {
try {
// Setup provider
await setupProviders(env.mockIpcRenderer, {
anthropic: {
apiKey: getApiKey("ANTHROPIC_API_KEY"),
openai: {
apiKey: getApiKey("OPENAI_API_KEY"),
},
});

Expand All @@ -182,7 +182,7 @@ describeIntegration("Runtime Bash Execution", () => {
env,
workspaceId,
'Run bash command: export TEST_VAR="test123" && echo "Value: $TEST_VAR"',
HAIKU_MODEL,
GPT_5_MINI_MODEL,
BASH_ONLY
);

Expand Down Expand Up @@ -217,8 +217,8 @@ describeIntegration("Runtime Bash Execution", () => {
try {
// Setup provider
await setupProviders(env.mockIpcRenderer, {
anthropic: {
apiKey: getApiKey("ANTHROPIC_API_KEY"),
openai: {
apiKey: getApiKey("OPENAI_API_KEY"),
},
});

Expand All @@ -240,7 +240,7 @@ describeIntegration("Runtime Bash Execution", () => {
env,
workspaceId,
'Run bash: echo "Test with $dollar and \\"quotes\\" and `backticks`"',
HAIKU_MODEL,
GPT_5_MINI_MODEL,
BASH_ONLY
);

Expand Down Expand Up @@ -276,8 +276,8 @@ describeIntegration("Runtime Bash Execution", () => {
try {
// Setup provider
await setupProviders(env.mockIpcRenderer, {
anthropic: {
apiKey: getApiKey("ANTHROPIC_API_KEY"),
openai: {
apiKey: getApiKey("OPENAI_API_KEY"),
},
});

Expand All @@ -295,25 +295,26 @@ describeIntegration("Runtime Bash Execution", () => {

try {
// Create a test file with JSON content
// Using gpt-5-mini for speed (bash tool tests don't need reasoning power)
await sendMessageAndWait(
env,
workspaceId,
'Run bash: echo \'{"test": "data"}\' > /tmp/test.json',
HAIKU_MODEL,
GPT_5_MINI_MODEL,
BASH_ONLY
);

// Test command that pipes file through stdin-reading command (jq)
// Test command that pipes file through stdin-reading command (grep)
// This would hang forever if stdin.close() was used instead of stdin.abort()
// Regression test for: https://github.com/coder/cmux/issues/503
const startTime = Date.now();
const events = await sendMessageAndWait(
env,
workspaceId,
"Run bash with 3s timeout: cat /tmp/test.json | jq '.'",
HAIKU_MODEL,
"Run bash: cat /tmp/test.json | grep test",
GPT_5_MINI_MODEL,
BASH_ONLY,
15000 // 15s max wait - should complete in < 5s
10000 // 10s timeout - should complete in ~4s per API call
);
const duration = Date.now() - startTime;

Expand All @@ -325,10 +326,9 @@ describeIntegration("Runtime Bash Execution", () => {
expect(responseText).toContain("data");

// Verify command completed quickly (not hanging until timeout)
// Should complete in under 15 seconds for SSH, 10 seconds for local
// Generous timeouts to account for CI runner variability
// (actual hangs would hit bash tool's 180s timeout)
const maxDuration = type === "ssh" ? 15000 : 10000;
// With tokenizer preloading, both local and SSH complete in ~8s total
// Actual hangs would hit bash tool's 180s timeout
const maxDuration = 10000;
expect(duration).toBeLessThan(maxDuration);

// Verify bash tool was called
Expand Down
4 changes: 0 additions & 4 deletions tests/ipcMain/runtimeFileEditing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
validateApiKeys,
getApiKey,
setupProviders,
preloadTestModules,
type TestEnvironment,
} from "./setup";
import { IPC_CHANNELS } from "../../src/constants/ipc-constants";
Expand Down Expand Up @@ -65,9 +64,6 @@ let sshConfig: SSHServerConfig | undefined;

describeIntegration("Runtime File Editing Tools", () => {
beforeAll(async () => {
// Preload AI SDK providers and tokenizers to avoid race conditions in concurrent tests
await preloadTestModules();

// Check if Docker is available (required for SSH tests)
if (!(await isDockerAvailable())) {
throw new Error(
Expand Down
15 changes: 15 additions & 0 deletions tests/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,18 @@ if (typeof globalThis.File === "undefined") {
lastModified: number;
};
}

// Preload tokenizer and AI SDK modules for integration tests
// This eliminates ~10s initialization delay on first use
if (process.env.TEST_INTEGRATION === "1") {
// Store promise globally to ensure it blocks subsequent test execution
(globalThis as any).__cmuxPreloadPromise = (async () => {
const { preloadTestModules } = await import("./ipcMain/setup");
await preloadTestModules();
})();

// Add a global beforeAll to block until preload completes
beforeAll(async () => {
await (globalThis as any).__cmuxPreloadPromise;
}, 30000); // 30s timeout for preload
}