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:
Ayaan Zaidi
2026-08-14 11:55:29 +05:30
committed by GitHub
parent e7a2d1758a
commit 14882ac44d
3 changed files with 89 additions and 43 deletions
+5
View File
@@ -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"],
+33 -29
View File
@@ -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) => ({
+51 -14
View File
@@ -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());