From 833a42c253385f4afc4dc9dbf69dbe1e21f90fdb Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 21:34:34 -0700 Subject: [PATCH] test(gateway): split full-suite gateway-server shard --- scripts/test-projects.test-support.mjs | 101 +++++++++++++++++++++++-- test/scripts/test-projects.test.ts | 32 ++++++-- 2 files changed, 118 insertions(+), 15 deletions(-) diff --git a/scripts/test-projects.test-support.mjs b/scripts/test-projects.test-support.mjs index ba2230897e9..e5b16c909ba 100644 --- a/scripts/test-projects.test-support.mjs +++ b/scripts/test-projects.test-support.mjs @@ -404,6 +404,19 @@ const BROAD_CHANGED_ENV_KEY = "OPENCLAW_TEST_CHANGED_BROAD"; const VITEST_NO_OUTPUT_TIMEOUT_ENV_KEY = "OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS"; const VITEST_NO_OUTPUT_RETRY_ENV_KEY = "OPENCLAW_VITEST_NO_OUTPUT_RETRY"; export const DEFAULT_TEST_PROJECTS_VITEST_NO_OUTPUT_TIMEOUT_MS = "300000"; +const GATEWAY_SERVER_FULL_SUITE_TARGET_CHUNK_COUNT = 4; +const GATEWAY_SERVER_BACKED_HTTP_TEST_TARGETS = [ + "src/gateway/embeddings-http.test.ts", + "src/gateway/models-http.test.ts", + "src/gateway/openai-http.test.ts", + "src/gateway/openresponses-http.test.ts", + "src/gateway/probe.auth.integration.test.ts", +]; +const GATEWAY_SERVER_EXCLUDED_TEST_TARGETS = new Set([ + "src/gateway/gateway.test.ts", + "src/gateway/server.startup-matrix-migration.integration.test.ts", + "src/gateway/sessions-history-http.test.ts", +]); const VITEST_CONFIG_TARGET_KIND_BY_PATH = new Map( Object.entries(VITEST_CONFIG_BY_KIND).map(([kind, config]) => [config, kind]), ); @@ -455,6 +468,62 @@ function normalizePathPattern(value) { return value.replaceAll("\\", "/"); } +function listRepoFilesRecursive(root, cwd) { + const entries = fs.readdirSync(root, { withFileTypes: true }); + return entries.flatMap((entry) => { + const absolute = path.join(root, entry.name); + if (entry.isDirectory()) { + return listRepoFilesRecursive(absolute, cwd); + } + if (!entry.isFile()) { + return []; + } + return [normalizePathPattern(path.relative(cwd, absolute))]; + }); +} + +function isGatewayServerFullSuiteTarget(relative) { + if ( + GATEWAY_SERVER_EXCLUDED_TEST_TARGETS.has(relative) || + relative.startsWith("src/gateway/server-methods/") + ) { + return false; + } + return ( + GATEWAY_SERVER_BACKED_HTTP_TEST_TARGETS.includes(relative) || + (relative.startsWith("src/gateway/") && + path.posix.basename(relative).includes("server") && + relative.endsWith(".test.ts")) + ); +} + +function resolveGatewayServerFullSuiteTargets(cwd) { + const gatewayDir = path.join(cwd, "src/gateway"); + if (!fs.existsSync(gatewayDir)) { + return []; + } + return listRepoFilesRecursive(gatewayDir, cwd) + .filter(isGatewayServerFullSuiteTarget) + .sort((a, b) => a.localeCompare(b)); +} + +function splitTargetChunks(targets, chunkCount) { + if (targets.length === 0) { + return []; + } + const normalizedChunkCount = Math.min(chunkCount, targets.length); + const baseSize = Math.floor(targets.length / normalizedChunkCount); + const remainder = targets.length % normalizedChunkCount; + const chunks = []; + let offset = 0; + for (let index = 0; index < normalizedChunkCount; index += 1) { + const chunkSize = baseSize + (index < remainder ? 1 : 0); + chunks.push(targets.slice(offset, offset + chunkSize)); + offset += chunkSize; + } + return chunks; +} + function isExistingPathTarget(arg, cwd) { return fs.existsSync(path.resolve(cwd, arg)); } @@ -1299,7 +1368,7 @@ export function buildVitestRunPlans( } export function buildFullSuiteVitestRunPlans(args, cwd = process.cwd()) { - const { forwardedArgs, watchMode } = parseTestProjectsArgs(args, cwd); + const { forwardedArgs, targetArgs, watchMode } = parseTestProjectsArgs(args, cwd); if (watchMode) { return [ { @@ -1324,12 +1393,30 @@ export function buildFullSuiteVitestRunPlans(args, cwd = process.cwd()) { } const expandShard = expandToProjectConfigs; const configs = expandShard ? shard.projects : [shard.config]; - return configs.map((config) => ({ - config, - forwardedArgs, - includePatterns: null, - watchMode: false, - })); + return configs.flatMap((config) => { + if (expandShard && targetArgs.length === 0 && config === GATEWAY_SERVER_VITEST_CONFIG) { + const chunks = splitTargetChunks( + resolveGatewayServerFullSuiteTargets(cwd), + GATEWAY_SERVER_FULL_SUITE_TARGET_CHUNK_COUNT, + ); + if (chunks.length > 0) { + return chunks.map((targets) => ({ + config, + forwardedArgs: [...forwardedArgs, ...targets], + includePatterns: null, + watchMode: false, + })); + } + } + return [ + { + config, + forwardedArgs, + includePatterns: null, + watchMode: false, + }, + ]; + }); }); } diff --git a/test/scripts/test-projects.test.ts b/test/scripts/test-projects.test.ts index 1338507fef2..e156f6c2279 100644 --- a/test/scripts/test-projects.test.ts +++ b/test/scripts/test-projects.test.ts @@ -1142,6 +1142,7 @@ describe("scripts/test-projects full-suite sharding", () => { it("can expand full-suite shards to project configs for perf experiments", () => { const previous = process.env.OPENCLAW_TEST_PROJECTS_LEAF_SHARDS; + const gatewayServerConfig = "test/vitest/vitest.gateway-server.config.ts"; process.env.OPENCLAW_TEST_PROJECTS_LEAF_SHARDS = "1"; let plans: ReturnType; try { @@ -1187,7 +1188,10 @@ describe("scripts/test-projects full-suite sharding", () => { "test/vitest/vitest.gateway-core.config.ts", "test/vitest/vitest.gateway-client.config.ts", "test/vitest/vitest.gateway-methods.config.ts", - "test/vitest/vitest.gateway-server.config.ts", + gatewayServerConfig, + gatewayServerConfig, + gatewayServerConfig, + gatewayServerConfig, "test/vitest/vitest.cli.config.ts", "test/vitest/vitest.commands-light.config.ts", "test/vitest/vitest.commands.config.ts", @@ -1230,13 +1234,25 @@ describe("scripts/test-projects full-suite sharding", () => { "test/vitest/vitest.extensions.config.ts", "test/vitest/vitest.extension-misc.config.ts", ]); - expect(plans).toEqual( - plans.map((plan) => ({ - config: plan.config, - forwardedArgs: [], - includePatterns: null, - watchMode: false, - })), + + const gatewayPlans = plans.filter((plan) => plan.config === gatewayServerConfig); + const gatewayTargets = gatewayPlans.flatMap((plan) => plan.forwardedArgs); + const gatewayChunkSizes = gatewayPlans.map((plan) => plan.forwardedArgs.length); + expect(gatewayPlans).toHaveLength(4); + expect(gatewayTargets.length).toBeGreaterThan(90); + expect(new Set(gatewayTargets).size).toBe(gatewayTargets.length); + expect(gatewayTargets).toContain("src/gateway/server-network-runtime.e2e.test.ts"); + expect(gatewayTargets).not.toContain("src/gateway/gateway.test.ts"); + expect(Math.max(...gatewayChunkSizes) - Math.min(...gatewayChunkSizes)).toBeLessThanOrEqual(1); + expect(plans.filter((plan) => plan.config !== gatewayServerConfig)).toEqual( + plans + .filter((plan) => plan.config !== gatewayServerConfig) + .map((plan) => ({ + config: plan.config, + forwardedArgs: [], + includePatterns: null, + watchMode: false, + })), ); });