feat(bdd): parallel-safe schema-per-package isolation (T12 stage 2/2) — 2.85x speedup #35

Merged
arcodange merged 1 commits from feat/bdd-parallel-isolation-stage2 into main 2026-05-03 19:42:10 +02:00
Owner

Summary

Re-enables BDD_SCHEMA_ISOLATION=true with the foundation merged in PR #34. Achieves 2.85x speedup on the BDD test suite by running feature packages in parallel.

This is the proper architectural answer to the user's request 2026-05-03:

"Il faut pouvoir déclencher des tests BDD en parallèles pour que le projet puisse grandir et que sa taille n'ait pas trop d'impact sur l'expérience du développement."

Architecture

When BDD_SCHEMA_ISOLATION=true, each test PACKAGE (process) gets its own isolated PostgreSQL schema:

  1. testserver.Start() generates a deterministic schema name from FEATURE env
  2. CREATE SCHEMA <name> via the bootstrap repo
  3. Open per-package gorm.DB with DSN search_path=<name>
  4. AutoMigrate runs in the isolated schema (creates the users table)
  5. Build per-package server.Server via server.NewServerWithUserRepo injecting the isolated repo + user service
  6. Stop() drops the schema + closes the per-package pool

Packages then run in parallel (default Go test parallelism, -p ≈ NumCPU) without contention because each has its own schema + connection pool.

Why per-PACKAGE and not per-SCENARIO

The previous attempt (PR #26, reverted in PR #28) targeted per-scenario isolation but ran into:

  • Empty schemas (no migrations) causing 500 errors
  • The production server's repo was unaware of the testserver's schema

Per-package isolation is simpler and sufficient for parallel go test ./features/... because each test package runs in its own process. Per-scenario in-process isolation would require dynamic repo replacement mid-process — much more invasive and not needed for current scaling goals.

If we ever need per-scenario isolation, the building blocks are ready (NewPostgresRepositoryFromDSN + BuildSchemaIsolatedDSN from PR #34, NewServerWithUserRepo here, Exec helper here).

Changes

  • pkg/server/server.go: NEW factory NewServerWithUserRepo(cfg, ctx, userRepo, userService). Existing NewServer becomes a thin wrapper.
  • pkg/bdd/testserver/server.go: Start() takes the isolated path when BDD_SCHEMA_ISOLATION=true. Stop() cleans up.
  • pkg/user/postgres_repository.go: Exec(sql) helper for schema lifecycle.
  • scripts/run-bdd-tests.sh: -p 1 only when BDD_SCHEMA_ISOLATION!=true.
  • .gitea/workflows/ci-cd.yaml: exports BDD_SCHEMA_ISOLATION=true.
  • adr/0025-bdd-scenario-isolation-strategies.md: Status updated to Implemented.

Validation

5 consecutive runs of AuthBDD with isolation enabled:

Run 1: PASS
Run 2: PASS
Run 3: PASS
Run 4: PASS
Run 5: PASS
==> 5/5
public.users count after runs: 0  ← isolation confirmed

Local benchmark on the full features/... suite:

Mode Time CPU%
Sequential -p 1 (no isolation) 12.87s 80%
Parallel + isolation (this PR) 4.51s 311%

Speedup: 2.85x on the local 4-core dev machine. CI runners with more cores should see better.

Test plan

  • go build ./... PASS
  • go test ./pkg/... PASS
  • 5/5 AuthBDD runs pass with isolation enabled
  • time go test ./features/... ≈ 4.5s with isolation vs 12.9s sequential
  • CI passes with BDD_SCHEMA_ISOLATION=true

Out of scope

  • Per-scenario isolation within a process (not needed for current scaling)
  • Auto-detect FEATURE name (currently relies on FEATURE env var, falls back to bdd)

🤖 Co-Authored-By: Claude Opus 4.7 (1M context)

## Summary Re-enables `BDD_SCHEMA_ISOLATION=true` with the foundation merged in PR #34. Achieves **2.85x speedup** on the BDD test suite by running feature packages in parallel. This is the proper architectural answer to the user's request 2026-05-03: > "Il faut pouvoir déclencher des tests BDD en parallèles pour que le projet puisse grandir et que sa taille n'ait pas trop d'impact sur l'expérience du développement." ## Architecture When `BDD_SCHEMA_ISOLATION=true`, each test PACKAGE (process) gets its own isolated PostgreSQL schema: 1. `testserver.Start()` generates a deterministic schema name from `FEATURE` env 2. `CREATE SCHEMA <name>` via the bootstrap repo 3. Open per-package `gorm.DB` with DSN `search_path=<name>` 4. `AutoMigrate` runs in the isolated schema (creates the `users` table) 5. Build per-package `server.Server` via `server.NewServerWithUserRepo` injecting the isolated repo + user service 6. `Stop()` drops the schema + closes the per-package pool Packages then run in parallel (default Go test parallelism, `-p` ≈ NumCPU) without contention because each has its own schema + connection pool. ## Why per-PACKAGE and not per-SCENARIO The previous attempt (PR #26, reverted in PR #28) targeted per-scenario isolation but ran into: - Empty schemas (no migrations) causing 500 errors - The production server's repo was unaware of the testserver's schema Per-package isolation is **simpler and sufficient** for parallel `go test ./features/...` because each test package runs in its own process. Per-scenario in-process isolation would require dynamic repo replacement mid-process — much more invasive and not needed for current scaling goals. If we ever need per-scenario isolation, the building blocks are ready (`NewPostgresRepositoryFromDSN` + `BuildSchemaIsolatedDSN` from PR #34, `NewServerWithUserRepo` here, `Exec` helper here). ## Changes - `pkg/server/server.go`: NEW factory `NewServerWithUserRepo(cfg, ctx, userRepo, userService)`. Existing `NewServer` becomes a thin wrapper. - `pkg/bdd/testserver/server.go`: `Start()` takes the isolated path when `BDD_SCHEMA_ISOLATION=true`. `Stop()` cleans up. - `pkg/user/postgres_repository.go`: `Exec(sql)` helper for schema lifecycle. - `scripts/run-bdd-tests.sh`: `-p 1` only when `BDD_SCHEMA_ISOLATION!=true`. - `.gitea/workflows/ci-cd.yaml`: exports `BDD_SCHEMA_ISOLATION=true`. - `adr/0025-bdd-scenario-isolation-strategies.md`: Status updated to **Implemented**. ## Validation 5 consecutive runs of AuthBDD with isolation enabled: ``` Run 1: PASS Run 2: PASS Run 3: PASS Run 4: PASS Run 5: PASS ==> 5/5 public.users count after runs: 0 ← isolation confirmed ``` Local benchmark on the full `features/...` suite: | Mode | Time | CPU% | |---|---|---| | Sequential `-p 1` (no isolation) | **12.87s** | 80% | | Parallel + isolation (this PR) | **4.51s** | 311% | **Speedup: 2.85x** on the local 4-core dev machine. CI runners with more cores should see better. ## Test plan - [x] `go build ./...` PASS - [x] `go test ./pkg/...` PASS - [x] 5/5 AuthBDD runs pass with isolation enabled - [x] `time go test ./features/...` ≈ 4.5s with isolation vs 12.9s sequential - [ ] CI passes with `BDD_SCHEMA_ISOLATION=true` ## Out of scope - Per-scenario isolation within a process (not needed for current scaling) - Auto-detect FEATURE name (currently relies on `FEATURE` env var, falls back to `bdd`) 🤖 Co-Authored-By: Claude Opus 4.7 (1M context)
arcodange added 1 commit 2026-05-03 19:41:58 +02:00
Re-enables BDD_SCHEMA_ISOLATION=true with the foundation from PR #34
(NewPostgresRepositoryFromDSN). Achieves ~2.85x speedup on the BDD
test suite by running feature packages in parallel.

ARCHITECTURE

When BDD_SCHEMA_ISOLATION=true, each test package (process) gets its
own isolated PostgreSQL schema:
  1. testserver.Start() generates a deterministic schema name per FEATURE
  2. CREATE SCHEMA <name>
  3. Open a per-package gorm.DB with DSN search_path=<name>
  4. AutoMigrate runs in the isolated schema (creates users table)
  5. Build a per-package server.Server with this isolated repo via
     server.NewServerWithUserRepo
  6. Stop() drops the schema + closes the per-package pool

Packages then run in parallel (default Go test parallelism) without
contention because each has its own schema + connection pool.

CHANGES

- pkg/server/server.go : NEW factory NewServerWithUserRepo(cfg, ctx,
  userRepo, userService) that injects a per-test repo. Existing NewServer
  becomes a thin wrapper.
- pkg/bdd/testserver/server.go : Start() chooses isolated mode based on
  BDD_SCHEMA_ISOLATION env var. Stop() drops schema + closes pool.
- pkg/user/postgres_repository.go : Exec(sql) helper for the schema
  lifecycle (CREATE/DROP) used by testserver.
- scripts/run-bdd-tests.sh : -p 1 only when BDD_SCHEMA_ISOLATION!=true.
  When true, default Go parallelism (~ NumCPU packages concurrent).
- .gitea/workflows/ci-cd.yaml : exports BDD_SCHEMA_ISOLATION=true.
- adr/0025-bdd-scenario-isolation-strategies.md : Status to "Implemented".

VALIDATION

5x AuthBDD with isolation: 5/5 PASS, public.users count=0 after runs.

Local benchmark on the full features/... suite:
- Sequential -p 1 (no isolation):     12.87s
- Parallel + isolation (this PR):      4.51s
- Speedup: 2.85x

🤖 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
arcodange merged commit 82feaec51f into main 2026-05-03 19:42:10 +02:00
arcodange deleted branch feat/bdd-parallel-isolation-stage2 2026-05-03 19:42:11 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: arcodange/dance-lessons-coach#35