fix(ci): split release chat verification into test shards

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Devin FoleyandPaperclip committed 2026-09-10 19:41:24 -07:00
1 parent 5b0417c88c
commit dd0e0ac59d
6 files changed
+200 -50

No files matched your search

@@ -206,28 +206,16 @@ test("release verify workflow covers the same split test surface as stable PR ve
assert.match(buildJob, /persist-credentials: false/);
assert.doesNotMatch(buildJob, /cache: pnpm/);
for (const group of [
"general-server",
"general-workspaces-a",
"general-workspaces-b",
]) {
for (const group of ["general-server-without-chat", "general-chat", "general-workspaces-a", "general-workspaces-b"]) {
assert.match(verifyWorkflow, new RegExp(`group: ${group}`));
}
for (const shardIndex of [0, 1, 2, 3, 4]) {
assert.match(
verifyWorkflow,
new RegExp(
`group: general-server[\\s\\S]*?shard_index: ${shardIndex}[\\s\\S]*?shard_count: 5`,
),
);
for (const [group, count] of [["general-server-without-chat", 4], ["general-chat", 3]]) {
const rows = [...verifyWorkflow.matchAll(new RegExp(`group: ${group}\\n\\s+group_label: [^\\n]+\\n\\s+shard_index: (\\d+)\\n\\s+shard_count: (\\d+)`, "g"))];
assert.deepEqual(rows.map((row) => [Number(row[1]), Number(row[2])]),
Array.from({ length: count }, (_, index) => [index, count]));
}
for (const shardIndex of [0, 1, 2, 3, 4]) {
assert.match(
verifyWorkflow,
new RegExp(`shard_index: ${shardIndex}[\\s\\S]*?shard_count: 5`),
);
assert.match(verifyWorkflow, new RegExp(`shard_index: ${shardIndex}[\\s\\S]*?shard_count: 5`));
}
// workspaces-a splits with Vitest native --shard in pr.yml; release
@@ -10,6 +10,8 @@ import {
partitionGeneralServerSuites,
} from "../general-server-shard.mjs";
import { assertSelectedTests, partitionTestLines } from "../test-line-shard.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
const script = path.join(repoRoot, "scripts", "run-vitest-stable.mjs");
const durationsManifest = path.join(repoRoot, "scripts", "general-server-shard-durations.json");
@@ -264,3 +266,53 @@ test("the real shard partition is duration-balanced", () => {
`shard weight spread ${maxTotal - minTotal}ms exceeds heaviest suite ${heaviest}ms: ${totals.join(", ")}`,
);
});
test("release server shards plus the dedicated chat file cover the original server group exactly", () => {
const full = dryRunJson(["--mode", "general", "--group", "general-server", "--shard-index", "0", "--shard-count", "1"]);
const shards = Array.from({ length: 4 }, (_, index) => dryRunJson([
"--mode", "general", "--group", "general-server-without-chat",
"--shard-index", String(index), "--shard-count", "4",
]));
const files = shards.flatMap((shard) => shard.selectedGeneralServerSuites);
const chat = "server/src/__tests__/chat-channels.integration.test.ts";
assert.ok(!files.includes(chat));
assert.deepEqual([...files, chat].sort(), full.selectedGeneralServerSuites.sort());
assert.equal(new Set(files).size, files.length);
const defaultRun = dryRunJson([]);
assert.ok(defaultRun.generalServerSuiteCount === full.generalServerSuiteCount);
});
const lineShardFile = path.join(repoRoot, "server/src/__tests__/chat-channels.integration.test.ts");
const caseAt = (line, name) => ({ name, file: lineShardFile, projectName: "@paperclipai/server", location: { line, column: 3 } });
test("test-line shards cover nested and parameterized cases exactly once without splitting a source line", () => {
const cases = [caseAt(10, "suite > nested > first"), caseAt(10, "suite > nested > second"),
caseAt(20, "same name"), caseAt(30, "same name"), caseAt(40, "last"), caseAt(50, "new case")];
const shards = partitionTestLines(cases, 3, lineShardFile);
assert.deepEqual(shards.map((shard) => shard.tests.length), [2, 2, 2]);
assert.equal(shards.filter((shard) => shard.lines.includes(10)).length, 1);
assert.equal(shards.find((shard) => shard.lines.includes(10)).tests.length, 2);
assert.equal(shards.flatMap((shard) => shard.lines).length, 5);
assert.deepEqual(shards.flatMap((shard) => shard.tests).sort((a, b) => a.location.line - b.location.line), cases);
assert.deepEqual(partitionTestLines([...cases].reverse(), 3, lineShardFile).map((shard) => shard.lines), shards.map((shard) => shard.lines));
});
test("line-shard collection rejects empty, foreign, or unlocated tests and invalid shard counts", () => {
const good = caseAt(10, "valid");
for (const input of [[], null, [{ ...good, file: "/another.test.ts" }], [{ ...good, projectName: "wrong" }],
[{ ...good, location: undefined }], [{ ...good, location: { line: 0 } }], [{ ...good, name: "" }]]) {
assert.throws(() => partitionTestLines(input, 1, lineShardFile));
}
for (const count of [0, -1, 1.5, Infinity, 2]) assert.throws(() => partitionTestLines([good], count, lineShardFile));
});
test("filtered collection must match the exact assigned case identities, including duplicates", () => {
const expected = [caseAt(10, "same"), caseAt(10, "same"), caseAt(20, "nested > case")];
assertSelectedTests(expected, [...expected].reverse(), lineShardFile);
for (const actual of [expected.slice(1), [...expected, caseAt(30, "extra")],
[expected[0], expected[1], caseAt(20, "renamed")],
[expected[0], expected[1], caseAt(21, "nested > case")]]) {
assert.throws(() => assertSelectedTests(expected, actual, lineShardFile));
}
});