Files
dance-lessons-coach/scripts/run-bdd-tests.sh
Gabriel Radureau 54c1eefb6c feat(bdd): parallel-safe schema-per-package isolation (T12 stage 2/2)
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>
2026-05-03 19:41:20 +02:00

251 lines
10 KiB
Bash
Executable File

#!/bin/bash
# Enhanced BDD Test Runner Script
# Supports subcommands: list-tags, run [tags...]
set -e
SCRIPTS_DIR=$(dirname `realpath ${BASH_SOURCE[0]}`)
cd $SCRIPTS_DIR/..
# Function to list all available tags
list_available_tags() {
echo "🏷️ Available BDD Test Tags"
echo "============================"
echo
# Find all feature files and extract unique tags
echo "Feature Tags:"
grep -h "^@" features/*/*.feature | sort -u | sed 's/^/ /'
echo
echo "Scenario Tags:"
grep -h " @" features/*/*.feature | sort -u | sed 's/^/ /'
echo
echo "📖 See BDD_TAGS.md for detailed tag documentation"
echo "💡 Usage: ./scripts/run-bdd-tests.sh run @smoke @critical"
}
# Function to run tests with specific tags
run_tests_with_tags() {
local tags=""
# Check if any tags were provided
if [ $# -gt 0 ]; then
tags="--tags=$(IFS=,; echo "$*")"
echo "🧪 Running BDD tests with tags: $*"
else
echo "🧪 Running all BDD tests (no tag filtering)"
fi
# Check if we're in CI environment
if [ -n "$GITHUB_ACTIONS" ] || [ -n "$GITEA_ACTIONS" ]; then
# CI environment - PostgreSQL is already running as a service
echo "🏗️ CI environment detected"
echo "🐋 PostgreSQL service is already running"
# Check if database is accessible
echo "📦 Checking PostgreSQL connectivity..."
if ! pg_isready -h postgres -p 5432 -U postgres -d dance_lessons_coach_bdd_test; then
echo "❌ PostgreSQL is not ready or accessible"
exit 1
fi
echo "✅ PostgreSQL is ready!"
else
# Local environment - use docker compose
echo "💻 Local environment detected"
# Check if PostgreSQL container is running, start it if not
echo "🐋 Checking PostgreSQL container..."
if ! docker ps --format '{{.Names}}' | grep -q "^dance-lessons-coach-postgres$"; then
echo "🐋 Starting PostgreSQL container..."
docker compose up -d postgres
# Wait for PostgreSQL to be ready
echo "⏳ Waiting for PostgreSQL to be ready..."
max_attempts=30
attempt=0
while [ $attempt -lt $max_attempts ]; do
if docker exec dance-lessons-coach-postgres pg_isready -U postgres 2>/dev/null; then
echo "✅ PostgreSQL is ready!"
break
fi
attempt=$((attempt + 1))
sleep 1
done
if [ $attempt -eq $max_attempts ]; then
echo "❌ PostgreSQL failed to start"
exit 1
fi
# Create BDD test database (separate from development database)
echo "📦 Creating BDD test database..."
# Drop database if it exists, then create fresh
docker exec dance-lessons-coach-postgres psql -U postgres -c "DROP DATABASE IF EXISTS dance_lessons_coach_bdd_test;"
if docker exec dance-lessons-coach-postgres createdb -U postgres dance_lessons_coach_bdd_test; then
echo "✅ BDD test database created successfully!"
else
echo "❌ Failed to create BDD test database"
exit 1
fi
else
echo "✅ PostgreSQL container is already running"
# Check if BDD test database exists, create if not
echo "📦 Checking BDD test database..."
if docker exec dance-lessons-coach-postgres psql -U postgres -lqt | cut -d \| -f 1 | grep -qw "dance_lessons_coach_bdd_test"; then
echo "✅ BDD test database already exists"
else
echo "📦 Creating BDD test database..."
if docker exec dance-lessons-coach-postgres createdb -U postgres dance_lessons_coach_bdd_test; then
echo "✅ BDD test database created successfully!"
else
echo "❌ Failed to create BDD test database"
exit 1
fi
fi
fi
fi
# Set database environment variables
if [ -z "$GITHUB_ACTIONS" ] && [ -z "$GITEA_ACTIONS" ]; then
echo "🔧 Setting database environment variables for local environment..."
export DLC_DATABASE_HOST="localhost"
export DLC_DATABASE_PORT="5432"
export DLC_DATABASE_USER="postgres"
export DLC_DATABASE_PASSWORD="postgres"
export DLC_DATABASE_NAME="dance_lessons_coach_bdd_test"
export DLC_DATABASE_SSL_MODE="disable"
else
echo "🏗️ CI environment detected, using service configuration"
echo "🔧 Setting database environment variables for CI environment..."
export DLC_DATABASE_HOST="postgres"
export DLC_DATABASE_PORT="5432"
export DLC_DATABASE_USER="postgres"
export DLC_DATABASE_PASSWORD="postgres"
export DLC_DATABASE_NAME="dance_lessons_coach_bdd_test"
export DLC_DATABASE_SSL_MODE="disable"
fi
# Run tests with proper coverage measurement and tag exclusion
set +e
# Default tag filter: exclude flaky, todo, and skip scenarios
DEFAULT_TAGS="~@flaky && ~@todo && ~@skip && ~@v2"
if [ -n "$tags" ]; then
# Use godog directly for tag filtering with exclusion
echo "🚀 Running: godog $tags --tags=~@flaky --tags=~@todo --tags=~@skip features/"
test_output=$(godog $tags --tags=~@flaky --tags=~@todo --tags=~@skip features/ 2>&1)
else
# Use go test for full test suite with GODOG_TAGS environement variable
# Note: -tags flag in go test is for Go build tags, NOT Godog feature tags
# We use GODOG_TAGS env var which is read by the test framework
echo "🚀 Running: GODOG_TAGS=\"${DEFAULT_TAGS}\" go test ./features/..."
# When BDD_SCHEMA_ISOLATION=true (T12 architecture):
# each test PACKAGE gets its own isolated PostgreSQL schema with its own
# connection pool + migrations (cf. pkg/bdd/testserver/server.go Start()).
# Packages then run in parallel safely. ~2.85x speedup observed locally.
# When unset:
# fall back to -p 1 (sequential). Uses public schema with TRUNCATE-style
# cleanup between scenarios.
if [ "${BDD_SCHEMA_ISOLATION:-}" = "true" ]; then
PARALLEL_FLAG=""
echo "🔀 BDD_SCHEMA_ISOLATION=true → feature packages run in parallel"
else
PARALLEL_FLAG="-p 1"
echo "🐌 BDD_SCHEMA_ISOLATION not set → feature packages run sequentially (-p 1)"
fi
GODOG_TAGS="$DEFAULT_TAGS" go test ./features/... -v $PARALLEL_FLAG -cover -coverpkg=./... -coverprofile=coverage.out 2>&1 | tee /tmp/bdd_test_output.txt && test_output=$(cat /tmp/bdd_test_output.txt) && rm -f /tmp/bdd_test_output.txt || test_output=$(cat /tmp/bdd_test_output.txt 2>/dev/null || echo "")
test_exit_code=${PIPESTATUS[0]}
fi
set -e
echo "$test_output"
# Check for undefined steps
if echo "$test_output" | grep -q "undefined"; then
echo "❌ FAILED: Found undefined steps"
if [ -n "$tags" ]; then
echo "Command: godog $tags features/ -v"
else
echo 'DLC_DATABASE_HOST=localhost DLC_DATABASE_PORT=5432 DLC_DATABASE_USER=postgres DLC_DATABASE_PASSWORD=postgres DLC_DATABASE_NAME=dance_lessons_coach_bdd_test DLC_DATABASE_SSL_MODE=disable go test ./features/... -v'
fi
exit 1
fi
# Check for pending steps
if echo "$test_output" | grep -q "pending"; then
echo "❌ FAILED: Found pending steps"
if [ -n "$tags" ]; then
echo "Command: godog $tags features/ -v"
else
echo 'DLC_DATABASE_HOST=localhost DLC_DATABASE_PORT=5432 DLC_DATABASE_USER=postgres DLC_DATABASE_PASSWORD=postgres DLC_DATABASE_NAME=dance_lessons_coach_bdd_test DLC_DATABASE_SSL_MODE=disable go test ./features/... -v'
fi
exit 1
fi
# Check for skipped steps - NO LONGER FAIL on skipped since we use GODOG_TAGS=~@todo by default
# Skipped steps are expected when @todo tagged scenarios are excluded
# if [ -z "$tags" ] && echo "$test_output" | grep -q "skipped"; then
# echo "❌ FAILED: Found skipped steps"
# echo 'DLC_DATABASE_HOST=localhost DLC_DATABASE_PORT=5432 DLC_DATABASE_USER=postgres DLC_DATABASE_PASSWORD=postgres DLC_DATABASE_NAME=dance_lessons_coach_bdd_test DLC_DATABASE_SSL_MODE=disable go test ./features/... -v'
# exit 1
# fi
# Check if tests passed
if [ $test_exit_code -eq 0 ]; then
if [ -n "$tags" ]; then
echo "✅ BDD tests with tags '$*' passed successfully!"
echo "Command: godog $tags features/ -v"
else
echo "✅ All BDD tests passed successfully!"
echo 'DLC_DATABASE_HOST=localhost DLC_DATABASE_PORT=5432 DLC_DATABASE_USER=postgres DLC_DATABASE_PASSWORD=postgres DLC_DATABASE_NAME=dance_lessons_coach_bdd_test DLC_DATABASE_SSL_MODE=disable go test ./features/... -v'
fi
exit 0
else
if [ -n "$tags" ]; then
echo "❌ BDD tests with tags '$*' failed"
echo "Command: godog $tags features/ -v"
else
echo "❌ BDD tests failed"
echo 'DLC_DATABASE_HOST=localhost DLC_DATABASE_PORT=5432 DLC_DATABASE_USER=postgres DLC_DATABASE_PASSWORD=postgres DLC_DATABASE_NAME=dance_lessons_coach_bdd_test DLC_DATABASE_SSL_MODE=disable go test ./features/... -v'
fi
exit 1
fi
}
# Main script logic
if [ $# -eq 0 ]; then
# Default behavior: run all tests
run_tests_with_tags
elif [ "$1" = "list-tags" ]; then
# List available tags
list_available_tags
elif [ "$1" = "run" ]; then
# Run tests with specific tags
shift
run_tests_with_tags "$@"
else
# Unknown command or direct tag specification
echo "❌ Unknown command or invalid arguments"
echo
echo "Usage: $0 [command] [tags...]"
echo
echo "Commands:"
echo " list-tags List all available BDD test tags"
echo " run [tags...] Run tests with specific tags (e.g., @smoke @critical)"
echo " [no arguments] Run all tests (default behavior)"
echo
echo "Examples:"
echo " $0 # Run all tests"
echo " $0 list-tags # List available tags"
echo " $0 run @smoke # Run smoke tests only"
echo " $0 run @smoke @critical # Run smoke and critical tests"
echo " $0 run @auth # Run authentication tests"
exit 1
fi