From d0f22d871ab3403943d6bcfc71bfa5a0e1f01614 Mon Sep 17 00:00:00 2001 From: Hare Date: Sun, 1 Nov 2026 01:54:34 +0900 Subject: [PATCH] fix: bound test shard size independently of concurrency Remove the 32-shard cap so growing inventories do not overfill fixed timeout windows. Keep serial Runtime/Worker/Server shards at most 36 cases and default top-level concurrency at most eight, with explicit --jobs overrides retained. No test exclusions or timeout increases. Validated the complete Server inventory through the normal runner helpers: 1724 passed, 4 existing ignored, no failures or timeouts; 50 suites including doctests, 580 seconds execution. Runner and naming checks: 26 passed. Root cargo check and formatting/diff checks passed. --- docs/development/regression-test-design.md | 2 +- tools/test.test.ts | 16 +++++++++------- tools/test.ts | 13 ++++++++----- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/docs/development/regression-test-design.md b/docs/development/regression-test-design.md index 91164df56..263027dcc 100644 --- a/docs/development/regression-test-design.md +++ b/docs/development/regression-test-design.md @@ -53,7 +53,7 @@ ### runner:選択の帳簿と実行引数を照合する -`tools/test.test.ts` の `round-robin shards retain every listed name exactly once at thresholds and the 32-shard cap` は本番parser/sharderへ実形式のlistingを入力し、全名の一意分配と `task.args.slice(3) === selectedNames`、exact/thread/ignore flagsをassertする。`runtime listing persists complete name inventory for large and small binaries` はDeno子でlibtest listingを代替し、本番discovery/executeの保存inventoryを再読取りする。実Rust libtest全体の実行結果照合ではない。 +`tools/test.test.ts` の `round-robin shards retain every listed name without exceeding the case budget` は本番parser/sharderへ実形式のlistingを入力し、全名の一意分配、分割後のケース数上限と `task.args.slice(3) === selectedNames`、exact/thread/ignore flagsをassertする。`runtime listing persists complete name inventory for large and small binaries` はDeno子でlibtest listingを代替し、本番discovery/executeの保存inventoryを再読取りする。実Rust libtest全体の実行結果照合ではない。 `execute preserves live stdout/stderr and times out a group containing a grandchild`、`execute escalates SIGTERM to SIGKILL and reaps a stubborn direct child`、`parent SIGINT/SIGTERM cleans up active groups, reaps children and does not spawn queued commands` は実隔離子プロセスを起動し、PID消滅・保存result・非起動のqueued PIDを観測する。fixtureに独立watchdogがあり、finallyでも回収する。同process group外へ自ら離脱した子孫は対象外。局所入力表を重いrunner全体起動へ移さない。 diff --git a/tools/test.test.ts b/tools/test.test.ts index bad5ee0c1..d2fca81b6 100644 --- a/tools/test.test.ts +++ b/tools/test.test.ts @@ -106,9 +106,9 @@ function assertThrows(fn: () => unknown) { assert(failed, "expected failure"); } -Deno.test("CPU-derived jobs accommodates concurrent suites within the CLI bound", () => { - assert(defaultJobs(24) === 32); - assert(defaultJobs(128) === 32); +Deno.test("default jobs bound suite pressure independently of explicit CLI concurrency", () => { + assert(defaultJobs(24) === 8); + assert(defaultJobs(128) === 8); assert(defaultJobs(6) === 8); assert(defaultJobs(1) === 2); assert(parseJobs([]) === defaultJobs()); @@ -153,7 +153,7 @@ function listedNames(count: number) { return Array.from({ length: count }, (_, i) => `unit::case_${i}`); } -Deno.test("round-robin shards retain every listed name exactly once at thresholds and the 32-shard cap", () => { +Deno.test("round-robin shards retain every listed name without exceeding the case budget", () => { const task = { name: "pkg/binary", command: "/test-binary", @@ -178,9 +178,10 @@ Deno.test("round-robin shards retain every listed name exactly once at threshold "small binaries must be unchanged", ); } else { - const expected = Math.min(32, Math.ceil(count / 36)); + const expected = Math.ceil(count / 36); assert(shards.length === expected); const lengths = shards.map((shard) => shard.selectedNames.length); + assert(Math.max(...lengths) <= 36); assert(Math.max(...lengths) - Math.min(...lengths) <= 1); shards.forEach((shard, index) => { assert( @@ -212,7 +213,7 @@ Deno.test("global runtime-path packages serialize every normal target without lo args: [], cwd: "/pkg", }; - for (const count of [0, 1, 59, 72, 73, 1135]) { + for (const count of [0, 1, 36, 37, 59, 72, 73, 1135, 1716, 2000]) { const names = listedNames(count); const shards = shardRustCommand(task, names); assert( @@ -222,7 +223,8 @@ Deno.test("global runtime-path packages serialize every normal target without lo for (const shard of shards) { const args = shard.task.args; assert(args[args.indexOf("--test-threads") + 1] === "1"); - if (count > 72) { + assert(shard.selectedNames.length <= 36); + if (count > 36) { assert(args[0] === "--exact"); assert(args.slice(3).join() === shard.selectedNames.join()); } else { diff --git a/tools/test.ts b/tools/test.ts index 35bf8c769..3c399873f 100644 --- a/tools/test.ts +++ b/tools/test.ts @@ -12,9 +12,10 @@ export const BUILD_TIMEOUT_MS = 1_800_000; export const MAX_JOBS = 64; export function defaultJobs(cpus = availableParallelism()): number { - // Leave headroom for internal runners; higher top-level counts can be chosen - // explicitly, but are not assumed to improve throughput or timing stability. - return Math.min(32, Math.max(1, Math.ceil(cpus * 4 / 3))); + // Each suite can spawn its own Runtime, database executors and compiler work. + // CPU count alone over-admits these suites and consumes their timeout budgets. + // Keep headroom by default; --jobs remains an explicit concurrency override. + return Math.min(8, Math.max(1, Math.ceil(cpus * 4 / 3))); } export function parseJobs(args: string[]): number { @@ -71,7 +72,7 @@ export function shardRustCommand( const serial = ["worker", "worker-runtime", "yoi-workspace-server"].includes( task.name.split("/")[0], ); - if (names.length <= 72) { + if (names.length <= (serial ? 36 : 72)) { return [{ task: serial ? { ...task, args: [...task.args, "--test-threads", "1"] } @@ -79,7 +80,9 @@ export function shardRustCommand( selectedNames: [...names], }]; } - const count = Math.min(32, Math.ceil(names.length / 36)); + // Shard size and concurrency are independent: a growing test inventory must + // not silently pack more serial cases into the same fixed timeout window. + const count = Math.ceil(names.length / 36); const shards: string[][] = Array.from({ length: count }, () => []); names.forEach((name, index) => shards[index % count].push(name)); return shards.map((selectedNames, index) => ({