Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Unit tests run on `bun test` (repo root, `bunfig.toml`); component and visual te

| Command | What it runs | Budget | Measured |
|---|---|---|---|
| `bun run test` = `test:changed` | what the diff affects: `bun test --changed` (unit); the component tests whose imports reach a changed file (`scripts/test-graph.ts`: a static import graph with the barrel flattened, because Vitest's own `--changed` selects every file in browser mode); the screenshots of those components; the docs routes mapped by `scripts/route-impact.ts`. Prints what it selected and why. Every component test runs when a shared file changed (styles, tokens, theme, stores, primitives, a test setup file). The site part needs a fresh `site/dist` (a build is ~75 s, over the budget) and says so when it skips | 60 s | 28 s for a one-component edit with a fresh `dist` (unit 0.4, components 4.6, screenshots 6.7, routes 16.6; load ~15) |
| `bun run test` = `test:changed` | what the diff affects: `bun test --changed` (unit); the component tests whose imports reach a changed file (`scripts/test-graph.ts`: a static import graph with the barrel flattened, because Vitest's own `--changed` selects every file in browser mode); the screenshots of those components; the docs routes mapped by `scripts/route-impact.ts`. Prints what it selected and why. Every component test runs when a shared file or a test-runtime dependency changed (styles, tokens, theme, stores, primitives, a test setup file, `package.json`, `bun.lock`): the run prints `widened to all components because <file>` and the budget widens with it. The site part needs a fresh `site/dist` (a build is ~75 s, over the budget) and says so when it skips | 60 s narrow; 240 s (`landShared`) when widened to all components (`scripts/test-changed.sh` reads it from `--print-budget`) | 28 s for a one-component edit with a fresh `dist` (unit 0.4, components 4.6, screenshots 6.7, routes 16.6; load ~15). Widened by the happy-dom bump (#588): 95 s at load 10.5 for the full component set (~2190 tests), which is why the narrow 60 s does not apply to it |
| `bun scripts/test-changed.ts --land` | the same plus the package and site builds and 5 canary routes (home, recipes, app-shell, templates/app, theme). This is the gate `issue-land.sh` runs; `validate` runs on the push. The package builds first (the unit tests that read `dist` need it), then the site build runs beside the unit and component tests (the component tests render from `src`). **When a shared file changed** (styles, tokens, theme, stores, primitives, a test setup file, the site layout, or a file the route map cannot place) it runs every component test and a **thinned route sweep** and **no screenshots**, under the larger budget; `--print-budget` says which one applies. The thinned sweep (`E2E_GALLERY=representatives`) keeps 6 gallery items, 3 atlas terms and the fewest docs pages that still render every component folder and subpath (`docsRepresentatives` in `scripts/route-impact.ts`: 81 of 160), plus every page the diff itself changed (`E2E_KEEP`); `test:full` audits all of them. The ~880 social cards are cached by the sha256 of their HTML and the browser version in `$TMPDIR/ca-og-cards` (shared by every worktree, `CA_OG_CACHE_DIR` overrides, cards unused for 30 days are pruned) and rendered four at a time | 180 s; 240 s when a shared file changed | 104 s for a one-component edit (builds 17 + 58, routes 17; load ~15). Shared case at load 7 (components ALL 210 files): 193.5 s with a warm card cache (package 11 + site build 94 beside the tests + routes 88) and 224.8 s with a cold one (site build 125); it was 382.5 s before #598 (unit 8 + components 80 + package 12 + site 134 + routes 148) |
| `bun run validate` | themes, token lint, Biome, unit tests, both type checks, build, size | 90 s | 51 s at load 11 |
| `bun run test:full` | unit, every component test, every route (layout, axe, dist-html, every project including WebKit), fixtures. Release gate, part 1 of 2 | 300 s | components 109 s (load 25) + routes 168 s (load 29) + unit 40 s + fixtures 7 s: over budget on a busy machine, not measured on a quiet one |
Expand Down
6 changes: 1 addition & 5 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
"lint:fix": "biome check --write .",
"validate": "bun scripts/test-run.ts --budget=validate bun scripts/validate.ts",
"postinstall": "node scripts/fix-ts-symlink.js && node scripts/install-hooks.js && bunx playwright install chromium webkit",
"test": "bun scripts/test-run.ts --budget=changed bun scripts/test-changed.ts",
"test:changed": "bun scripts/test-run.ts --budget=changed bun scripts/test-changed.ts",
"test": "sh scripts/test-changed.sh",
"test:changed": "sh scripts/test-changed.sh",
"test:full": "bun scripts/test-run.ts --budget=full sh -c 'bun run test:unit && bun run test:browser && bun run test:fixtures && bun run test:e2e'",
"test:unit": "bun run --cwd packages/ui test:unit && bun run --cwd site test:unit",
"test:browser": "bun run --cwd packages/ui test:browser",
Expand Down
46 changes: 31 additions & 15 deletions packages/ui/tests/test-graph.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { describe, expect, it } from "bun:test";
import { affected, buildGraph, isSharedComponent, landBudget } from "../../../scripts/test-graph";
import {
affected,
buildGraph,
isSharedComponent,
resolveBudget,
widenedBecause,
} from "../../../scripts/test-graph";

// scripts/test-graph.ts selects component tests by a static import graph, because Vitest's `--changed`
// selects every file in browser mode. Too wide a selection defeats it; too narrow hides a regression.
Expand Down Expand Up @@ -70,13 +76,21 @@ describe("test-graph", () => {
});
});

// `issue-land.sh` picks the land budget from this: a shared change runs every component test and every route
// (360 s, no screenshots), anything else the affected ones plus the canaries (180 s).
describe("landBudget", () => {
it("a component edit is under the 180 s budget", () => {
expect(landBudget([`${dir}/Avatar/Avatar.svelte`])).toBe("land");
expect(landBudget([`${dir}/Avatar/Avatar.svelte.test.ts`, "AGENTS.md"])).toBe("land");
expect(landBudget([])).toBe("land");
// The budget follows the scope of the selection. A dependency bump (happy-dom, #588) correctly selects every
// component test, and the full set alone is ~80 s (95 s at load 10.5), over the 60 s `changed` budget that
// assumed a narrow selection. Widened to all components: `landShared`; narrow: `changed` (dev) or `land`.
describe("resolveBudget", () => {
const narrow = [`${dir}/Avatar/Avatar.svelte`];

it("a narrow selection keeps its budget and has no widening reason", () => {
expect(resolveBudget(narrow, false)).toBe("changed");
expect(resolveBudget([`${dir}/Avatar/Avatar.svelte.test.ts`, "AGENTS.md"], false)).toBe(
"changed"
);
expect(resolveBudget([], false)).toBe("changed");
expect(resolveBudget(narrow, true)).toBe("land");
expect(resolveBudget([], true)).toBe("land");
expect(widenedBecause(narrow)).toBeUndefined();
});

it.each([
Expand All @@ -86,16 +100,18 @@ describe("landBudget", () => {
"packages/ui/src/lib/primitives/presence.svelte.ts",
"packages/ui/tests/setup.browser.ts",
"packages/ui/vite.config.ts",
"packages/ui/package.json",
"site/src/layouts/Base.astro",
])("%s is shared: the 360 s budget", (file) => {
expect(landBudget([file])).toBe("landShared");
])("%s widens to all components: landShared, dev run and land gate", (file) => {
for (const land of [false, true])
expect(resolveBudget([...narrow, file], land)).toBe("landShared");
expect(widenedBecause([...narrow, file])).toBe(file);
});

it("one shared file among ordinary ones is enough, and a file the route map does not know is shared", () => {
expect(landBudget([`${dir}/Avatar/Avatar.svelte`, "packages/ui/src/lib/styles/base.css"])).toBe(
"landShared"
);
expect(landBudget(["site/public/some-new-asset.svg"])).toBe("landShared");
it("a file the route map does not know widens too, and the reason names it", () => {
const file = "site/public/some-new-asset.svg";
expect(resolveBudget([file], false)).toBe("landShared");
expect(widenedBecause([...narrow, file])).toBe(file);
});

it("isSharedComponent only flags what every component test renders through", () => {
Expand Down
9 changes: 9 additions & 0 deletions scripts/test-changed.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/sh
# `bun run test` / `test:changed`: the budget follows the selection's scope. A narrow selection keeps the 60 s
# `changed` budget; one widened to every component test (a shared file, a test-runtime dependency like happy-dom)
# runs under `landShared`, and test-changed.ts says why. The budget is read before the clock starts.
# sh scripts/test-changed.sh [test-changed args]
set -e
cd "$(dirname "$0")/.."
BUDGET=$(bun scripts/test-changed.ts --print-budget "$@")
exec bun scripts/test-run.ts --budget="$BUDGET" bun scripts/test-changed.ts "$@"
30 changes: 24 additions & 6 deletions scripts/test-changed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
* A SHARED change (a shared component file, or a route-wide one: styles, tokens, theme, primitives, the
* site layout, a test setup file, or a file the route map cannot place) runs every component test and, when
* it reaches the site, every route, and NO screenshots: those run at release (`test:vrt`) and on demand.
* `--print-budget` prints the budget this selection runs under (`land`, 180 s, or `landShared`, 240 s).
* `--print-budget` prints the budget this selection runs under: `changed` (60 s) for a narrow dev run, `land`
* (180 s) for a narrow land run, `landShared` (240 s) for either when the selection widens to every component
* test (a shared file or a test-runtime dependency such as happy-dom); the run then prints
* "widened to all components because <file>".
*
* unit `bun test --changed <base>` (module graph);
* components the component tests whose imports reach a changed file (scripts/test-graph.ts; Vitest's
Expand All @@ -34,7 +37,14 @@ import { existsSync, readFileSync, statSync } from "node:fs";
import { join } from "node:path";
import { distStale } from "./dist-fresh.ts";
import { lockReachesRuntime, ownRoutes, ROOT, select } from "./route-impact.ts";
import { affected, buildGraph, isSharedComponent, landBudget } from "./test-graph.ts";
import {
affected,
buildGraph,
isSharedComponent,
resolveBudget,
widenedBecause,
} from "./test-graph.ts";
import { BUDGETS } from "./test-run.ts";

if (!import.meta.main) throw new Error("scripts/test-changed.ts is a command, not a module");

Expand Down Expand Up @@ -88,6 +98,13 @@ const changed = [
]),
].filter((f) => f !== "bun.lock" || !lockIsDevOnly());
const short = base.slice(0, 8);
// The budget follows the selection's scope (scripts/test-graph.ts `resolveBudget`): scripts/test-changed.sh and
// `issue-land.sh` read it from here before the clock starts.
const budget = resolveBudget(changed, land);
if (process.argv.includes("--print-budget")) {
console.log(budget);
process.exit(0);
}
if (!changed.length && !land) {
console.log(`test:changed: no changes against origin/main (${short}), nothing to run`);
process.exit(0);
Expand All @@ -103,10 +120,11 @@ const visualRun = sel.all || sel.routes.some((r) => VISUAL_ROUTES.has(r));
const routeRun = sweepAll || routes.length > 0;
// Shared: everything that can reach every test runs, and screenshots are left to `test:vrt`.
const shared = sel.all || sharedFile !== undefined;
if (process.argv.includes("--print-budget")) {
console.log(land ? landBudget(changed) : "changed");
process.exit(0);
}
const widened = widenedBecause(changed);
if (widened !== undefined)
console.log(
`test:changed: widened to all components because ${widened}, so the ${budget} budget applies (${BUDGETS[budget]} s)`
);

console.log(
`test:changed${land ? " --land" : ""}: ${changed.length} changed file(s) against ${short}`
Expand Down
22 changes: 17 additions & 5 deletions scripts/test-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,22 @@ export const isSharedComponent = (file: string) =>
!isTestFile(file) && SHARED_COMPONENT.some((re) => re.test(file));

/**
* The budget `issue-land.sh` runs the land gate under: a SHARED change (a shared component file, or one the
* route map says reaches every page or does not know) runs every component test and every route and gets
* `landShared`; any other change runs what it affects plus the canaries under `land`.
* Why a selection widens to EVERY component test: the first shared file, or the file the route map says reaches
* every page or does not know. `undefined` when the selection is narrow.
*/
export function landBudget(changed: string[]): "land" | "landShared" {
return changed.some(isSharedComponent) || select(changed).all ? "landShared" : "land";
export function widenedBecause(changed: string[]): string | undefined {
const shared = changed.find(isSharedComponent);
if (shared !== undefined) return shared;
const sel = select(changed);
return sel.all ? sel.reason.split(":")[0] : undefined;
}

/**
* The budget a run gets, from the scope of its selection (the budget assumed a narrow one: a dependency bump
* selects every component test, ~80 s alone, 95 s at load 10.5 in #588). Widened to all components: `landShared`
* (dev run and land gate alike). Narrow: `changed` (60 s) for a dev run, `land` (180 s) with the canaries.
*/
export function resolveBudget(changed: string[], land: boolean): "changed" | "land" | "landShared" {
if (widenedBecause(changed) !== undefined) return "landShared";
return land ? "land" : "changed";
}
4 changes: 3 additions & 1 deletion scripts/test-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ export function releaseLock(pid: number) {

/** Wall-time budget in seconds of each command. Raise one only in its own commit, with the measured reason. */
export const BUDGETS = {
/** `bun run test` / `test:changed`: what the diff against origin/main affects */
/** `bun run test` / `test:changed` with a NARROW selection (one that widens to every component test runs under `landShared`, scripts/test-graph.ts `resolveBudget`) */
changed: 60,
/** `bun run validate`: build, types, lint, unit tests, size */
validate: 90,
Expand All @@ -209,6 +209,8 @@ export const BUDGETS = {
* 1044 (#598). Measured at load 7 on 12 cores, a shared change (components ALL 210 files): 224.8 s with a cold card
* cache (package 11 s, then site build 125 s beside the tests, then routes 88 s) and 193.5 s with a warm one (site
* build 94 s). The full sweep stays at release (`test:full`). Do not raise it again: make the run faster.
* A dev run (`bun run test`) widened to every component test gets it too: the full component set alone is ~80 s
* (95 s at load 10.5 on the happy-dom bump #588), over the 60 s `changed` budget that assumed a narrow selection.
*/
landShared: 240,
/** `bun run test:full`: unit, every component test, every route, fixtures (release gate, part 1) */
Expand Down