fix(test): prevent Vitest shard stalls

This commit is contained in:
Peter Steinberger
2026-04-23 06:20:22 +01:00
parent e6d0342629
commit ebf351b138
6 changed files with 210 additions and 3 deletions

View File

@@ -1,3 +1,4 @@
import { spawn } from "node:child_process";
import { createRequire } from "node:module";
import path from "node:path";
import { spawnPnpmRunner } from "./pnpm-runner.mjs";
@@ -49,6 +50,21 @@ export function shouldSuppressVitestStderrLine(line) {
return SUPPRESSED_VITEST_STDERR_PATTERNS.some((pattern) => line.includes(pattern));
}
export function resolveDirectNodeVitestArgs(pnpmArgs) {
return pnpmArgs[0] === "exec" && pnpmArgs[1] === "node" ? pnpmArgs.slice(2) : null;
}
function spawnVitestProcess({ pnpmArgs, spawnParams }) {
const directNodeArgs = resolveDirectNodeVitestArgs(pnpmArgs);
if (directNodeArgs) {
return spawn(process.execPath, directNodeArgs, spawnParams);
}
return spawnPnpmRunner({
pnpmArgs,
...spawnParams,
});
}
export function installVitestNoOutputWatchdog(params) {
const timeoutMs = params.timeoutMs;
if (!timeoutMs || timeoutMs <= 0) {
@@ -165,9 +181,9 @@ export function forwardVitestOutput(stream, target, shouldSuppressLine = () => f
}
export function spawnWatchedVitestProcess({ pnpmArgs, spawnParams, env, label }) {
const child = spawnPnpmRunner({
const child = spawnVitestProcess({
pnpmArgs,
...spawnParams,
spawnParams,
});
const teardownChildCleanup = installVitestProcessGroupCleanup({ child });
const teardownNoOutputWatchdog = installVitestNoOutputWatchdog({

View File

@@ -10,6 +10,8 @@ import {
spawnWatchedVitestProcess,
} from "./run-vitest.mjs";
import {
applyDefaultMultiSpecVitestCachePaths,
applyDefaultVitestNoOutputTimeout,
applyParallelVitestCachePaths,
buildFullSuiteVitestRunPlans,
createVitestRunSpecs,
@@ -324,7 +326,7 @@ async function main() {
const { targetArgs } = parseTestProjectsArgs(args, process.cwd());
const changedTargetArgs =
targetArgs.length === 0 ? resolveChangedTargetArgs(args, process.cwd()) : null;
const runSpecs =
const rawRunSpecs =
targetArgs.length === 0 && changedTargetArgs === null
? buildFullSuiteVitestRunPlans(args, process.cwd()).map((plan) => ({
config: plan.config,
@@ -348,6 +350,10 @@ async function main() {
baseEnv: process.env,
cwd: process.cwd(),
});
const runSpecs = applyDefaultMultiSpecVitestCachePaths(
applyDefaultVitestNoOutputTimeout(rawRunSpecs, { env: process.env }),
{ cwd: process.cwd(), env: process.env },
);
if (runSpecs.length === 0) {
console.error("[test] no changed test targets; skipping Vitest.");

View File

@@ -14,6 +14,8 @@ export type VitestRunSpec = {
watchMode: boolean;
};
export const DEFAULT_TEST_PROJECTS_VITEST_NO_OUTPUT_TIMEOUT_MS: string;
export function parseTestProjectsArgs(
args: string[],
cwd?: string,
@@ -51,6 +53,21 @@ export function createVitestRunSpecs(
},
): VitestRunSpec[];
export function applyDefaultVitestNoOutputTimeout(
specs: VitestRunSpec[],
params?: {
env?: Record<string, string | undefined>;
},
): VitestRunSpec[];
export function applyDefaultMultiSpecVitestCachePaths(
specs: VitestRunSpec[],
params?: {
cwd?: string;
env?: Record<string, string | undefined>;
},
): VitestRunSpec[];
export function writeVitestIncludeFile(filePath: string, includePatterns: string[]): void;
export function buildVitestArgs(args: string[], cwd?: string): string[];

View File

@@ -217,6 +217,8 @@ const GENERATED_CHANGED_TEST_TARGETS = new Set([
"src/canvas-host/a2ui/.bundle.hash",
"src/canvas-host/a2ui/a2ui.bundle.js",
]);
const VITEST_NO_OUTPUT_TIMEOUT_ENV_KEY = "OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS";
export const DEFAULT_TEST_PROJECTS_VITEST_NO_OUTPUT_TIMEOUT_MS = "180000";
const VITEST_CONFIG_TARGET_KIND_BY_PATH = new Map(
Object.entries(VITEST_CONFIG_BY_KIND).map(([kind, config]) => [config, kind]),
);
@@ -1028,6 +1030,32 @@ export function applyParallelVitestCachePaths(specs, params = {}) {
});
}
export function applyDefaultMultiSpecVitestCachePaths(specs, params = {}) {
if (specs.length <= 1 || specs.some((spec) => spec.watchMode)) {
return specs;
}
return applyParallelVitestCachePaths(specs, params);
}
export function applyDefaultVitestNoOutputTimeout(specs, params = {}) {
const baseEnv = params.env ?? process.env;
if (Object.hasOwn(baseEnv, VITEST_NO_OUTPUT_TIMEOUT_ENV_KEY)) {
return specs;
}
return specs.map((spec) => {
if (spec.watchMode || Object.hasOwn(spec.env ?? {}, VITEST_NO_OUTPUT_TIMEOUT_ENV_KEY)) {
return spec;
}
return {
...spec,
env: {
...spec.env,
[VITEST_NO_OUTPUT_TIMEOUT_ENV_KEY]: DEFAULT_TEST_PROJECTS_VITEST_NO_OUTPUT_TIMEOUT_MS,
},
};
});
}
export function createVitestRunSpecs(args, params = {}) {
const cwd = params.cwd ?? process.cwd();
const baseEnv = params.baseEnv ?? process.env;