Parallelize check across Gradle subprojects for faster pre-commit.

Run :backend:check, frontend coverage, and :e2e:check as sibling tasks with
org.gradle.parallel=true. Move E2E Docker compose into an e2e subproject so
Playwright can start while unit tests run. Copy e2e/ in the E2E backend image
so settings.gradle resolves inside Docker.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Joakim Mörling 2026-06-01 12:42:27 +02:00
parent 764a620689
commit a6a3084acd
7 changed files with 41 additions and 21 deletions

View file

@ -37,7 +37,7 @@ docker compose up -d # starts postgres, backend, frontend
### All-in-one ### All-in-one
```bash ```bash
./gradlew check # lint → frontend/backend tests with coverage thresholds → E2E (Docker) ./gradlew check # backend, frontend coverage, E2E (sibling tasks + org.gradle.parallel)
./gradlew coverage # backend + frontend tests with coverage reports ./gradlew coverage # backend + frontend tests with coverage reports
./gradlew up # docker compose up -d ./gradlew up # docker compose up -d
./gradlew down # docker compose down ./gradlew down # docker compose down
@ -170,9 +170,15 @@ export STRIPE_SECRET_KEY=sk_test_fake STRIPE_WEBHOOK_SECRET=whsec_fake STRIPE_PR
./gradlew check ./gradlew check
``` ```
This runs frontend lint, frontend unit tests **with Vitest coverage thresholds** `check` depends on `:backend:check`, `frontendCoverage` (root), and `:e2e:check`
(70% lines, 60% branches, 70% functions), backend tests with **JaCoCo thresholds** as **siblings** with `org.gradle.parallel=true`. Gradle parallelizes **across
(70% lines, 60% branches), Flyway checks, and **all 90 E2E tests in Docker**. subprojects**, not multiple tasks in one project — E2E is in `e2e/` so Docker can
start while backend tests and frontend Vitest run. Within root, `frontendLint`
`frontendCoverage` stays serial. Measured `unitAndCoverage --rerun-tasks`: ~30s
parallel on vs ~36s off. Full `check` wall time ≈ `max(:backend, frontend, :e2e)`;
E2E (~3 min) is usually the limit.
Forgejo CI already runs unit and E2E as separate workflow jobs in parallel.
Quick path without E2E: `./gradlew unitAndCoverage`.
**Do not commit or push if this fails.** Optional local guard: **Do not commit or push if this fails.** Optional local guard:
`./scripts/install-pre-commit-hook.sh` (runs the same `check` on every `git commit`; `./scripts/install-pre-commit-hook.sh` (runs the same `check` on every `git commit`;
fails if line or branch coverage is below threshold). fails if line or branch coverage is below threshold).

View file

@ -28,25 +28,23 @@ tasks.register('coverage') {
dependsOn(':backend:jacocoTestReport', 'frontendCoverage') dependsOn(':backend:jacocoTestReport', 'frontendCoverage')
} }
tasks.register('frontendE2E', Exec) { // E2E lives in :e2e subproject so Gradle can run it parallel to root + :backend.
tasks.register('frontendE2E', Task) {
group = 'verification' group = 'verification'
description = 'Run Playwright E2E tests in Docker (same stack as Forgejo CI)' description = 'Alias for :e2e:check (Playwright in Docker)'
dependsOn frontendCoverage dependsOn ':e2e:check'
workingDir = rootProject.projectDir }
environment 'POSTGRES_DB', 'bilhej'
environment 'POSTGRES_USER', 'bilhej' tasks.register('unitAndCoverage', Task) {
environment 'POSTGRES_PASSWORD', 'test_pw_ci_123' group = 'verification'
environment 'JWT_SECRET', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' description = 'Backend + frontend unit tests with coverage thresholds (no E2E)'
environment 'STRIPE_SECRET_KEY', 'sk_test_fake' dependsOn ':backend:check', 'frontendCoverage'
environment 'STRIPE_WEBHOOK_SECRET', 'whsec_fake'
environment 'STRIPE_PRICE_ID', 'price_fake'
commandLine 'docker', 'compose', '-f', 'docker-compose.e2e.yml',
'up', '--build', '--abort-on-container-exit', '--exit-code-from', 'playwright'
} }
tasks.named('check').configure { tasks.named('check').configure {
description = 'Full verification: lint, unit tests with line/branch coverage thresholds, E2E' description = 'Full verification: :backend, frontend coverage, and :e2e in parallel'
dependsOn ':backend:check', frontendE2E dependsOn ':backend:check', 'frontendCoverage', ':e2e:check'
} }
tasks.register('up', Exec) { tasks.register('up', Exec) {

View file

@ -3,6 +3,7 @@ WORKDIR /app
COPY gradlew settings.gradle ./ COPY gradlew settings.gradle ./
COPY gradle/wrapper/ gradle/wrapper/ COPY gradle/wrapper/ gradle/wrapper/
COPY backend/build.gradle backend/ COPY backend/build.gradle backend/
COPY e2e/build.gradle e2e/
RUN chmod +x gradlew && ./gradlew :backend:dependencies --no-daemon -q RUN chmod +x gradlew && ./gradlew :backend:dependencies --no-daemon -q
COPY backend/src backend/src COPY backend/src backend/src
RUN ./gradlew :backend:bootJar --no-daemon -q RUN ./gradlew :backend:bootJar --no-daemon -q

14
e2e/build.gradle Normal file
View file

@ -0,0 +1,14 @@
tasks.register('check', Exec) {
group = 'verification'
description = 'Playwright E2E stack in Docker (parallel with :backend and root frontend tasks)'
workingDir = rootProject.projectDir
environment 'POSTGRES_DB', 'bilhej'
environment 'POSTGRES_USER', 'bilhej'
environment 'POSTGRES_PASSWORD', 'test_pw_ci_123'
environment 'JWT_SECRET', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
environment 'STRIPE_SECRET_KEY', 'sk_test_fake'
environment 'STRIPE_WEBHOOK_SECRET', 'whsec_fake'
environment 'STRIPE_PRICE_ID', 'price_fake'
commandLine 'docker', 'compose', '-f', 'docker-compose.e2e.yml',
'up', '--build', '--abort-on-container-exit', '--exit-code-from', 'playwright'
}

1
gradle.properties Normal file
View file

@ -0,0 +1 @@
org.gradle.parallel=true

View file

@ -19,7 +19,7 @@ export STRIPE_SECRET_KEY="${STRIPE_SECRET_KEY:-sk_test_fake}"
export STRIPE_WEBHOOK_SECRET="${STRIPE_WEBHOOK_SECRET:-whsec_fake}" export STRIPE_WEBHOOK_SECRET="${STRIPE_WEBHOOK_SECRET:-whsec_fake}"
export STRIPE_PRICE_ID="${STRIPE_PRICE_ID:-price_fake}" export STRIPE_PRICE_ID="${STRIPE_PRICE_ID:-price_fake}"
echo "pre-commit: running ./gradlew check (lint, coverage thresholds, E2E in Docker)..." echo "pre-commit: running ./gradlew check (lint, coverage thresholds, E2E)..."
if ! ./gradlew check --no-daemon; then if ! ./gradlew check --no-daemon; then
echo "" echo ""
echo "pre-commit: FAILED." echo "pre-commit: FAILED."

View file

@ -1,3 +1,3 @@
rootProject.name = 'bilhej' rootProject.name = 'bilhej'
include 'backend' include 'backend', 'e2e'