Files
reverse-engineering-anything/tests/boundary/filesystem/ciTestSequencer.test.ts
T
0add6a5aae ci: split fast and deep static checks (#5634)
* ci: split fast and deep static checks

Fast PRs run typecheck, lint, and format before provider admission.
Knip, duplication, architecture guards, and Python stay on the full tier.
Shared-boundary fallbacks keep the Linux x64 baseline, and Turbo jobs
export remote-cache credentials when they are configured.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* ci: keep pull requests from writing the remote cache

Trusted default-branch runs still receive TURBO_TOKEN. Pull requests get an
empty token and remote read-only mode. Knip saves a per-commit cache key so
the next trusted full run can refresh the restored snapshot.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* ci: forward the release cache token and upload deep static reports

Release qualification now passes TURBO_TOKEN into the called workflow.
Labeled pull requests still do not. The deep static job records its task
context and uploads duplication reports after a failed gate.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* style: format the turbo cache policy test

Co-authored-by: morluto <morluto@users.noreply.github.com>

* ci: keep fast checks exclusive and trusted cache writes explicit

Declare the static artifact name and context script so typecheck matches the
implementations. Lock check:ci:fast around dist, and publish the remote cache
only for release qualification or default-branch push, schedule, and dispatch.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* ci: record static task graphs without nesting the artifacts lock

Keep check:ci:fast locked, and point graph recording at the unlocked
check:ci:fast:tasks script so check:pr and check:test can record hashes
while they already hold that lock.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* ci: keep unauthenticated Turbo runs on the local cache

Open pull requests do not receive TURBO_TOKEN, so a remote read mode cannot
authenticate. Those runs now use the local cache. Trusted runs still publish.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* test(ida): treat unchanged invalid requests as nonretryable

Main already stopped marking invalid_request retryable. The pull request
merge runs that projector, while the IDA lookup tests still expected
retryable true. Match the merged behavior.

Co-authored-by: morluto <morluto@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-21 03:29:31 +08:00

192 lines
5.7 KiB
TypeScript

import { mkdir, writeFile } from "node:fs/promises";
import { join, resolve } from "node:path";
import { fc, it } from "@fast-check/vitest";
import { describe, expect } from "vitest";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import {
allocateByDuration,
estimatedShardCost,
parseTestDurations,
readTestDurations,
weightFileDurations,
} from "../../ciTestSequencer.js";
describe("CI test history", () => {
it("uses only successful in-root file durations", () => {
const root = resolve("/workspace/rea");
const durations = parseTestDurations(
JSON.stringify({
version: 1,
success: true,
files: [
{
name: join(root, "tests/slow.test.ts"),
durationMs: 100,
},
{
name: join(root, "tests/slow.test.ts"),
durationMs: 60,
},
{
name: resolve(root, "../outside.test.ts"),
durationMs: 990,
},
{ name: "tests/negative.test.ts", durationMs: -1 },
{ name: "tests/zero.test.ts", durationMs: 0 },
{ name: "tests/invalid.test.ts", durationMs: "100" },
null,
],
}),
root,
);
expect(durations).toEqual(new Map([["tests/slow.test.ts", 100]]));
expect(
parseTestDurations('{"version": 1, "success": false, "files": []}', root),
).toBeUndefined();
// Legacy test-body spans must not be mixed with complete module costs.
expect(
parseTestDurations(
JSON.stringify({
success: true,
numFailedTests: 0,
testResults: [
{ name: "tests/legacy.test.ts", startTime: 0, endTime: 100 },
],
}),
root,
),
).toBeUndefined();
expect(parseTestDurations("not json", root)).toBeUndefined();
expect(parseTestDurations("null", root)).toBeUndefined();
});
it("falls back for missing history while preserving real filesystem errors", async () => {
const temporaryRoot = await createTestTempDirectory("rea-ci-sequencer-");
const missing = await readTestDurations(
join(temporaryRoot, "missing.json"),
temporaryRoot,
);
expect(missing).toBeUndefined();
const directory = join(temporaryRoot, "directory");
await mkdir(directory);
const reportPath = join(temporaryRoot, "report.json");
await writeFile(reportPath, "{");
expect(await readTestDurations(reportPath, temporaryRoot)).toBeUndefined();
await expect(
readTestDurations(directory, temporaryRoot),
).rejects.toMatchObject({
code: "EISDIR",
});
});
});
describe("duration shard allocation", () => {
it("charges serial process-boundary files their full duration and other projects one worker share", () => {
expect(estimatedShardCost("process-boundary", 400)).toBe(400);
expect(estimatedShardCost("domain", 400)).toBe(200);
expect(estimatedShardCost("adapters", 400)).toBe(200);
});
it("fills unknown files from the raw median before serial and shared weighting", () => {
expect(
weightFileDurations([
{ project: "domain", duration: 400 },
{ project: "process-boundary", duration: undefined },
{ project: "adapters", duration: undefined },
]),
).toEqual([200, 400, 200]);
expect(
weightFileDurations([
{ project: "process-boundary", duration: undefined },
]),
).toEqual([undefined]);
});
it.prop([
fc.uniqueArray(fc.integer({ min: 0, max: 10_000 }), {
minLength: 1,
maxLength: 80,
}),
fc.integer({ min: 3, max: 8 }),
])(
"is complete, disjoint, reference-preserving, and input-order independent",
(ids, shardCount) => {
const items = ids.map((id, index) => ({
id,
path: `tests/${id}.test.ts`,
project: `project-${id % 3}`,
pool: id % 2 === 0 ? "forks" : "threads",
duration: index > 0 && id % 5 === 0 ? undefined : (id % 17) + 1,
}));
const specifications = items.map((item) => ({
item,
key: `${item.path}\0${item.project}\0${item.pool}`,
duration: item.duration,
}));
const shards = allocateByDuration(specifications, shardCount);
const reversedShards = allocateByDuration(
[...specifications].reverse(),
shardCount,
);
expect(shards).toBeDefined();
expect(reversedShards).toEqual(shards);
const assigned = shards!.flat();
expect(assigned).toHaveLength(items.length);
expect(new Set(assigned).size).toBe(items.length);
expect(assigned.every((item) => items.includes(item))).toBe(true);
},
);
it("keeps tied path, project, pool, and new-file entries deterministic", () => {
const items = [
{
id: "a",
path: "tests/shared.test.ts",
project: "alpha",
pool: "forks",
duration: 10,
},
{
id: "b",
path: "tests/shared.test.ts",
project: "beta",
pool: "forks",
duration: 10,
},
{
id: "c",
path: "tests/shared.test.ts",
project: "beta",
pool: "threads",
duration: 10,
},
{
id: "new",
path: "tests/new.test.ts",
project: "alpha",
pool: "forks",
duration: undefined,
},
];
const specifications = items.map((item) => ({
item,
key: `${item.path}\0${item.project}\0${item.pool}`,
duration: item.duration,
}));
const shards = allocateByDuration(specifications, 3);
const reversedShards = allocateByDuration([...specifications].reverse(), 3);
expect(shards).toBeDefined();
expect(reversedShards).toEqual(shards);
expect(shards!.flat()).toHaveLength(items.length);
expect(new Set(shards!.flat()).size).toBe(items.length);
});
});