mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-23 19:08:22 -06:00
fix(ci): prevent Telegram test watchdog stalls (#123514)
Bound Telegram extension tests to five files per Vitest process across explicit config, directory, and full-suite routes while preserving serial isolated execution. Co-authored-by: Ayaan Zaidi <hi@obviy.us>
This commit is contained in:
@@ -93,6 +93,11 @@ const EXTENSION_TEST_PROCESS_FILE_LIMITS = new Map<string, number>([
|
||||
// The non-isolated Matrix suite intentionally shares module state within a process.
|
||||
// Bound its lifetime so Vite's transformed module graph cannot grow across the whole suite.
|
||||
["test/vitest/vitest.extension-matrix.config.ts", 40],
|
||||
[
|
||||
"test/vitest/vitest.extension-telegram.config.ts",
|
||||
// A 10-file serial worker reached 231s and 2.7 GiB RSS (observed 2026-08).
|
||||
5,
|
||||
],
|
||||
]);
|
||||
const EXTENSION_TEST_CONFIG_ROUTES: Array<[(root: string) => boolean, string]> = [
|
||||
[isActiveMemoryExtensionRoot, "test/vitest/vitest.extension-active-memory.config.ts"],
|
||||
|
||||
@@ -50,7 +50,10 @@ import {
|
||||
isProviderOpenAiExtensionRoot,
|
||||
} from "../test/vitest/vitest.extension-provider-paths.mjs";
|
||||
import { isQaExtensionRoot } from "../test/vitest/vitest.extension-qa-paths.mjs";
|
||||
import { isTelegramExtensionRoot } from "../test/vitest/vitest.extension-telegram-paths.mjs";
|
||||
import {
|
||||
isTelegramExtensionRoot,
|
||||
telegramExtensionTestRoots,
|
||||
} from "../test/vitest/vitest.extension-telegram-paths.mjs";
|
||||
import { isVoiceCallExtensionRoot } from "../test/vitest/vitest.extension-voice-call-paths.mjs";
|
||||
import { isWhatsAppExtensionRoot } from "../test/vitest/vitest.extension-whatsapp-paths.mjs";
|
||||
import { isZaloExtensionRoot } from "../test/vitest/vitest.extension-zalo-paths.mjs";
|
||||
@@ -255,6 +258,10 @@ const UNIT_FAST_FAKE_TIMERS_VITEST_CONFIG = "test/vitest/vitest.unit-fast-fake-t
|
||||
const UNIT_SECURITY_VITEST_CONFIG = "test/vitest/vitest.unit-security.config.ts";
|
||||
const UNIT_SRC_VITEST_CONFIG = "test/vitest/vitest.unit-src.config.ts";
|
||||
const UNIT_SUPPORT_VITEST_CONFIG = "test/vitest/vitest.unit-support.config.ts";
|
||||
const EXTENSION_TEST_PROCESS_ROOTS = new Map([
|
||||
[EXTENSION_MATRIX_VITEST_CONFIG, matrixExtensionTestRoots],
|
||||
[EXTENSION_TELEGRAM_VITEST_CONFIG, telegramExtensionTestRoots],
|
||||
]);
|
||||
|
||||
const FULL_SUITE_CONFIG_WEIGHT = new Map([
|
||||
[GATEWAY_VITEST_CONFIG, 180],
|
||||
@@ -962,16 +969,15 @@ function createBroadToolingScriptPlans(params: VitestRunPlan & { cwd: string })
|
||||
: null;
|
||||
}
|
||||
|
||||
function createBoundedExtensionPlans(
|
||||
params: Omit<VitestRunPlan, "includePatterns"> & { roots: string[] },
|
||||
) {
|
||||
const { config, forwardedArgs, roots, watchMode } = params;
|
||||
if (watchMode) {
|
||||
return null;
|
||||
function createBoundedExtensionPlans(plan: VitestRunPlan) {
|
||||
const { config, forwardedArgs, watchMode } = plan;
|
||||
const roots = EXTENSION_TEST_PROCESS_ROOTS.get(config);
|
||||
if (watchMode || !roots) {
|
||||
return [plan];
|
||||
}
|
||||
const chunks = createExtensionTestProcessTargetChunks(config, roots, forwardedArgs);
|
||||
if (chunks.length <= 1) {
|
||||
return null;
|
||||
return [plan];
|
||||
}
|
||||
return chunks.map((includePatterns) => ({
|
||||
config,
|
||||
@@ -3602,12 +3608,14 @@ export function buildVitestRunPlans(
|
||||
"watch mode with mixed test suites is not supported; target one suite at a time or use a dedicated suite command",
|
||||
);
|
||||
}
|
||||
return explicitConfigTargets.map((config) => ({
|
||||
config,
|
||||
forwardedArgs: nonTargetArgs,
|
||||
includePatterns: null,
|
||||
watchMode,
|
||||
}));
|
||||
return explicitConfigTargets.flatMap((config) =>
|
||||
createBoundedExtensionPlans({
|
||||
config,
|
||||
forwardedArgs: nonTargetArgs,
|
||||
includePatterns: null,
|
||||
watchMode,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const groupedTargets = new Map<string, string[]>();
|
||||
@@ -3718,13 +3726,7 @@ export function buildVitestRunPlans(
|
||||
includePatterns: null,
|
||||
watchMode,
|
||||
};
|
||||
const boundedPlans = createBoundedExtensionPlans({
|
||||
config,
|
||||
forwardedArgs: nonTargetArgs,
|
||||
roots: matrixExtensionTestRoots,
|
||||
watchMode,
|
||||
});
|
||||
plans.push(...(boundedPlans ?? [plan]));
|
||||
plans.push(...createBoundedExtensionPlans(plan));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -3761,9 +3763,12 @@ export function buildVitestRunPlans(
|
||||
plans.push(...broadToolingScriptPlans);
|
||||
continue;
|
||||
}
|
||||
const processRoots = EXTENSION_TEST_PROCESS_ROOTS.get(config);
|
||||
const boundedExtensionRoots = grouped.flatMap((targetArg) => {
|
||||
const root = toRepoRelativeTarget(targetArg, cwd);
|
||||
return isMatrixExtensionRoot(root) && isExistingDirectoryTarget(targetArg, cwd) ? [root] : [];
|
||||
return processRoots?.includes(root) && isExistingDirectoryTarget(targetArg, cwd)
|
||||
? [root]
|
||||
: [];
|
||||
});
|
||||
const boundedRootsCoverGroupedTargets = grouped.every((targetArg) => {
|
||||
const relativeTarget = toRepoRelativeTarget(targetArg, cwd);
|
||||
@@ -3776,7 +3781,7 @@ export function buildVitestRunPlans(
|
||||
? createBoundedExtensionPlans({
|
||||
config,
|
||||
forwardedArgs: forwardedPlanArgs,
|
||||
roots: boundedExtensionRoots,
|
||||
includePatterns,
|
||||
watchMode,
|
||||
})
|
||||
: null;
|
||||
@@ -3856,12 +3861,11 @@ export function buildFullSuiteVitestRunPlans(args: string[], cwd = process.cwd()
|
||||
listGatewayServerTestTargets(cwd),
|
||||
GATEWAY_SERVER_TEST_PROCESS_COUNT,
|
||||
);
|
||||
} else if (config === EXTENSION_MATRIX_VITEST_CONFIG) {
|
||||
chunks = createExtensionTestProcessTargetChunks(
|
||||
config,
|
||||
matrixExtensionTestRoots,
|
||||
forwardedArgs,
|
||||
);
|
||||
} else {
|
||||
const roots = EXTENSION_TEST_PROCESS_ROOTS.get(config);
|
||||
if (roots) {
|
||||
chunks = createExtensionTestProcessTargetChunks(config, roots, forwardedArgs);
|
||||
}
|
||||
}
|
||||
if (chunks.length > 0) {
|
||||
return chunks.map((targets) => ({
|
||||
|
||||
@@ -42,26 +42,35 @@ import {
|
||||
|
||||
const normalizeRepoPath = toRepoPath;
|
||||
const MATRIX_TEST_PROCESS_FILE_LIMIT = 40;
|
||||
const TELEGRAM_TEST_PROCESS_FILE_LIMIT = 5;
|
||||
|
||||
function expectedMatrixTestProcessCount() {
|
||||
const testFileCount = listExtensionTestFilesForRoots(["extensions/matrix"]).length;
|
||||
return Math.max(1, Math.ceil(testFileCount / MATRIX_TEST_PROCESS_FILE_LIMIT));
|
||||
}
|
||||
|
||||
function expectedTelegramTestProcessCount() {
|
||||
const testFileCount = listExtensionTestFilesForRoots(["extensions/telegram"]).length;
|
||||
return Math.max(1, Math.ceil(testFileCount / TELEGRAM_TEST_PROCESS_FILE_LIMIT));
|
||||
}
|
||||
|
||||
function listExpectedFullExtensionRunPlans() {
|
||||
const matrixConfig = "test/vitest/vitest.extension-matrix.config.ts";
|
||||
const matrixPlans = buildVitestRunPlans(["extensions/matrix"], process.cwd());
|
||||
return listFullExtensionVitestProjectConfigs().flatMap((config) =>
|
||||
config === matrixConfig
|
||||
? matrixPlans
|
||||
: [
|
||||
{
|
||||
config,
|
||||
forwardedArgs: [],
|
||||
includePatterns: null,
|
||||
watchMode: false,
|
||||
},
|
||||
],
|
||||
const telegramConfig = "test/vitest/vitest.extension-telegram.config.ts";
|
||||
const boundedPlansByConfig = new Map([
|
||||
[matrixConfig, buildVitestRunPlans(["extensions/matrix"], process.cwd())],
|
||||
[telegramConfig, buildVitestRunPlans(["extensions/telegram"], process.cwd())],
|
||||
]);
|
||||
return listFullExtensionVitestProjectConfigs().flatMap(
|
||||
(config) =>
|
||||
boundedPlansByConfig.get(config) ?? [
|
||||
{
|
||||
config,
|
||||
forwardedArgs: [],
|
||||
includePatterns: null,
|
||||
watchMode: false,
|
||||
},
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2084,12 +2093,15 @@ describe("scripts/test-projects changed-target routing", () => {
|
||||
|
||||
it("routes the top-level extensions target to every extension shard", () => {
|
||||
const matrixConfig = "test/vitest/vitest.extension-matrix.config.ts";
|
||||
const telegramConfig = "test/vitest/vitest.extension-telegram.config.ts";
|
||||
const plans = buildVitestRunPlans(["extensions"], process.cwd());
|
||||
const matrixPlans = plans.filter((plan) => plan.config === matrixConfig);
|
||||
const telegramPlans = plans.filter((plan) => plan.config === telegramConfig);
|
||||
const boundedConfigs = new Set([matrixConfig, telegramConfig]);
|
||||
|
||||
expect(plans.filter((plan) => plan.config !== matrixConfig)).toEqual(
|
||||
expect(plans.filter((plan) => !boundedConfigs.has(plan.config))).toEqual(
|
||||
listFullExtensionVitestProjectConfigs()
|
||||
.filter((config) => config !== matrixConfig)
|
||||
.filter((config) => !boundedConfigs.has(config))
|
||||
.map((config) => ({
|
||||
config,
|
||||
forwardedArgs: [],
|
||||
@@ -2106,9 +2118,34 @@ describe("scripts/test-projects changed-target routing", () => {
|
||||
expect(matrixPlans.flatMap((plan) => plan.includePatterns ?? [])).toEqual(
|
||||
listExtensionTestFilesForRoots(["extensions/matrix"]),
|
||||
);
|
||||
expect(telegramPlans).toHaveLength(expectedTelegramTestProcessCount());
|
||||
expect(
|
||||
telegramPlans.every(
|
||||
(plan) => (plan.includePatterns?.length ?? 0) <= TELEGRAM_TEST_PROCESS_FILE_LIMIT,
|
||||
),
|
||||
).toBe(true);
|
||||
expect(telegramPlans.flatMap((plan) => plan.includePatterns ?? [])).toEqual(
|
||||
listExtensionTestFilesForRoots(["extensions/telegram"]),
|
||||
);
|
||||
expect(plans).toEqual(listExpectedFullExtensionRunPlans());
|
||||
});
|
||||
|
||||
it("bounds an explicit Telegram config target across process lifetimes", () => {
|
||||
const config = "test/vitest/vitest.extension-telegram.config.ts";
|
||||
const plans = buildVitestRunPlans([config], process.cwd());
|
||||
|
||||
expect(plans).toHaveLength(expectedTelegramTestProcessCount());
|
||||
expect(plans.every((plan) => plan.config === config)).toBe(true);
|
||||
expect(
|
||||
plans.every(
|
||||
(plan) => (plan.includePatterns?.length ?? 0) <= TELEGRAM_TEST_PROCESS_FILE_LIMIT,
|
||||
),
|
||||
).toBe(true);
|
||||
expect(plans.flatMap((plan) => plan.includePatterns ?? [])).toEqual(
|
||||
listExtensionTestFilesForRoots(["extensions/telegram"]),
|
||||
);
|
||||
});
|
||||
|
||||
it("bounds an explicit Matrix directory target across process lifetimes", () => {
|
||||
const plans = buildVitestRunPlans(["extensions/matrix"], process.cwd());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user