Skip to content

Commit 4924e4c

Browse files
committed
improvement(ci): reuse E2E builds and rebalance integration runners
1 parent b8be2f0 commit 4924e4c

5 files changed

Lines changed: 74 additions & 51 deletions

File tree

‎.github/scripts/ci-gate.sh‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#
66
# A skip passes only for a gated job whose `changes` flag is exactly `false`:
77
#
8-
# integration, e2e, e2e-build, e2e-mobile full
8+
# integration, e2e, e2e-build, e2e-production full
99
# test, test-types test
1010
# build build
1111
# desktop-live desktop_live
@@ -24,7 +24,7 @@ if ! jq -e 'type == "object"' <<< "$needs" > /dev/null 2>&1; then
2424
fi
2525

2626
failed="$(jq -r '
27-
{"integration": "full", "e2e": "full", "e2e-build": "full", "e2e-mobile": "full", "test": "test",
27+
{"integration": "full", "e2e": "full", "e2e-build": "full", "e2e-production": "full", "test": "test",
2828
"test-types": "test", "build": "build", "desktop-live": "desktop_live"} as $gate
2929
| (.changes.outputs // {}) as $flags
3030
| to_entries[]

‎.github/scripts/http-e2e.sh‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env bash
22
# Runs one end-to-end suite group over real HTTP, each against its own app: `next dev`, except
3-
# mobile, which serves the production build the `e2e-build` job uploads with `next start` (the
4-
# job extracts it into apps/sim/.next first).
3+
# mobile and Project Files in CI, which reuse the production build from `e2e-build`.
4+
# The job extracts it into apps/sim/.next before running these suites.
55
#
66
# Usage: http-e2e.sh <scim|cli|stop-after|desktop-inbox|project-files|mobile> (run from apps/sim)
77
#

‎.github/workflows/checks.yml‎

Lines changed: 57 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,19 @@ jobs:
4646
#
4747
# The two paths build different schemas (migrations add triggers, checks and NOT VALID
4848
# constraints that `db:push` does not), so each runs the whole suite. Vitest splits each
49-
# suite's files across four shards by measured duration (DurationBalancedSequencer in vitest.shared.ts); a
49+
# suite's files across eight shards by measured duration (DurationBalancedSequencer in vitest.shared.ts); a
5050
# shard runs its files one at a time against its own database. Files run serially, so a shard
51-
# barely uses more than one core: 4 vCPU is enough.
52-
name: integration (${{ matrix.provision }}, ${{ matrix.shard }}/4)
51+
# barely uses more than one core: two cores leave room for Postgres and Redis.
52+
name: integration (${{ matrix.provision }}, ${{ matrix.shard }}/8)
5353
needs: changes
5454
if: needs.changes.outputs.full == 'true'
55-
runs-on: &runner-4vcpu ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
55+
runs-on: *runner-2vcpu
5656
timeout-minutes: 20
5757
strategy:
5858
fail-fast: false
5959
matrix:
6060
provision: [push, migrate]
61-
shard: [1, 2, 3, 4]
61+
shard: [1, 2, 3, 4, 5, 6, 7, 8]
6262
services:
6363
redis:
6464
image: redis:8.2-alpine
@@ -137,14 +137,14 @@ jobs:
137137

138138
- name: Run packages/db integration tests
139139
working-directory: packages/db
140-
run: bun run test --mode integration --shard=${{ matrix.shard }}/4
140+
run: bun run test --mode integration --shard=${{ matrix.shard }}/8
141141

142142
- name: Run apps/sim integration tests
143143
working-directory: apps/sim
144144
# A non-UTC process zone keeps timestamp-without-time-zone handling honest.
145145
env:
146146
TZ: America/Los_Angeles
147-
run: bun run test --mode integration --shard=${{ matrix.shard }}/4
147+
run: bun run test --mode integration --shard=${{ matrix.shard }}/8
148148

149149
- name: Run apps/realtime integration tests
150150
if: matrix.shard == 1
@@ -179,7 +179,7 @@ jobs:
179179
# the production (migrate) path. Each group's app environment lives in http-e2e.sh.
180180
#
181181
# SCIM's hosted suites use 8 vCPU; the other self-hosted suites fit on 4. The mobile browser
182-
# suite runs in e2e-mobile below.
182+
# suite runs in e2e-production below.
183183
e2e:
184184
name: e2e (${{ matrix.group }})
185185
needs: changes
@@ -196,8 +196,6 @@ jobs:
196196
runner: blacksmith-4vcpu-ubuntu-2404
197197
- group: stop-after
198198
runner: blacksmith-4vcpu-ubuntu-2404
199-
- group: project-files
200-
runner: blacksmith-8vcpu-ubuntu-2404
201199
- group: desktop-inbox
202200
runner: blacksmith-4vcpu-ubuntu-2404
203201
services:
@@ -243,17 +241,6 @@ jobs:
243241
working-directory: packages/db
244242
run: bun run db:migrate
245243

246-
# JOBS=4 caps node-gyp parallelism as docker/app.Dockerfile does (laverdet/isolated-vm#428).
247-
- name: Build native isolate addon for Project file rendering
248-
if: matrix.group == 'project-files'
249-
working-directory: apps/sim
250-
env:
251-
JOBS: 4
252-
run: |
253-
addon_dir="$(node -p 'require("node:path").dirname(require.resolve("isolated-vm/package.json", { paths: ["."] }))')"
254-
cd "$addon_dir"
255-
bun run install
256-
257244
# No step timeout: the job's bound covers a hang without cutting a slow but healthy suite
258245
# short of writing its report.
259246
- name: Run end-to-end suites
@@ -269,23 +256,34 @@ jobs:
269256
if-no-files-found: ignore
270257
retention-days: 7
271258

272-
# The mobile browser suite against the production build e2e-build uploads, served by
273-
# `next start` (http-e2e.sh), one browser per job: Chromium and WebKit no longer run back to
274-
# back, and no route waits on a dev compile. Gated like `e2e`, on a build that already ran.
259+
# Browser and Project Files suites reuse the production build, avoiding dev-route compiles.
260+
# Each job has its own database and runtime flags; the build reads public env at startup.
275261
#
276262
# 8 vCPU: `next start`, Postgres, the realtime server and one Playwright browser would fit a 4
277263
# vCPU runner's 16 GB, but the suite is timing-sensitive, and CPU contention between the browser
278264
# and the server is what turns a slow tap into a flake.
279-
e2e-mobile:
280-
name: e2e (mobile, ${{ matrix.browser }})
265+
e2e-production:
266+
name: e2e (${{ matrix.name }})
281267
needs: [changes, e2e-build]
282268
if: needs.changes.outputs.full == 'true'
283269
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }}
284270
timeout-minutes: 25
285271
strategy:
286272
fail-fast: false
287273
matrix:
288-
browser: [chromium, webkit]
274+
include:
275+
- group: mobile
276+
name: mobile, chromium
277+
browser: chromium
278+
artifact: mobile-chromium
279+
- group: mobile
280+
name: mobile, webkit
281+
browser: webkit
282+
artifact: mobile-webkit
283+
- group: project-files
284+
name: project-files
285+
browser: ''
286+
artifact: project-files
289287
services:
290288
postgres:
291289
image: pgvector/pgvector:pg17
@@ -300,6 +298,15 @@ jobs:
300298
--health-interval 5s
301299
--health-timeout 5s
302300
--health-retries 10
301+
redis:
302+
image: ${{ matrix.group == 'project-files' && 'redis:8.2-alpine' || '' }}
303+
ports:
304+
- 6379:6379
305+
options: >-
306+
--health-cmd "redis-cli ping"
307+
--health-interval 5s
308+
--health-timeout 5s
309+
--health-retries 10
303310
env:
304311
DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_test
305312
BETTER_AUTH_SECRET: http-e2e-ci-secret-at-least-32-characters
@@ -315,6 +322,7 @@ jobs:
315322
provider: ${{ vars.CI_PROVIDER }}
316323

317324
- name: Provision the database through migrations
325+
if: matrix.group != 'project-files'
318326
working-directory: packages/db
319327
run: bun run db:migrate
320328

@@ -334,17 +342,29 @@ jobs:
334342
apps/sim/.next/prerender-manifest.json > /dev/null ||
335343
{ echo "::error::The build prerendered posts (a hosted build) or has no prerender manifest."; exit 1; }
336344
345+
# JOBS=4 caps node-gyp parallelism as docker/app.Dockerfile does (laverdet/isolated-vm#428).
346+
- name: Build native isolate addon for Project file rendering
347+
if: matrix.group == 'project-files'
348+
working-directory: apps/sim
349+
env:
350+
JOBS: 4
351+
run: |
352+
addon_dir="$(node -p 'require("node:path").dirname(require.resolve("isolated-vm/package.json", { paths: ["."] }))')"
353+
cd "$addon_dir"
354+
bun run install
355+
337356
- name: Run end-to-end suites
338357
working-directory: apps/sim
339358
env:
340359
MOBILE_E2E_BROWSER: ${{ matrix.browser }}
341-
run: bash ../../.github/scripts/http-e2e.sh mobile
360+
PROJECT_FILES_E2E_APP_SERVER: start
361+
run: bash ../../.github/scripts/http-e2e.sh "${{ matrix.group }}"
342362

343363
- name: Upload end-to-end reports and server logs
344-
if: failure()
364+
if: always()
345365
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
346366
with:
347-
name: http-e2e-mobile-${{ matrix.browser }}
367+
name: http-e2e-${{ matrix.artifact }}
348368
path: ${{ runner.temp }}/e2e/
349369
if-no-files-found: ignore
350370
retention-days: 7
@@ -612,7 +632,7 @@ jobs:
612632
# kept off the test shards so neither waits on the other.
613633
lint:
614634
name: lint
615-
runs-on: *runner-4vcpu
635+
runs-on: &runner-4vcpu ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
616636
timeout-minutes: 15
617637

618638
steps:
@@ -931,7 +951,7 @@ jobs:
931951
TURBO_CACHE_DIR: .turbo
932952
run: bunx turbo run build --filter=@sim/app
933953

934-
# The production build e2e-mobile serves with `next start`, built in the public-env mode
954+
# The production build e2e-production serves with `next start`, built in the public-env mode
935955
# production images use: NEXT_PUBLIC_APP_URL is docker/app.Dockerfile's localhost default, so
936956
# every route is dynamic and NEXT_PUBLIC_* are read at start. (Images run the standalone
937957
# DOCKER_BUILD server; this is the plain `next build` output.) The `build` job above keeps the
@@ -974,24 +994,24 @@ jobs:
974994
AWS_REGION: 'us-west-2'
975995
ENCRYPTION_KEY: '0000000000000000000000000000000000000000000000000000000000000000' # dummy key for CI only
976996
TURBO_CACHE_DIR: .turbo
977-
# This build is on the e2e-mobile critical path; its ~30 s TypeScript pass repeats what
997+
# This build is on the e2e-production critical path; its ~30 s TypeScript pass repeats what
978998
# lint's type-check and the `build` job's own `next build` (generated route types too)
979999
# already enforce.
9801000
NEXT_BUILD_SKIP_TYPE_CHECK: 'true'
9811001
run: bunx turbo run build --filter=@sim/app
9821002

983-
# Everything `next start` reads; node_modules and the source tree come from e2e-mobile's own
1003+
# Everything `next start` reads; node_modules and the source tree come from e2e-production's own
9841004
# checkout and setup, at the same workspace path and lockfile. Source maps are over half the
9851005
# build and only serve stack traces and devtools, which the suite never reads.
986-
- name: Package the build for e2e-mobile
1006+
- name: Package the build for e2e-production
9871007
run: >-
9881008
tar -I 'zstd -T0' -cf "$RUNNER_TEMP/next-build.tar.zst" -C apps/sim
9891009
--exclude .next/cache --exclude .next/trace --exclude '*.map' .next
9901010
9911011
# Already zstd-compressed, so stored without zip compression. `overwrite` lets a re-run of
9921012
# this job replace the earlier attempt's build; three days covers re-running a failed shard
9931013
# after a weekend.
994-
- name: Upload the build for e2e-mobile
1014+
- name: Upload the build for e2e-production
9951015
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
9961016
with:
9971017
name: next-build
@@ -1011,7 +1031,7 @@ jobs:
10111031
ci:
10121032
name: ci
10131033
needs:
1014-
[changes, integration, e2e, e2e-mobile, desktop-live, lint, test-types, test, build, e2e-build]
1034+
[changes, integration, e2e, e2e-production, desktop-live, lint, test-types, test, build, e2e-build]
10151035
if: ${{ always() }}
10161036
runs-on: *runner-2vcpu
10171037
timeout-minutes: 5

‎apps/sim/scripts/test-project-files-e2e.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ function requiredString(value: unknown): string {
2525
assert.ok(typeof value === 'string' && value.length > 0, 'Fixture response field is missing')
2626
return value
2727
}
28+
const appServer = process.env.PROJECT_FILES_E2E_APP_SERVER ?? 'dev'
29+
assert.ok(appServer === 'dev' || appServer === 'start', 'App server must be dev or start')
2830
const reportPath = resolve(required('PROJECT_FILES_E2E_REPORT_PATH'))
2931
const reportDir = dirname(reportPath)
3032
const adminUrl = new URL(required('PROJECT_FILES_E2E_ADMIN_DATABASE_URL'))
@@ -76,7 +78,8 @@ const environment: NodeJS.ProcessEnv = {
7678
HOME: process.env.HOME,
7779
TMPDIR: process.env.TMPDIR,
7880
SYSTEMROOT: process.env.SYSTEMROOT,
79-
NODE_ENV: 'development',
81+
NODE_ENV: appServer === 'start' ? 'production' : 'development',
82+
LOG_LEVEL: 'INFO',
8083
__NEXT_PROCESSED_ENV: 'true',
8184
DATABASE_URL: databaseUrl.toString(),
8285
MIGRATION_DATABASE_URL: databaseUrl.toString(),
@@ -394,7 +397,7 @@ try {
394397
[
395398
'node',
396399
join(rootDir, 'node_modules/next/dist/bin/next'),
397-
'dev',
400+
appServer,
398401
'--hostname',
399402
base.hostname,
400403
'--port',

‎scripts/ci-gate.test.ts‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ type Job =
1010
| 'integration'
1111
| 'e2e'
1212
| 'e2e-build'
13-
| 'e2e-mobile'
13+
| 'e2e-production'
1414
| 'desktop-live'
1515
| 'lint'
1616
| 'test'
@@ -22,7 +22,7 @@ const GATED: Job[] = [
2222
'integration',
2323
'e2e',
2424
'e2e-build',
25-
'e2e-mobile',
25+
'e2e-production',
2626
'desktop-live',
2727
'test',
2828
'test-types',
@@ -101,7 +101,7 @@ describe('CI gate', () => {
101101
results: skipGated,
102102
reported: [
103103
'e2e-build: skipped',
104-
'e2e-mobile: skipped',
104+
'e2e-production: skipped',
105105
'e2e: skipped',
106106
'integration: skipped',
107107
],
@@ -132,7 +132,7 @@ describe('CI gate', () => {
132132
'build: skipped',
133133
'desktop-live: skipped',
134134
'e2e-build: skipped',
135-
'e2e-mobile: skipped',
135+
'e2e-production: skipped',
136136
'e2e: skipped',
137137
'integration: skipped',
138138
'test-types: skipped',
@@ -147,7 +147,7 @@ describe('CI gate', () => {
147147
'build: skipped',
148148
'desktop-live: skipped',
149149
'e2e-build: skipped',
150-
'e2e-mobile: skipped',
150+
'e2e-production: skipped',
151151
'e2e: skipped',
152152
'integration: skipped',
153153
'test-types: skipped',
@@ -163,7 +163,7 @@ describe('CI gate', () => {
163163
'changes: failure',
164164
'desktop-live: skipped',
165165
'e2e-build: skipped',
166-
'e2e-mobile: skipped',
166+
'e2e-production: skipped',
167167
'e2e: skipped',
168168
'integration: skipped',
169169
'test-types: skipped',
@@ -179,8 +179,8 @@ describe('CI gate', () => {
179179
{
180180
name: 'a mobile skip after a failed e2e build, though its flag is true',
181181
outputs: { ...ALL_FALSE, full: 'true', build: 'true' },
182-
results: { 'e2e-build': 'failure', 'e2e-mobile': 'skipped' },
183-
reported: ['e2e-build: failure', 'e2e-mobile: skipped'],
182+
results: { 'e2e-build': 'failure', 'e2e-production': 'skipped' },
183+
reported: ['e2e-build: failure', 'e2e-production: skipped'],
184184
},
185185
{
186186
name: 'a failed job whose flag is false',

0 commit comments

Comments
 (0)