diff --git a/scripts/run-vitest.mjs b/scripts/run-vitest.mjs index 49d5b1e56678..29100770f9aa 100644 --- a/scripts/run-vitest.mjs +++ b/scripts/run-vitest.mjs @@ -25,6 +25,8 @@ export const DEFAULT_VITEST_NO_OUTPUT_TIMEOUT_MS = 120_000; export const DEFAULT_VITEST_NO_OUTPUT_HEARTBEAT_MS = 30_000; /** Longer watchdog timeout for known long-running Vitest configs. */ export const DEFAULT_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS = 300_000; +/** Extra-long watchdog timeout for broad configs that can stay silent on macOS. */ +export const DEFAULT_EXTRA_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS = 2_400_000; const VITEST_NO_OUTPUT_TIMEOUT_ENV_KEY = "OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS"; const VITEST_NO_OUTPUT_HEARTBEAT_ENV_KEY = "OPENCLAW_VITEST_NO_OUTPUT_HEARTBEAT_MS"; const UI_VITEST_CONFIG = "test/vitest/vitest.ui.config.ts"; @@ -32,12 +34,20 @@ const UNIT_UI_VITEST_CONFIG = "test/vitest/vitest.unit-ui.config.ts"; const TOOLING_DOCKER_VITEST_CONFIG = "test/vitest/vitest.tooling-docker.config.ts"; const TOOLING_VITEST_CONFIG = "test/vitest/vitest.tooling.config.ts"; const GATEWAY_VITEST_CONFIG = "test/vitest/vitest.gateway.config.ts"; -const LONG_RUNNING_VITEST_CONFIGS = new Set([ - "test/vitest/vitest.e2e.config.ts", - GATEWAY_VITEST_CONFIG, - "test/vitest/vitest.ui-e2e.config.ts", - "test/vitest/vitest.full-agentic.config.ts", - "test/vitest/vitest.full-core-contracts.config.ts", +const VITEST_CONFIG_NO_OUTPUT_TIMEOUT_MS = new Map([ + ["test/vitest/vitest.e2e.config.ts", DEFAULT_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS], + [GATEWAY_VITEST_CONFIG, DEFAULT_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS], + ["test/vitest/vitest.ui-e2e.config.ts", DEFAULT_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS], + ["test/vitest/vitest.full-agentic.config.ts", DEFAULT_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS], + [ + "test/vitest/vitest.full-core-contracts.config.ts", + DEFAULT_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS, + ], + [ + "test/vitest/vitest.contracts-plugin.config.ts", + DEFAULT_EXTRA_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS, + ], + ["test/vitest/vitest.infra.config.ts", DEFAULT_EXTRA_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS], ]); const TOOLING_EXCLUDED_TESTS = new Set([ ...boundaryTestFiles, @@ -362,10 +372,9 @@ export function resolveRunVitestSpawnEnv(env = process.env, argv = []) { */ export function resolveDefaultVitestNoOutputTimeoutMs(argv = []) { const config = resolveVitestConfigArg(argv); - if (config !== null && isLongRunningVitestConfig(config)) { - return DEFAULT_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS; - } - return DEFAULT_VITEST_NO_OUTPUT_TIMEOUT_MS; + return config === null + ? DEFAULT_VITEST_NO_OUTPUT_TIMEOUT_MS + : (resolveVitestConfigNoOutputTimeoutMs(config) ?? DEFAULT_VITEST_NO_OUTPUT_TIMEOUT_MS); } function resolveVitestConfigArg(argv) { @@ -384,14 +393,14 @@ function resolveVitestConfigArg(argv) { return null; } -function isLongRunningVitestConfig(config) { +function resolveVitestConfigNoOutputTimeoutMs(config) { const normalized = path.normalize(config).replaceAll(path.sep, "/").replace(/^\.\//u, ""); - for (const candidate of LONG_RUNNING_VITEST_CONFIGS) { + for (const [candidate, timeoutMs] of VITEST_CONFIG_NO_OUTPUT_TIMEOUT_MS) { if (normalized === candidate || normalized.endsWith(`/${candidate}`)) { - return true; + return timeoutMs; } } - return false; + return null; } /** diff --git a/scripts/test-projects.test-support.mjs b/scripts/test-projects.test-support.mjs index 3e8a0e0e39e6..cbd758c17081 100644 --- a/scripts/test-projects.test-support.mjs +++ b/scripts/test-projects.test-support.mjs @@ -55,6 +55,7 @@ import { import { isCiLikeEnv, resolveLocalFullSuiteProfile } from "./lib/vitest-local-scheduling.mjs"; import { DEFAULT_VITEST_NO_OUTPUT_HEARTBEAT_MS, + resolveDefaultVitestNoOutputTimeoutMs, resolveVitestCliEntry, resolveVitestNodeArgs, } from "./run-vitest.mjs"; @@ -865,6 +866,12 @@ const GATEWAY_SERVER_EXCLUDED_TEST_TARGETS = new Set([ "src/gateway/server.startup-matrix-migration.integration.test.ts", "src/gateway/sessions-history-http.test.ts", ]); +function resolveTestProjectsVitestNoOutputTimeoutMs(config) { + const directRunnerTimeoutMs = resolveDefaultVitestNoOutputTimeoutMs(["run", "--config", config]); + return String( + Math.max(Number(DEFAULT_TEST_PROJECTS_VITEST_NO_OUTPUT_TIMEOUT_MS), directRunnerTimeoutMs), + ); +} const VITEST_CONFIG_TARGET_KIND_BY_PATH = new Map( Object.entries(VITEST_CONFIG_BY_KIND).map(([kind, config]) => [config, kind]), ); @@ -2601,7 +2608,9 @@ export function applyDefaultVitestNoOutputTimeout(specs, params = {}) { !Object.hasOwn(baseEnv, VITEST_NO_OUTPUT_TIMEOUT_ENV_KEY) && !Object.hasOwn(env, VITEST_NO_OUTPUT_TIMEOUT_ENV_KEY) ) { - nextEnv[VITEST_NO_OUTPUT_TIMEOUT_ENV_KEY] = DEFAULT_TEST_PROJECTS_VITEST_NO_OUTPUT_TIMEOUT_MS; + nextEnv[VITEST_NO_OUTPUT_TIMEOUT_ENV_KEY] = resolveTestProjectsVitestNoOutputTimeoutMs( + spec.config, + ); } if ( !Object.hasOwn(baseEnv, VITEST_NO_OUTPUT_HEARTBEAT_ENV_KEY) && diff --git a/test/scripts/run-vitest.test.ts b/test/scripts/run-vitest.test.ts index 9cc6c0457c3b..2a5cabb88630 100644 --- a/test/scripts/run-vitest.test.ts +++ b/test/scripts/run-vitest.test.ts @@ -7,6 +7,7 @@ import nodePath from "node:path"; import { setTimeout as delay } from "node:timers/promises"; import { describe, expect, it, vi } from "vitest"; import { + DEFAULT_EXTRA_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS, DEFAULT_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS, installVitestNoOutputWatchdog, resolveDefaultVitestNoOutputTimeoutMs, @@ -473,6 +474,7 @@ describe("scripts/run-vitest", () => { it("uses a longer default stall watchdog for broad e2e and project shard configs", () => { const timeout = String(DEFAULT_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS); + const extraLongTimeout = String(DEFAULT_EXTRA_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS); for (const configArg of [ "--config=test/vitest/vitest.e2e.config.ts", @@ -487,6 +489,16 @@ describe("scripts/run-vitest", () => { OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS: timeout, }); } + for (const configArg of [ + "--config=test/vitest/vitest.contracts-plugin.config.ts", + "--config=test/vitest/vitest.infra.config.ts", + ]) { + expect(resolveRunVitestSpawnEnv({ PATH: "/usr/bin" }, ["run", configArg])).toEqual({ + PATH: "/usr/bin", + OPENCLAW_VITEST_NO_OUTPUT_HEARTBEAT_MS: "30000", + OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS: extraLongTimeout, + }); + } expect( resolveDefaultVitestNoOutputTimeoutMs([ "run", @@ -515,6 +527,20 @@ describe("scripts/run-vitest", () => { "/repo/test/vitest/vitest.full-core-contracts.config.ts", ]), ).toBe(DEFAULT_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS); + expect( + resolveDefaultVitestNoOutputTimeoutMs([ + "run", + "--config", + "/repo/test/vitest/vitest.contracts-plugin.config.ts", + ]), + ).toBe(DEFAULT_EXTRA_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS); + expect( + resolveDefaultVitestNoOutputTimeoutMs([ + "run", + "--config", + "/repo/test/vitest/vitest.infra.config.ts", + ]), + ).toBe(DEFAULT_EXTRA_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS); }); it("does not default implicit interactive runs to the stall watchdog", () => { diff --git a/test/scripts/test-projects.test.ts b/test/scripts/test-projects.test.ts index c502cdba8afe..435e6d49dfe0 100644 --- a/test/scripts/test-projects.test.ts +++ b/test/scripts/test-projects.test.ts @@ -2716,6 +2716,44 @@ describe("scripts/test-projects Vitest stall watchdog", () => { ); }); + it("extends the no-output watchdog for slow silent full-suite configs", () => { + const specs = applyDefaultVitestNoOutputTimeout( + [ + { + config: "test/vitest/vitest.contracts-plugin.config.ts", + env: { PATH: "/usr/bin" }, + includeFilePath: null, + includePatterns: null, + pnpmArgs: [], + watchMode: false, + }, + { + config: "test/vitest/vitest.infra.config.ts", + env: { PATH: "/usr/bin" }, + includeFilePath: null, + includePatterns: null, + pnpmArgs: [], + watchMode: false, + }, + { + config: "test/vitest/vitest.extension-feishu.config.ts", + env: { PATH: "/usr/bin" }, + includeFilePath: null, + includePatterns: null, + pnpmArgs: [], + watchMode: false, + }, + ], + { env: { PATH: "/usr/bin" } }, + ); + + expect(specs[0]?.env.OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS).toBe("2400000"); + expect(specs[1]?.env.OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS).toBe("2400000"); + expect(specs[2]?.env.OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS).toBe( + DEFAULT_TEST_PROJECTS_VITEST_NO_OUTPUT_TIMEOUT_MS, + ); + }); + it("keeps explicit watchdog settings and watch mode untouched", () => { const specs = applyDefaultVitestNoOutputTimeout( [