Files
rea/scripts/ci/test-history.mjs
morlutoandCursor Agent ea24207b57 perf(ci): pack each test tier from its own timing history (#5799)
* perf(ci): pack each test tier from its own timing history

Fast shards were still ordered with the latest full-suite timing artifact,
which stayed stale across compiled-CLI session reuse. Publish and download
test-timings-fast and test-timings-full separately, and keep the legacy
artifact as a fallback until a tier has its own history.

* perf(ci): drop the shared timing-history fallback

Shard lookup now reads only the artifact for the current tier. A missing
artifact leaves history unset instead of packing from the old test-timings
file.

* test(ci): look up fast history when only full timings exist

The empty-history case now requests the fast tier against a full-tier
artifact and checks that the fast lookup ran and returned nothing. A
missing tier stays a separate test that does not query.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-10-21 14:58:17 +08:00

143 lines
4.5 KiB
JavaScript

import { execFileSync } from "node:child_process";
import { appendFileSync } from "node:fs";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { normalizeWorkflowRun } from "./workflow-run.mjs";
const TIER_EVENTS = ["push", "schedule", "workflow_dispatch"];
const TIER_PROFILES = {
fast: { artifactName: "test-timings-fast", events: TIER_EVENTS },
full: { artifactName: "test-timings-full", events: TIER_EVENTS },
};
const MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
const MAX_CANDIDATES = 5;
const EMPTY_SELECTION = { runId: "", artifactName: "" };
const positiveId = (value) =>
(typeof value === "string" || typeof value === "number") &&
/^[1-9]\d*$/.test(String(value)) &&
Number.isSafeInteger(Number(value));
function findTimingRun({
repository,
branch,
repositoryId,
tier,
now = Date.now(),
}) {
if (
!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository ?? "") ||
!branch ||
!positiveId(repositoryId)
)
return EMPTY_SELECTION;
const profile = Object.hasOwn(TIER_PROFILES, tier)
? TIER_PROFILES[tier]
: null;
// Reused and other tiers do not publish shard history. Missing history uses
// Vitest's own assignment rather than another tier's artifact.
if (!profile) return EMPTY_SELECTION;
const deadline = Date.now() + 20_000;
const api = (...arguments_) => {
const remaining = deadline - Date.now();
if (remaining <= 0)
throw new Error("Timing history lookup budget exhausted");
return JSON.parse(
execFileSync("gh", ["api", "--method", "GET", ...arguments_], {
encoding: "utf8",
timeout: Math.min(10_000, remaining),
maxBuffer: 4 * 1024 * 1024,
stdio: ["ignore", "pipe", "pipe"],
}),
);
};
const search = ({ artifactName, events }) => {
const { artifacts } = api(
`repos/${repository}/actions/artifacts`,
"-f",
`name=${artifactName}`,
"-F",
"per_page=100",
);
if (!Array.isArray(artifacts))
throw new Error("Missing timing artifact inventory");
const candidates = artifacts
.filter((artifact) => {
const lineage = artifact?.workflow_run;
const age = now - Date.parse(artifact?.created_at);
return (
artifact?.name === artifactName &&
artifact?.expired === false &&
typeof artifact?.created_at === "string" &&
Number.isFinite(age) &&
age >= 0 &&
age < MAX_AGE_MS &&
positiveId(lineage?.id) &&
positiveId(lineage?.repository_id) &&
positiveId(lineage?.head_repository_id) &&
lineage.repository_id === lineage.head_repository_id &&
lineage.repository_id === Number(repositoryId) &&
lineage.head_branch === branch
);
})
.sort(
(left, right) =>
Date.parse(right.created_at) - Date.parse(left.created_at),
)
.slice(0, MAX_CANDIDATES);
for (const artifact of candidates) {
const run = normalizeWorkflowRun(
api(`repos/${repository}/actions/runs/${artifact.workflow_run.id}`),
);
const runAge = now - Date.parse(run?.created_at);
if (
typeof run?.created_at === "string" &&
Number.isFinite(runAge) &&
runAge >= 0 &&
runAge < MAX_AGE_MS &&
run.id === artifact.workflow_run.id &&
run.status === "completed" &&
run.conclusion === "success" &&
run.workflowPath?.file === ".github/workflows/ci.yml" &&
events.includes(run.event) &&
run.head_branch === branch &&
run.head_repository?.full_name === repository
)
return String(run.id);
}
return "";
};
const runId = search(profile);
return runId
? { runId, artifactName: profile.artifactName }
: EMPTY_SELECTION;
}
function write({ runId, artifactName }) {
if (process.env.GITHUB_OUTPUT)
appendFileSync(
process.env.GITHUB_OUTPUT,
`run-id=${runId}\nartifact-name=${artifactName}\n`,
);
console.log(JSON.stringify({ run_id: runId, artifact_name: artifactName }));
}
if (
process.argv[1] &&
fileURLToPath(import.meta.url) === resolve(process.argv[1])
) {
let selection = EMPTY_SELECTION;
try {
selection = findTimingRun({
repository: process.env.CI_REPOSITORY,
branch: process.env.CI_DEFAULT_BRANCH,
repositoryId: process.env.CI_REPOSITORY_ID,
tier: process.env.CI_TIMING_TIER,
});
} catch {
// History improves shard ordering but must never block CI.
}
write(selection);
}