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.
This commit is contained in:
2026-11-01 01:54:34 +09:00
parent 922e5e3fc3
commit d0f22d871a
3 changed files with 18 additions and 13 deletions
+1 -1
View File
@@ -53,7 +53,7 @@
### runner:選択の帳簿と実行引数を照合する ### 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全体起動へ移さない。 `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全体起動へ移さない。
+9 -7
View File
@@ -106,9 +106,9 @@ function assertThrows(fn: () => unknown) {
assert(failed, "expected failure"); assert(failed, "expected failure");
} }
Deno.test("CPU-derived jobs accommodates concurrent suites within the CLI bound", () => { Deno.test("default jobs bound suite pressure independently of explicit CLI concurrency", () => {
assert(defaultJobs(24) === 32); assert(defaultJobs(24) === 8);
assert(defaultJobs(128) === 32); assert(defaultJobs(128) === 8);
assert(defaultJobs(6) === 8); assert(defaultJobs(6) === 8);
assert(defaultJobs(1) === 2); assert(defaultJobs(1) === 2);
assert(parseJobs([]) === defaultJobs()); assert(parseJobs([]) === defaultJobs());
@@ -153,7 +153,7 @@ function listedNames(count: number) {
return Array.from({ length: count }, (_, i) => `unit::case_${i}`); 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 = { const task = {
name: "pkg/binary", name: "pkg/binary",
command: "/test-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", "small binaries must be unchanged",
); );
} else { } else {
const expected = Math.min(32, Math.ceil(count / 36)); const expected = Math.ceil(count / 36);
assert(shards.length === expected); assert(shards.length === expected);
const lengths = shards.map((shard) => shard.selectedNames.length); const lengths = shards.map((shard) => shard.selectedNames.length);
assert(Math.max(...lengths) <= 36);
assert(Math.max(...lengths) - Math.min(...lengths) <= 1); assert(Math.max(...lengths) - Math.min(...lengths) <= 1);
shards.forEach((shard, index) => { shards.forEach((shard, index) => {
assert( assert(
@@ -212,7 +213,7 @@ Deno.test("global runtime-path packages serialize every normal target without lo
args: [], args: [],
cwd: "/pkg", 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 names = listedNames(count);
const shards = shardRustCommand(task, names); const shards = shardRustCommand(task, names);
assert( assert(
@@ -222,7 +223,8 @@ Deno.test("global runtime-path packages serialize every normal target without lo
for (const shard of shards) { for (const shard of shards) {
const args = shard.task.args; const args = shard.task.args;
assert(args[args.indexOf("--test-threads") + 1] === "1"); 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[0] === "--exact");
assert(args.slice(3).join() === shard.selectedNames.join()); assert(args.slice(3).join() === shard.selectedNames.join());
} else { } else {
+8 -5
View File
@@ -12,9 +12,10 @@ export const BUILD_TIMEOUT_MS = 1_800_000;
export const MAX_JOBS = 64; export const MAX_JOBS = 64;
export function defaultJobs(cpus = availableParallelism()): number { export function defaultJobs(cpus = availableParallelism()): number {
// Leave headroom for internal runners; higher top-level counts can be chosen // Each suite can spawn its own Runtime, database executors and compiler work.
// explicitly, but are not assumed to improve throughput or timing stability. // CPU count alone over-admits these suites and consumes their timeout budgets.
return Math.min(32, Math.max(1, Math.ceil(cpus * 4 / 3))); // 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 { export function parseJobs(args: string[]): number {
@@ -71,7 +72,7 @@ export function shardRustCommand(
const serial = ["worker", "worker-runtime", "yoi-workspace-server"].includes( const serial = ["worker", "worker-runtime", "yoi-workspace-server"].includes(
task.name.split("/")[0], task.name.split("/")[0],
); );
if (names.length <= 72) { if (names.length <= (serial ? 36 : 72)) {
return [{ return [{
task: serial task: serial
? { ...task, args: [...task.args, "--test-threads", "1"] } ? { ...task, args: [...task.args, "--test-threads", "1"] }
@@ -79,7 +80,9 @@ export function shardRustCommand(
selectedNames: [...names], 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 }, () => []); const shards: string[][] = Array.from({ length: count }, () => []);
names.forEach((name, index) => shards[index % count].push(name)); names.forEach((name, index) => shards[index % count].push(name));
return shards.map((selectedNames, index) => ({ return shards.map((selectedNames, index) => ({