From 6cc7432cd1bed85a710abe4be9f6fe07675e3f7d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 1 May 2026 03:37:19 +0100 Subject: [PATCH] perf(test): split gateway server control-plane shard --- scripts/lib/ci-node-test-plan.mjs | 82 +++++++++++++++++-- .../server.models-voicewake-misc.test.ts | 50 +++++------ test/scripts/ci-node-test-plan.test.ts | 57 +++++++++++-- 3 files changed, 151 insertions(+), 38 deletions(-) diff --git a/scripts/lib/ci-node-test-plan.mjs b/scripts/lib/ci-node-test-plan.mjs index 295d6e58f7e..0f8f091108a 100644 --- a/scripts/lib/ci-node-test-plan.mjs +++ b/scripts/lib/ci-node-test-plan.mjs @@ -137,6 +137,81 @@ function createAgenticCommandSplitShards() { .filter((shard) => shard.includePatterns.length > 0); } +const GATEWAY_SERVER_BACKED_HTTP_TESTS = new Set([ + "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_TESTS = new Set([ + "src/gateway/gateway.test.ts", + "src/gateway/server.startup-matrix-migration.integration.test.ts", + "src/gateway/sessions-history-http.test.ts", +]); + +function isGatewayServerTestFile(file) { + return ( + file.startsWith("src/gateway/") && + !file.startsWith("src/gateway/server-methods/") && + !GATEWAY_SERVER_EXCLUDED_TESTS.has(file) && + (file.includes("server") || GATEWAY_SERVER_BACKED_HTTP_TESTS.has(file)) + ); +} + +function resolveGatewayServerShardName(file) { + const name = relative("src/gateway", file).replaceAll("\\", "/"); + if ( + GATEWAY_SERVER_BACKED_HTTP_TESTS.has(file) || + name.startsWith("server.models") || + name.startsWith("server.talk") + ) { + return "agentic-control-plane-http-models"; + } + if ( + name.startsWith("server.agent") || + name.startsWith("server.chat") || + name.startsWith("server.sessions") + ) { + return "agentic-control-plane-agent-chat"; + } + if ( + name.includes("auth") || + name.includes("device") || + name.includes("node") || + name.includes("roles") || + name.includes("silent") || + name.includes("preauth") || + name.includes("control-plane-rate-limit") + ) { + return "agentic-control-plane-auth-node"; + } + return "agentic-control-plane-runtime"; +} + +function createGatewayServerSplitShards() { + const groups = new Map(); + for (const file of listTestFiles("src/gateway").filter(isGatewayServerTestFile)) { + const shardName = resolveGatewayServerShardName(file); + groups.set(shardName, [...(groups.get(shardName) ?? []), file]); + } + return [ + "agentic-control-plane-agent-chat", + "agentic-control-plane-auth-node", + "agentic-control-plane-http-models", + "agentic-control-plane-runtime", + ] + .map((shardName) => ({ + configs: ["test/vitest/vitest.gateway-server.config.ts"], + includePatterns: groups.get(shardName) ?? [], + requiresDist: false, + runner: "blacksmith-4vcpu-ubuntu-2404", + shardName, + })) + .filter((shard) => shard.includePatterns.length > 0); +} + const SPLIT_NODE_SHARDS = new Map([ [ "core-unit-fast", @@ -225,12 +300,7 @@ const SPLIT_NODE_SHARDS = new Map([ [ "agentic", [ - { - shardName: "agentic-control-plane", - configs: ["test/vitest/vitest.gateway-server.config.ts"], - requiresDist: false, - runner: "blacksmith-4vcpu-ubuntu-2404", - }, + ...createGatewayServerSplitShards(), { shardName: "agentic-cli", configs: ["test/vitest/vitest.cli.config.ts"], diff --git a/src/gateway/server.models-voicewake-misc.test.ts b/src/gateway/server.models-voicewake-misc.test.ts index 47ae9aa575c..8569fba4c79 100644 --- a/src/gateway/server.models-voicewake-misc.test.ts +++ b/src/gateway/server.models-voicewake-misc.test.ts @@ -3,7 +3,6 @@ import { createServer } from "node:net"; import path from "node:path"; import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { WebSocket } from "ws"; -import { resetModelCatalogCacheForTest } from "../agents/model-catalog.js"; import type { ChannelOutboundAdapter } from "../channels/plugins/types.js"; import { clearConfigCache, clearRuntimeConfigSnapshot } from "../config/config.js"; import { resolveCanvasHostUrl } from "../infra/canvas-host-url.js"; @@ -11,6 +10,7 @@ import { createOutboundTestPlugin } from "../test-utils/channel-plugins.js"; import { withEnvAsync } from "../test-utils/env.js"; import { createTempHomeEnv } from "../test-utils/temp-home.js"; import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js"; +import { __resetModelCatalogCacheForTest as resetGatewayModelCatalogCacheForTest } from "./server-model-catalog.js"; import { createRegistry } from "./server.e2e-registry-helpers.js"; import { connectOk, @@ -89,6 +89,8 @@ type ModelCatalogRpcEntry = { provider: string; alias?: string; contextWindow?: number; + input?: string[]; + reasoning?: boolean; }; type PiCatalogFixtureEntry = { @@ -154,14 +156,14 @@ describe("gateway server models + voicewake", () => { : await rpcReq<{ models: ModelCatalogRpcEntry[] }>(ws, "models.list"), ); - const setPiCatalog = (entries: PiCatalogFixtureEntry[]) => { + const setPiCatalog = async (entries: PiCatalogFixtureEntry[]) => { piSdkMock.enabled = true; piSdkMock.models = entries; - resetModelCatalogCacheForTest(); + await resetGatewayModelCatalogCacheForTest(); }; - const seedPiCatalog = () => { - setPiCatalog(buildPiCatalogFixture()); + const seedPiCatalog = async () => { + await setPiCatalog(buildPiCatalogFixture()); }; const withModelsConfig = async (config: unknown, run: () => Promise): Promise => { @@ -220,7 +222,7 @@ describe("gateway server models + voicewake", () => { }, }, async () => { - seedPiCatalog(); + await seedPiCatalog(); const res = await listModels(); expect(res.ok).toBe(true); expect(res.payload?.models).toEqual(options.expected); @@ -472,7 +474,7 @@ describe("gateway server models + voicewake", () => { }); test("models.list all view returns model catalog", async () => { - seedPiCatalog(); + await seedPiCatalog(); const res1 = await listModels({ view: "all" }); const res2 = await listModels({ view: "all" }); @@ -499,18 +501,18 @@ describe("gateway server models + voicewake", () => { }, }, async () => { - setPiCatalog([ + await setPiCatalog([ { id: "remote-a", provider: "unauth-a", name: "Remote A" }, { id: "remote-b", provider: "unauth-b", name: "Remote B" }, ]); const res = await listModels(); expect(res.ok).toBe(true); expect(res.payload?.models).toEqual([ - { + expect.objectContaining({ id: "MiniMax-M2.7-highspeed", name: "MiniMax M2.7 Highspeed", provider: "minimax", - }, + }), ]); }, ); @@ -525,7 +527,7 @@ describe("gateway server models + voicewake", () => { }, async () => { await withModelsConfig({}, async () => { - seedPiCatalog(); + await seedPiCatalog(); const res = await listModels({ view: "configured" }); expect(res.ok).toBe(true); expect(res.payload?.models).toEqual([ @@ -563,24 +565,24 @@ describe("gateway server models + voicewake", () => { }, }, async () => { - setPiCatalog([ + await setPiCatalog([ { id: "remote-a", provider: "unauth-a", name: "Remote A" }, { id: "remote-b", provider: "unauth-b", name: "Remote B" }, ]); const res = await listModels({ view: "configured" }); expect(res.ok).toBe(true); expect(res.payload?.models).toEqual([ - { + expect.objectContaining({ id: "MiniMax-M2.7-highspeed", name: "MiniMax M2.7 Highspeed", provider: "minimax", - }, - { + }), + expect.objectContaining({ id: "glm-4.5-air", name: "GLM 4.5 Air", provider: "zhipu", reasoning: true, - }, + }), ]); }, ); @@ -607,7 +609,7 @@ describe("gateway server models + voicewake", () => { }, }, async () => { - seedPiCatalog(); + await seedPiCatalog(); const res = await listModels({ view: "configured" }); expect(res.ok).toBe(true); expect(res.payload?.models).toEqual([ @@ -634,7 +636,7 @@ describe("gateway server models + voicewake", () => { }, }, async () => { - seedPiCatalog(); + await seedPiCatalog(); const res = await listModels({ view: "all" }); expect(res.ok).toBe(true); expect(res.payload?.models).toEqual(expectedSortedCatalog()); @@ -708,17 +710,17 @@ describe("gateway server models + voicewake", () => { }, }, async () => { - seedPiCatalog(); + await seedPiCatalog(); const res = await listModels(); expect(res.ok).toBe(true); expect(res.payload?.models).toEqual([ - { + expect.objectContaining({ id: "moonshotai/kimi-k2.5", name: "Kimi K2.5 (Configured)", alias: "Kimi K2.5 (NVIDIA)", provider: "nvidia", contextWindow: 32_000, - }, + }), ]); }, ); @@ -751,17 +753,17 @@ describe("gateway server models + voicewake", () => { }, }, async () => { - seedPiCatalog(); + await seedPiCatalog(); const res = await listModels(); expect(res.ok).toBe(true); expect(res.payload?.models).toEqual([ - { + expect.objectContaining({ id: "gpt-test-z", name: "Configured GPT Test Z", alias: "GPT Test Z Alias", provider: "openai", contextWindow: 64_000, - }, + }), ]); }, ); diff --git a/test/scripts/ci-node-test-plan.test.ts b/test/scripts/ci-node-test-plan.test.ts index 646d262bb07..1999ee462d5 100644 --- a/test/scripts/ci-node-test-plan.test.ts +++ b/test/scripts/ci-node-test-plan.test.ts @@ -17,6 +17,19 @@ type VitestConfig = { }; const PLUGIN_PRERELEASE_NPM_SPEC_TEST = "src/plugins/install.npm-spec.test.ts"; +const GATEWAY_SERVER_BACKED_HTTP_TESTS = new Set([ + "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_TESTS = new Set([ + "src/gateway/gateway.test.ts", + "src/gateway/server.startup-matrix-migration.integration.test.ts", + "src/gateway/sessions-history-http.test.ts", +]); function listTestFiles(rootDir: string): string[] { if (!existsSync(rootDir)) { @@ -53,6 +66,15 @@ function listMatchedTestFiles(config: VitestConfig): string[] { .toSorted((a, b) => a.localeCompare(b)); } +function isGatewayServerTestFile(file: string): boolean { + return ( + file.startsWith("src/gateway/") && + !file.startsWith("src/gateway/server-methods/") && + !GATEWAY_SERVER_EXCLUDED_TESTS.has(file) && + (file.includes("server") || GATEWAY_SERVER_BACKED_HTTP_TESTS.has(file)) + ); +} + describe("scripts/lib/ci-node-test-plan.mjs", () => { it("combines the small core unit shards to reduce CI runner fanout", () => { const coreUnitShards = createNodeTestShards() @@ -172,7 +194,9 @@ describe("scripts/lib/ci-node-test-plan.mjs", () => { it("splits the agentic lane into control-plane, command, agent, gateway, SDK, and plugin shards", () => { const shards = createNodeTestShards(); - const controlPlaneShard = shards.find((shard) => shard.shardName === "agentic-control-plane"); + const controlPlaneShards = shards.filter((shard) => + shard.shardName.startsWith("agentic-control-plane-"), + ); const cliShard = shards.find((shard) => shard.shardName === "agentic-cli"); const commandSupportShard = shards.find( (shard) => shard.shardName === "agentic-command-support", @@ -186,13 +210,30 @@ describe("scripts/lib/ci-node-test-plan.mjs", () => { const pluginSdkShard = shards.find((shard) => shard.shardName === "agentic-plugin-sdk"); const pluginsShard = shards.find((shard) => shard.shardName === "agentic-plugins"); - expect(controlPlaneShard).toEqual({ - checkName: "checks-node-agentic-control-plane", - shardName: "agentic-control-plane", - configs: ["test/vitest/vitest.gateway-server.config.ts"], - runner: "blacksmith-4vcpu-ubuntu-2404", - requiresDist: false, - }); + expect(controlPlaneShards.map((shard) => shard.shardName)).toEqual([ + "agentic-control-plane-agent-chat", + "agentic-control-plane-auth-node", + "agentic-control-plane-http-models", + "agentic-control-plane-runtime", + ]); + expect(controlPlaneShards).toEqual( + controlPlaneShards.map((shard) => ({ + checkName: `checks-node-${shard.shardName}`, + configs: ["test/vitest/vitest.gateway-server.config.ts"], + includePatterns: shard.includePatterns, + requiresDist: false, + runner: "blacksmith-4vcpu-ubuntu-2404", + shardName: shard.shardName, + })), + ); + const controlPlaneShardFiles = controlPlaneShards + .flatMap((shard) => shard.includePatterns ?? []) + .toSorted((a, b) => a.localeCompare(b)); + const expectedControlPlaneFiles = listTestFiles("src/gateway") + .filter(isGatewayServerTestFile) + .toSorted((a, b) => a.localeCompare(b)); + expect(controlPlaneShardFiles).toEqual(expectedControlPlaneFiles); + expect(new Set(controlPlaneShardFiles).size).toBe(controlPlaneShardFiles.length); expect(cliShard).toEqual({ checkName: "checks-node-agentic-cli", shardName: "agentic-cli",