Baseline spec introduction #182
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Examples CI | |
| on: | |
| push: | |
| branches: [ main ] | |
| pull_request: | |
| branches: [ main ] | |
| paths: | |
| - 'examples/**' | |
| - '.github/workflows/examples-ci.yml' | |
| # Allow manual trigger | |
| workflow_dispatch: | |
| env: | |
| GO_VERSION: '^1.25' | |
| jobs: | |
| validate-examples: | |
| name: Validate Examples | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| example: | |
| - basic-app | |
| - reverse-proxy | |
| - http-client | |
| - advanced-logging | |
| - multi-tenant-app | |
| - instance-aware-db | |
| - verbose-debug | |
| - feature-flag-proxy | |
| - testing-scenarios | |
| - observer-pattern | |
| - health-aware-reverse-proxy | |
| - multi-engine-eventbus | |
| - logmasker-example | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| - name: Set up Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| check-latest: true | |
| cache: true | |
| - name: Validate example structure | |
| run: | | |
| cd examples/${{ matrix.example }} | |
| # Check required files exist | |
| if [ ! -f "go.mod" ]; then | |
| echo "❌ Missing go.mod in ${{ matrix.example }}" | |
| exit 1 | |
| fi | |
| if [ ! -f "config.yaml" ]; then | |
| echo "❌ Missing config.yaml in ${{ matrix.example }}" | |
| exit 1 | |
| fi | |
| if [ ! -f "main.go" ] && [ ! -f "$(basename $(pwd)).go" ]; then | |
| echo "❌ Missing main Go file in ${{ matrix.example }}" | |
| exit 1 | |
| fi | |
| echo "✅ Required files found in ${{ matrix.example }}" | |
| - name: Build example | |
| run: | | |
| cd examples/${{ matrix.example }} | |
| # Use GOWORK=off to treat each example as independent | |
| echo "🔨 Building ${{ matrix.example }}..." | |
| GOWORK=off go mod download | |
| GOWORK=off go mod verify | |
| GOWORK=off go build -v . | |
| echo "✅ ${{ matrix.example }} builds successfully" | |
| - name: Test example startup | |
| run: | | |
| cd examples/${{ matrix.example }} | |
| echo "🚀 Testing ${{ matrix.example }} startup..." | |
| # Build the example first | |
| GOWORK=off go build -o example . | |
| # Start the example in background and test it can start | |
| if [ "${{ matrix.example }}" = "basic-app" ]; then | |
| # Basic app just needs to start and respond to health check | |
| timeout 10s ./example & | |
| PID=$! | |
| sleep 3 | |
| # Test health endpoint | |
| if curl -f http://localhost:8080/health; then | |
| echo "✅ basic-app health check passed" | |
| else | |
| echo "❌ basic-app health check failed" | |
| kill $PID 2>/dev/null || true | |
| exit 1 | |
| fi | |
| kill $PID 2>/dev/null || true | |
| elif [ "${{ matrix.example }}" = "multi-tenant-app" ]; then | |
| # Multi-tenant app needs special validation to ensure tenants are loaded | |
| echo "🏢 Testing multi-tenant app with tenant validation..." | |
| # Run the app and capture logs | |
| timeout 10s ./example > app.log 2>&1 & | |
| PID=$! | |
| sleep 5 | |
| # Check if process is still running | |
| if ! kill -0 $PID 2>/dev/null; then | |
| echo "❌ multi-tenant-app crashed during startup" | |
| cat app.log | |
| exit 1 | |
| fi | |
| # Validate that tenants were loaded successfully | |
| if grep -q "Successfully loaded tenant configurations" app.log; then | |
| echo "✅ multi-tenant-app successfully loaded tenant configurations" | |
| else | |
| echo "❌ multi-tenant-app failed to load tenant configurations" | |
| echo "📋 Application logs:" | |
| cat app.log | |
| kill $PID 2>/dev/null || true | |
| exit 1 | |
| fi | |
| # Check for any tenant loading errors | |
| if grep -q "Failed to load tenant config" app.log; then | |
| echo "❌ multi-tenant-app encountered tenant loading errors" | |
| echo "📋 Application logs:" | |
| cat app.log | |
| kill $PID 2>/dev/null || true | |
| exit 1 | |
| fi | |
| # Validate that expected tenants were registered | |
| if grep -q "tenantCount=2" app.log; then | |
| echo "✅ multi-tenant-app loaded expected number of tenants" | |
| else | |
| echo "❌ multi-tenant-app did not load expected number of tenants" | |
| echo "📋 Application logs:" | |
| cat app.log | |
| kill $PID 2>/dev/null || true | |
| exit 1 | |
| fi | |
| kill $PID 2>/dev/null || true | |
| elif [ "${{ matrix.example }}" = "testing-scenarios" ]; then | |
| # Testing scenarios example has comprehensive validation scripts | |
| echo "🧪 Testing testing-scenarios with validation scripts..." | |
| # Make scripts executable | |
| chmod +x *.sh | |
| # Run the demo script (includes comprehensive testing) | |
| echo "Running demo.sh for rapid validation..." | |
| if timeout 60s ./demo.sh; then | |
| echo "✅ testing-scenarios demo script passed" | |
| else | |
| echo "❌ testing-scenarios demo script failed" | |
| exit 1 | |
| fi | |
| # Run health check validation | |
| echo "Running health check validation..." | |
| if timeout 30s ./test-health-checks.sh; then | |
| echo "✅ testing-scenarios health check validation passed" | |
| else | |
| echo "❌ testing-scenarios health check validation failed" | |
| exit 1 | |
| fi | |
| # Run feature flag testing | |
| echo "Running feature flag validation..." | |
| if timeout 30s ./test-feature-flags.sh; then | |
| echo "✅ testing-scenarios feature flag validation passed" | |
| else | |
| echo "❌ testing-scenarios feature flag validation failed" | |
| exit 1 | |
| fi | |
| elif [ "${{ matrix.example }}" = "health-aware-reverse-proxy" ]; then | |
| # Health-aware reverse proxy needs comprehensive circuit breaker testing | |
| echo "🔄 Testing health-aware-reverse-proxy with circuit breaker validation..." | |
| # Make test script executable | |
| chmod +x test-circuit-breakers.sh | |
| # Start the application in background | |
| timeout 60s ./example > app.log 2>&1 & | |
| PID=$! | |
| sleep 8 # Allow time for mock backends to start | |
| # Check if process is still running | |
| if ! kill -0 $PID 2>/dev/null; then | |
| echo "❌ health-aware-reverse-proxy crashed during startup" | |
| cat app.log | |
| exit 1 | |
| fi | |
| # Test basic health endpoint (accepts both 200 and 503 status codes) | |
| echo "Testing basic health endpoint..." | |
| health_response=$(curl -s -w "HTTP_CODE:%{http_code}" http://localhost:8080/health) | |
| http_code=$(echo "$health_response" | grep -o "HTTP_CODE:[0-9]*" | cut -d: -f2) | |
| if [ "$http_code" = "200" ] || [ "$http_code" = "503" ]; then | |
| echo "✅ health-aware-reverse-proxy health endpoint responding (HTTP $http_code)" | |
| else | |
| echo "❌ health-aware-reverse-proxy health endpoint returned unexpected status: HTTP $http_code" | |
| echo "Response: $health_response" | |
| kill $PID 2>/dev/null || true | |
| exit 1 | |
| fi | |
| # Test that unreachable backend triggers circuit breaker (simplified test) | |
| echo "Testing circuit breaker functionality..." | |
| # Make 3 requests to unreachable API to trigger circuit breaker | |
| for i in {1..3}; do | |
| curl -s http://localhost:8080/api/unreachable > /dev/null || true | |
| done | |
| # Wait a moment for circuit breaker to update | |
| sleep 2 | |
| # Check that health status reflects circuit breaker state | |
| health_response=$(curl -s http://localhost:8080/health) | |
| if echo "$health_response" | grep -q '"circuit_open_count":[1-9]'; then | |
| echo "✅ health-aware-reverse-proxy circuit breaker properly triggered" | |
| else | |
| echo "⚠️ Circuit breaker may not have triggered as expected (this could be timing-related)" | |
| echo "Health response: $health_response" | |
| # Don't fail here as this could be timing-sensitive in CI | |
| fi | |
| kill $PID 2>/dev/null || true | |
| elif [ "${{ matrix.example }}" = "observer-pattern" ]; then | |
| # Observer pattern example needs to complete its demo and show success message | |
| echo "🔍 Testing observer-pattern example completion..." | |
| # Run the observer pattern demo and capture output | |
| timeout 30s ./example > app.log 2>&1 | |
| EXIT_CODE=$? | |
| # Check if the demo completed successfully | |
| if [ $EXIT_CODE -eq 0 ] && grep -q "Observer Pattern Demo completed successfully" app.log; then | |
| echo "✅ observer-pattern demo completed successfully" | |
| # Verify key lifecycle signals were logged (updated patterns) | |
| # We now log structured messages like "Registered service" and "Initialized module". | |
| if grep -q "Registered service" app.log && grep -q "Initialized module" app.log; then | |
| echo "✅ observer-pattern logged expected lifecycle events" | |
| else | |
| echo "❌ observer-pattern missing expected lifecycle events (looking for 'Registered service' and 'Initialized module')" | |
| echo "📋 Application logs:" | |
| cat app.log | |
| exit 1 | |
| fi | |
| # Verify CloudEvents functionality was tested | |
| if grep -q "CloudEvent emitted successfully" app.log; then | |
| echo "✅ observer-pattern CloudEvents functionality verified" | |
| else | |
| echo "❌ observer-pattern CloudEvents functionality not verified" | |
| echo "📋 Application logs:" | |
| cat app.log | |
| exit 1 | |
| fi | |
| else | |
| echo "❌ observer-pattern demo failed to complete successfully" | |
| echo "📋 Application logs:" | |
| cat app.log | |
| exit 1 | |
| fi | |
| elif [ "${{ matrix.example }}" = "multi-engine-eventbus" ]; then | |
| # Multi-engine eventbus example - use Redis for external service demo | |
| echo "🔄 Testing multi-engine-eventbus with Redis service..." | |
| # Make run-demo.sh executable | |
| chmod +x run-demo.sh | |
| # Check if Docker is available for Redis | |
| if command -v docker &> /dev/null; then | |
| echo "Docker available, testing with Redis service" | |
| # Try to start Redis and run the demo | |
| if timeout 60s ./run-demo.sh run-redis; then | |
| echo "✅ multi-engine-eventbus demo completed successfully with Redis" | |
| # Clean up Redis container | |
| docker-compose -f docker-compose.yml down -v 2>/dev/null || true | |
| else | |
| echo "⚠️ Redis demo failed, testing graceful degradation mode" | |
| # Clean up any containers | |
| docker-compose -f docker-compose.yml down -v 2>/dev/null || true | |
| # Test graceful degradation (this should always work) | |
| timeout 10s ./example & | |
| PID=$! | |
| sleep 5 | |
| if kill -0 $PID 2>/dev/null; then | |
| echo "✅ multi-engine-eventbus handles missing services gracefully" | |
| kill $PID 2>/dev/null || true | |
| else | |
| echo "❌ multi-engine-eventbus failed to handle missing services" | |
| exit 1 | |
| fi | |
| fi | |
| else | |
| echo "Docker not available, testing graceful degradation mode only" | |
| # Test without external services (graceful degradation) | |
| timeout 10s ./example & | |
| PID=$! | |
| sleep 5 | |
| if kill -0 $PID 2>/dev/null; then | |
| echo "✅ multi-engine-eventbus handles missing services gracefully" | |
| kill $PID 2>/dev/null || true | |
| else | |
| echo "❌ multi-engine-eventbus failed to handle missing services" | |
| exit 1 | |
| fi | |
| fi | |
| elif [ "${{ matrix.example }}" = "reverse-proxy" ] || [ "${{ matrix.example }}" = "http-client" ] || [ "${{ matrix.example }}" = "advanced-logging" ] || [ "${{ matrix.example }}" = "verbose-debug" ] || [ "${{ matrix.example }}" = "instance-aware-db" ] || [ "${{ matrix.example }}" = "feature-flag-proxy" ]; then | |
| # These apps just need to start without immediate errors | |
| timeout 5s ./example & | |
| PID=$! | |
| sleep 3 | |
| # Check if process is still running (no immediate crash) | |
| if kill -0 $PID 2>/dev/null; then | |
| echo "✅ ${{ matrix.example }} started successfully" | |
| kill $PID 2>/dev/null || true | |
| else | |
| echo "❌ ${{ matrix.example }} failed to start or crashed immediately" | |
| exit 1 | |
| fi | |
| fi | |
| echo "✅ ${{ matrix.example }} startup test passed" | |
| - name: Verify go.mod configuration | |
| run: | | |
| cd examples/${{ matrix.example }} | |
| echo "🔍 Verifying go.mod configuration for ${{ matrix.example }}..." | |
| # Allow either a short module name (directory) or fully-qualified path under the repo | |
| MODULE_NAME=$(grep "^module " go.mod | awk '{print $2}') | |
| SHORT_NAME="${{ matrix.example }}" | |
| FQ_EXPECTED="github.com/GoCodeAlone/modular/examples/${{ matrix.example }}" | |
| if [ "$MODULE_NAME" != "$SHORT_NAME" ] && [ "$MODULE_NAME" != "$FQ_EXPECTED" ]; then | |
| echo "❌ Module name unexpected in ${{ matrix.example }}" | |
| echo "Found: $MODULE_NAME" | |
| echo "Expected one of: $SHORT_NAME OR $FQ_EXPECTED" | |
| exit 1 | |
| fi | |
| # The replace directive is optional when using go.work; warn if absent but don't fail. | |
| if ! grep -q "replace .*github.com/GoCodeAlone/modular => ../../" go.mod; then | |
| echo "⚠️ Warning: replace directive to root module not found (acceptable when using go.work)." | |
| fi | |
| echo "✅ go.mod configuration verified for ${{ matrix.example }} (module: $MODULE_NAME)" | |
| examples-overview: | |
| name: Examples Overview | |
| runs-on: ubuntu-latest | |
| needs: validate-examples | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| - name: Generate examples summary | |
| run: | | |
| echo "# 📋 Examples Validation Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "All examples have been validated successfully!" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "## 🎯 Validated Examples" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| cd examples | |
| for example in */; do | |
| example=${example%/} | |
| echo "- **$example**: ✅ Build and startup tests passed" >> $GITHUB_STEP_SUMMARY | |
| done | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "## 🧪 Test Coverage" >> $GITHUB_STEP_SUMMARY | |
| echo "- Structure validation: ✅" >> $GITHUB_STEP_SUMMARY | |
| echo "- Build verification: ✅" >> $GITHUB_STEP_SUMMARY | |
| echo "- Startup testing: ✅" >> $GITHUB_STEP_SUMMARY | |
| echo "- Configuration validation: ✅" >> $GITHUB_STEP_SUMMARY |