History

Bring CI under two minutes

Rhys Sullivan committed 2 months ago

Commit 012f1271 parent cb317864 files · +33 −13Browse files

Unified · Split

4 files
  1. .github/workflows/ci.yml +11 −2
  2. e2e/scripts/ci-shard-durations.ts +1 −1
  3. e2e/scripts/ci-shard.test.ts +17 −7
  4. e2e/scripts/ci-shard.ts +4 −3
.github/workflows/ci.yml+11−2
@@ -201,7 +201,16 @@ jobs: · 200 unmodified lines
201 # durations plus a conservative weight for new tests. Extra shards201 # durations plus a conservative weight for new tests. Extra shards
202 # keep each hermetic stack below the PGlite lifetime failure window.202 # keep each hermetic stack below the PGlite lifetime failure window.
203 - { target: cloud, shard-index: 1, shard-name: 1of16 }203 - { target: cloud, shard-index: 1, shard-name: 1of16 }
204 - { target: cloud, shard-index: 2, shard-name: 2of16 }204 # This shard owns the intentional 65-second human-delay approval
205 # scenario. Extra CPU cannot shorten that assertion, but it does keep
206 # dependency linking and the cloud stack's startup outside the
207 # remaining two-minute wall-clock budget.
208 - {
209 target: cloud,
210 shard-index: 2,
211 shard-name: 2of16,
212 runner: blacksmith-8vcpu-ubuntu-2404,
213 }
205 - { target: cloud, shard-index: 3, shard-name: 3of16 }214 - { target: cloud, shard-index: 3, shard-name: 3of16 }
206 - { target: cloud, shard-index: 4, shard-name: 4of16 }215 - { target: cloud, shard-index: 4, shard-name: 4of16 }
207 - { target: cloud, shard-index: 5, shard-name: 5of16 }216 - { target: cloud, shard-index: 5, shard-name: 5of16 }
@@ -231,7 +240,7 @@ jobs: · 23 unmodified lines
231 # lane from the two-minute critical path.240 # lane from the two-minute critical path.
232 - { target: local, shard-index: 1, shard-name: 1of2 }241 - { target: local, shard-index: 1, shard-name: 1of2 }
233 - { target: local, shard-index: 2, shard-name: 2of2 }242 - { target: local, shard-index: 2, shard-name: 2of2 }
234 runs-on: blacksmith-4vcpu-ubuntu-2404243 runs-on: ${{ matrix.runner || 'blacksmith-4vcpu-ubuntu-2404' }}
235 timeout-minutes: 30244 timeout-minutes: 30
236 steps:245 steps:
237 - uses: actions/checkout@v4246 - uses: actions/checkout@v4
e2e/scripts/ci-shard-durations.ts+1−1
@@ -43,7 +43,7 @@ export const OBSERVED_TEST_DURATIONS_MS: Readonly< · 42 unmodified lines
43 "scenarios/connect-handoff-session.test.ts": 10_296,43 "scenarios/connect-handoff-session.test.ts": 10_296,
44 "scenarios/connect-handoff.test.ts": 7_519,44 "scenarios/connect-handoff.test.ts": 7_519,
45 "scenarios/connection-remove-confirm.test.ts": 5_197,45 "scenarios/connection-remove-confirm.test.ts": 5_197,
46 "scenarios/google-health-checks.test.ts": 20_737,46 "scenarios/google-health-checks.test.ts": 39_421,
47 "scenarios/google-photos-preset-ui.test.ts": 5_696,47 "scenarios/google-photos-preset-ui.test.ts": 5_696,
48 "scenarios/graphql-introspection-health.test.ts": 12_617,48 "scenarios/graphql-introspection-health.test.ts": 12_617,
49 "scenarios/health-checks-ui.test.ts": 71_165,49 "scenarios/health-checks-ui.test.ts": 71_165,
e2e/scripts/ci-shard.test.ts+17−7
@@ -113,17 +113,27 @@ describe("CI shard planning", () => { · 112 unmodified lines
113 );113 );
114 114
115 for (const target of CI_TARGETS) {115 for (const target of CI_TARGETS) {
116 const entries = workflow116 const entries = workflow.matchAll(
117 .split("\n")117 new RegExp(`-\\s*\\{[^}]*target:\\s*${target},[^}]*shard-index:\\s*(\\d+)`, "g"),
118 .filter((line) => line.includes(`{ target: ${target}, shard-index:`));118 );
119 const indexes = entries.map((line) => {119 const indexes = [...entries].map((entry) => entry[1]);
120 const match = /shard-index: (\d+)/.exec(line);
121 return match?.[1];
122 });
123 120
124 expect(indexes).toEqual(121 expect(indexes).toEqual(
125 Array.from({ length: CI_SHARD_COUNTS[target] }, (_, index) => String(index + 1)),122 Array.from({ length: CI_SHARD_COUNTS[target] }, (_, index) => String(index + 1)),
126 );123 );
127 }124 }
125
126 const cloudPlan = await planTargetShards(
127 "cloud",
128 fileURLToPath(new URL("..", import.meta.url)),
129 );
130 const approvalShard = cloudPlan.find((shard) =>
131 shard.files.includes("scenarios/artifact-approval.test.ts"),
132 );
133 expect(approvalShard?.files).toEqual(["scenarios/artifact-approval.test.ts"]);
134 const acceleratedEntry = new RegExp(
135 `-\\s*\\{[^}]*target:\\s*cloud,[^}]*shard-index:\\s*${approvalShard?.index},[^}]*runner:\\s*blacksmith-8vcpu-ubuntu-2404`,
136 );
137 expect(workflow).toMatch(acceleratedEntry);
128 });138 });
129});139});
e2e/scripts/ci-shard.ts+4−3
@@ -60,9 +60,10 @@ const testDirectories: Readonly<Record<CiTarget, readonly string[]>> = { · 59 unmodified lines
60 local: ["local"],60 local: ["local"],
61};61};
62 62
63// A file this long already consumes most of the two-minute budget after the63// A file this long already consumes most of the two-minute budget after
64// target stack boots. Keep it alone instead of balancing unrelated work onto it.64// dependency linking, target startup, and Vitest global setup. Keep it alone
65const ISOLATED_TEST_DURATION_MS = 45_000;65// instead of balancing unrelated work onto it.
66const ISOLATED_TEST_DURATION_MS = 35_000;
66 67
67const comparePaths = (left: string, right: string): number =>68const comparePaths = (left: string, right: string): number =>
68 left < right ? -1 : left > right ? 1 : 0;69 left < right ? -1 : left > right ? 1 : 0;