Skip to content

Commit 2c4d2a7

Browse files
committed
ci(studio): fail a dropped journey and an unreadable base in the work-count ratchet
1 parent 0a1c2fa commit 2c4d2a7

2 files changed

Lines changed: 55 additions & 11 deletions

File tree

‎packages/studio/tests/e2e/perf-ratchet.mjs‎

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -134,18 +134,33 @@ function journeyInputs([ceilingsPath, journey, evidencePath]) {
134134
return { all, ceilingsPath, journey, evidence, counts: evidence.workCounts ?? {} };
135135
}
136136

137-
function runCheck(args) {
138-
const { all, journey, evidence, counts } = journeyInputs(args);
139-
// The base branch's copy of this file; absent while the journey is new there.
140-
const basePath = args[3];
141-
const base = basePath && existsSync(basePath) ? readJson(basePath)[journey]?.counts : undefined;
142-
const { passed, rows } = checkCeilings(all[journey].counts, counts, base);
143-
console.log(`[perf-ratchet] ${journey}: ${passed ? "PASS" : "FAIL"}`);
144-
for (const row of rows) console.log(`[perf-ratchet] ${formatRow(row)}`);
137+
const isObject = (value) => typeof value === "object" && value !== null;
138+
139+
/**
140+
* The base branch's ceilings for `journey`, and the base journeys this file no longer has. A
141+
* base file in a shape this script does not write throws: an unreadable base must not pass.
142+
*/
143+
export function readBase(base, all, journey) {
144+
for (const [name, entry] of Object.entries(base)) {
145+
if (!isObject(entry?.counts)) throw new Error(`base ceilings: "${name}" has no counts object`);
146+
}
147+
return {
148+
counts: base[journey]?.counts,
149+
removedJourneys: Object.keys(base).filter((name) => !Object.hasOwn(all, name)),
150+
};
151+
}
152+
153+
/** Absent while the base branch has no ceilings file yet (and on runs without a base). */
154+
function loadBase(path, all, journey) {
155+
if (!path || !existsSync(path)) return { counts: undefined, removedJourneys: [] };
156+
return readBase(readJson(path), all, journey);
157+
}
158+
159+
function printNotes(recorded, evidence, rows) {
145160
const measuredOn = browserMajor(evidence);
146-
if (measuredOn !== all[journey].browser) {
161+
if (measuredOn !== recorded.browser) {
147162
console.log(
148-
`[perf-ratchet] note: measured on Chrome ${measuredOn}, ceilings recorded on Chrome ${all[journey].browser}`,
163+
`[perf-ratchet] note: measured on Chrome ${measuredOn}, ceilings recorded on Chrome ${recorded.browser}`,
149164
);
150165
}
151166
if (rows.some((row) => row.status === "rose")) {
@@ -154,7 +169,22 @@ function runCheck(args) {
154169
"exactly: run the journey a few times and compare with perf-ratchet.mjs correlate.",
155170
);
156171
}
157-
return passed ? 0 : 1;
172+
}
173+
174+
function runCheck(args) {
175+
const { all, journey, evidence, counts } = journeyInputs(args);
176+
const base = loadBase(args[3], all, journey);
177+
const { passed, rows } = checkCeilings(all[journey].counts, counts, base.counts);
178+
const ok = passed && base.removedJourneys.length === 0;
179+
console.log(`[perf-ratchet] ${journey}: ${ok ? "PASS" : "FAIL"}`);
180+
for (const row of rows) console.log(`[perf-ratchet] ${formatRow(row)}`);
181+
for (const name of base.removedJourneys) {
182+
console.log(
183+
`[perf-ratchet] FAIL journey ${name} removed against the base branch; ceilings only go down`,
184+
);
185+
}
186+
printNotes(all[journey], evidence, rows);
187+
return ok ? 0 : 1;
158188
}
159189

160190
function runLower(args) {

‎packages/studio/tests/e2e/perf-ratchet.test.mjs‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
correlate,
1111
formatRow,
1212
lowerCeilings,
13+
readBase,
1314
} from "./perf-ratchet.mjs";
1415

1516
describe("checkCeilings", () => {
@@ -45,6 +46,19 @@ describe("checkCeilings", () => {
4546
});
4647
});
4748

49+
describe("readBase", () => {
50+
const all = { open: { browser: "153", counts: { a: 1 } } };
51+
52+
it("names base journeys this file dropped", () => {
53+
const base = { open: { counts: { a: 2 } }, scroll: { counts: { b: 1 } } };
54+
expect(readBase(base, all, "open")).toEqual({ counts: { a: 2 }, removedJourneys: ["scroll"] });
55+
});
56+
57+
it("refuses a base file in a shape it cannot compare", () => {
58+
expect(() => readBase({ open: { a: 13 } }, all, "open")).toThrow(/no counts object/);
59+
});
60+
});
61+
4862
describe("browserMajor", () => {
4963
it("reads Chrome's major version from either evidence shape", () => {
5064
expect(browserMajor({ browser: "HeadlessChrome/153.0.8010.52" })).toBe("153");

0 commit comments

Comments
 (0)