fix(ci): per-package timing files, acp flake timeout, 4040-guard marker
- ci-test-shard: timing outputFile is now RELATIVE — one pnpm invocation fans out to several packages whose vitests all received the same absolute path, so every package overwrote the same timings file (last writer wins). Each package now writes <pkgDir>/.timings/; discovery (discoverWorkspaceTimingFiles) and the CI artifact globs scan the tree - acp event-bridge-bounds: 20s timeout on the CPU-bound plan-flood test (timed out at default 5s under loaded CI shard, passes in isolation) - acp process-manager: port-4040-allowlist marker for its doc comments (main-side; local guard flagged it after merging main)
This commit is contained in:
8
.github/workflows/pr-checks.yml
vendored
8
.github/workflows/pr-checks.yml
vendored
@@ -96,7 +96,13 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-timings-shard-${{ matrix.shard }}
|
||||
path: .timings/timings-*.json
|
||||
# Relative outputFile paths mean each package writes its own
|
||||
# <pkgDir>/.timings/ file — glob the whole tree, not just the root.
|
||||
path: |
|
||||
.timings/timings-*.json
|
||||
packages/*/.timings/timings-*.json
|
||||
plugins/*/.timings/timings-*.json
|
||||
plugins/examples/*/.timings/timings-*.json
|
||||
if-no-files-found: ignore
|
||||
retention-days: 14
|
||||
|
||||
|
||||
@@ -203,7 +203,10 @@ describe("plan output bounds (S5)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("a plan-ONLY stream stops emitting once the per-turn cap is crossed", async () => {
|
||||
// Generous timeout: this test does CPU-bound string flooding (~25 plan
|
||||
// events x 100 entries x 2k chars) and has timed out at the default 5s
|
||||
// under loaded CI shards while passing easily in isolation.
|
||||
it("a plan-ONLY stream stops emitting once the per-turn cap is crossed", { timeout: 20_000 }, async () => {
|
||||
const { createEventBridge, PER_CHUNK_CAP_CHARS, PER_TURN_OUTPUT_CAP_CHARS, MAX_PLAN_ENTRIES } =
|
||||
await import("../event-bridge.js");
|
||||
const thinking: string[] = [];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// port-4040-allowlist: doc comments below reference the "never kill port 4040" rule; no kill targets it.
|
||||
// Subprocess lifecycle for the ACP runtime.
|
||||
//
|
||||
// Mirrors the hardening conventions in
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
buildTimingsSnapshot,
|
||||
writeTimings,
|
||||
TIMINGS_SNAPSHOT_RELATIVE,
|
||||
discoverWorkspaceTimingFiles,
|
||||
} from "../ci-test-shard.mjs";
|
||||
|
||||
const PACKAGES = [
|
||||
@@ -203,3 +204,23 @@ test("writeTimings warns and does not write when there are no input files", () =
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("discoverWorkspaceTimingFiles finds root and per-package .timings files", (t) => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "wts-discover-"));
|
||||
t.after(() => rmSync(root, { recursive: true, force: true }));
|
||||
mkdirSync(path.join(root, ".timings"), { recursive: true });
|
||||
mkdirSync(path.join(root, "packages/aaa/.timings"), { recursive: true });
|
||||
mkdirSync(path.join(root, "plugins/bbb/.timings"), { recursive: true });
|
||||
mkdirSync(path.join(root, "packages/no-timings-here"), { recursive: true });
|
||||
writeFileSync(path.join(root, ".timings/timings-shard1-0.json"), "{}");
|
||||
writeFileSync(path.join(root, "packages/aaa/.timings/timings-shard1-0.json"), "{}");
|
||||
writeFileSync(path.join(root, "plugins/bbb/.timings/timings-shard2-0.json"), "{}");
|
||||
writeFileSync(path.join(root, "packages/aaa/.timings/not-a-match.txt"), "");
|
||||
|
||||
const found = discoverWorkspaceTimingFiles(root).map((f) => path.relative(root, f));
|
||||
assert.deepEqual(found.sort(), [
|
||||
".timings/timings-shard1-0.json",
|
||||
"packages/aaa/.timings/timings-shard1-0.json",
|
||||
"plugins/bbb/.timings/timings-shard2-0.json",
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -964,6 +964,27 @@ export function discoverTimingFiles(dir) {
|
||||
.sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover timing files across the whole workspace: the root .timings/ dir
|
||||
* plus every package/plugin's own .timings/ dir (shard runs emit RELATIVE
|
||||
* outputFile paths, so each package writes under its own directory — see
|
||||
* the timingFlags comment in main()).
|
||||
*
|
||||
* @param {string} projectRoot
|
||||
* @returns {string[]} Absolute paths, sorted.
|
||||
*/
|
||||
export function discoverWorkspaceTimingFiles(projectRoot) {
|
||||
const dirs = [
|
||||
path.join(projectRoot, ".timings"),
|
||||
...globSync("{packages,plugins,plugins/examples}/*/.timings", { cwd: projectRoot }).map((d) =>
|
||||
path.join(projectRoot, d),
|
||||
),
|
||||
];
|
||||
const files = new Set();
|
||||
for (const dir of dirs) for (const f of discoverTimingFiles(dir)) files.add(f);
|
||||
return [...files].sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge per-shard JSON reporter outputs into the committed snapshot.
|
||||
* Refuses to overwrite a snapshot whose capturedAt is newer than this run's.
|
||||
@@ -975,7 +996,9 @@ export function writeTimings(options = {}) {
|
||||
const projectRoot = options.projectRoot ?? process.cwd();
|
||||
const snapshotPath = options.snapshotPath ?? path.join(projectRoot, TIMINGS_SNAPSHOT_RELATIVE);
|
||||
const inputs = options.inputs
|
||||
?? discoverTimingFiles(options.inputDir ?? path.join(projectRoot, ".timings"));
|
||||
?? (options.inputDir
|
||||
? discoverTimingFiles(options.inputDir)
|
||||
: discoverWorkspaceTimingFiles(projectRoot));
|
||||
|
||||
if (inputs.length === 0) {
|
||||
console.warn("[ci-test-shard] no timing input files found; snapshot unchanged.");
|
||||
@@ -1236,11 +1259,16 @@ export function main(argv = process.argv.slice(2), env = process.env) {
|
||||
// Per-shard timing telemetry (U1 / R4): each test invocation also emits a
|
||||
// vitest JSON reporter file under .timings/. These are uploaded as CI
|
||||
// artifacts and consumed by `--write-timings` to refresh the snapshot.
|
||||
const timingsDir = path.join(process.cwd(), ".timings");
|
||||
mkdirSync(timingsDir, { recursive: true });
|
||||
//
|
||||
// The path MUST be RELATIVE: one pnpm invocation can fan out to several
|
||||
// packages, each spawning its own vitest with these identical forwarded
|
||||
// flags. A relative path resolves against each package's cwd, giving every
|
||||
// package its own <pkgDir>/.timings/ file; an absolute path would make all
|
||||
// packages in the invocation overwrite the same file (last writer wins,
|
||||
// silently dropping every other package's timings).
|
||||
let invocationIndex = 0;
|
||||
const timingFlags = () => {
|
||||
const outputFile = path.join(timingsDir, `timings-shard${shard}-${invocationIndex++}.json`);
|
||||
const outputFile = path.join(".timings", `timings-shard${shard}-${invocationIndex++}.json`);
|
||||
return ["--reporter=json", `--outputFile.json=${outputFile}`];
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user