Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 152 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,24 @@ jobs:
{
echo "## 📋 Selective CI Execution Plan"
echo ""
echo "### Matrix Outputs"
echo ""
echo "| Output | Value |"
echo "|--------|-------|"
echo "| \`run_full\` | \`${{ steps.check-label.outputs.run_full }}\` |"
echo "| \`has_test\` | \`${{ steps.matrix.outputs.has_test }}\` |"
echo "| \`matrix\` | \`${{ steps.matrix.outputs.matrix }}\` |"
echo ""
echo "### Execution Summary"
echo ""
if [ "${{ steps.matrix.outputs.has_test }}" = "true" ]; then
echo "✅ **Tests**: Will run affected test suites"
echo ""
echo "> Matrix jobs will execute with proper names (e.g., 'Test MemStore Driver')"
else
echo "⏭️ **Tests**: Skipped (no test-related changes)"
echo ""
echo "> Matrix jobs will run but skip actual test execution"
fi
echo ""
echo "💡 Add \`ci:full\` label to run complete CI pipeline"
Expand All @@ -130,7 +144,16 @@ jobs:
{
echo "## 🔄 Full CI Execution"
echo ""
echo "### Matrix Outputs"
echo ""
echo "| Output | Value |"
echo "|--------|-------|"
echo "| \`run_full\` | \`true\` |"
echo "| \`has_test\` | \`${{ steps.matrix.outputs.has_test }}\` |"
echo ""
echo "✅ Running complete CI pipeline (ci:full label detected)"
echo ""
echo "> All matrix jobs will execute with full test coverage"
} >> "$GITHUB_STEP_SUMMARY"

# Job 1: Generate protobuf code once (new)
Expand Down Expand Up @@ -489,23 +512,56 @@ jobs:
build/*-runner
retention-days: 7

# Job 2: Test Rust proxy
# Job 2: Test Rust proxy (unit and integration tests)
# Note: Job always runs to provide visibility. Steps skip when Rust tests aren't needed.
test-proxy:
name: Test Rust Proxy
runs-on: ubuntu-latest
timeout-minutes: 15
needs: [generate-matrix]
# No job-level 'if' - provides consistent visibility in CI UI

steps:
- name: Check if Rust tests should run
id: should-run
run: |
# Debug: Show execution decision
{
echo "## Rust Proxy Tests Configuration"
echo ""
echo "| Input | Value |"
echo "|-------|-------|"
echo "| run_full | \`${{ needs.generate-matrix.outputs.run_full }}\` |"
echo "| has_test | \`${{ needs.generate-matrix.outputs.has_test }}\` |"
echo ""
} >> "$GITHUB_STEP_SUMMARY"

# Rust tests run when full CI or any tests are triggered
# (prism-proxy changes will set has_test=true via test:unit-proxy)
if [[ "${{ needs.generate-matrix.outputs.run_full }}" == "true" ]] || \
[[ "${{ needs.generate-matrix.outputs.has_test }}" == "true" ]]; then
echo "run=true" >> "$GITHUB_OUTPUT"
echo "**Decision:** Running Rust proxy tests" >> "$GITHUB_STEP_SUMMARY"
else
echo "run=false" >> "$GITHUB_OUTPUT"
echo "**Decision:** Skipping Rust tests (no test-related changes detected)" >> "$GITHUB_STEP_SUMMARY"
echo "::notice::Skipping Rust proxy tests - no test-related changes detected"
fi

- name: Checkout
if: steps.should-run.outputs.run == 'true'
uses: actions/checkout@v6

- name: Setup Rust
if: steps.should-run.outputs.run == 'true'
uses: dtolnay/rust-toolchain@stable

- name: Install protoc
if: steps.should-run.outputs.run == 'true'
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler

- name: Cache Rust dependencies
if: steps.should-run.outputs.run == 'true'
uses: actions/cache@v5
with:
path: |
Expand All @@ -519,22 +575,32 @@ jobs:
${{ runner.os }}-cargo-

- name: Cache cargo-tarpaulin
if: steps.should-run.outputs.run == 'true'
id: cache-tarpaulin
uses: actions/cache@v5
with:
path: ~/.cargo/bin/cargo-tarpaulin
key: ${{ runner.os }}-cargo-tarpaulin-0.27.0

- name: Install cargo-tarpaulin
if: steps.cache-tarpaulin.outputs.cache-hit != 'true'
if: steps.should-run.outputs.run == 'true' && steps.cache-tarpaulin.outputs.cache-hit != 'true'
run: cargo install cargo-tarpaulin --version 0.27.0

- name: Run Rust tests with coverage
- name: Run Rust unit tests with coverage
if: steps.should-run.outputs.run == 'true'
run: |
mkdir -p build/coverage-reports/coverage-rust
cd prism-proxy && cargo tarpaulin --lib --verbose --out xml --output-dir ../build/coverage-reports/coverage-rust

- name: Run Rust integration tests
if: steps.should-run.outputs.run == 'true'
run: |
echo "Running Rust integration tests..."
cd prism-proxy && cargo test --test integration_test -- --ignored --nocapture || echo "::warning::Rust integration tests failed or were skipped"
echo "Rust integration tests complete"

- name: Upload Rust coverage
if: steps.should-run.outputs.run == 'true'
uses: actions/upload-artifact@v6
with:
name: coverage-rust
Expand All @@ -543,12 +609,14 @@ jobs:
if-no-files-found: error

# Job 3: Test Go drivers and patterns (including acceptance tests)
# Note: Job always runs to ensure matrix.name is properly displayed in UI.
# Steps use conditions to skip actual work when tests aren't needed.
test-patterns:
name: Test ${{ matrix.name }}
runs-on: ubuntu-latest
timeout-minutes: 15
needs: [generate-matrix, generate-proto]
if: needs.generate-matrix.outputs.run_full == 'true' || needs.generate-matrix.outputs.has_test == 'true'
# No job-level 'if' - this ensures matrix.name is properly expanded in job names

strategy:
fail-fast: false
Expand Down Expand Up @@ -608,22 +676,66 @@ jobs:
pattern_dir: "patterns/unified"

steps:
- name: Check if tests should run
id: should-run
run: |
# Debug: Show matrix expansion worked correctly
{
echo "## Matrix Configuration"
echo ""
echo "| Property | Value |"
echo "|----------|-------|"
echo "| **Name** | ${{ matrix.name }} |"
echo "| **Path** | \`${{ matrix.path }}\` |"
echo "| **Type** | ${{ matrix.type }} |"
echo "| **Artifact** | ${{ matrix.artifact }} |"
} >> "$GITHUB_STEP_SUMMARY"
if [[ -n "${{ matrix.pattern_dir }}" ]]; then
echo "| **Pattern Dir** | \`${{ matrix.pattern_dir }}\` |" >> "$GITHUB_STEP_SUMMARY"
fi
echo "" >> "$GITHUB_STEP_SUMMARY"

# Determine if tests should run
{
echo "## Execution Decision"
echo ""
echo "| Input | Value |"
echo "|-------|-------|"
echo "| run_full | \`${{ needs.generate-matrix.outputs.run_full }}\` |"
echo "| has_test | \`${{ needs.generate-matrix.outputs.has_test }}\` |"
echo ""
} >> "$GITHUB_STEP_SUMMARY"

if [[ "${{ needs.generate-matrix.outputs.run_full }}" == "true" ]] || \
[[ "${{ needs.generate-matrix.outputs.has_test }}" == "true" ]]; then
echo "run=true" >> "$GITHUB_OUTPUT"
echo "**Decision:** Running tests" >> "$GITHUB_STEP_SUMMARY"
else
echo "run=false" >> "$GITHUB_OUTPUT"
echo "**Decision:** Skipping tests (no test-related changes detected)" >> "$GITHUB_STEP_SUMMARY"
echo "::notice::Skipping tests for ${{ matrix.name }} - no test-related changes detected"
fi

- name: Checkout
if: steps.should-run.outputs.run == 'true'
uses: actions/checkout@v6

- name: Setup Go
if: steps.should-run.outputs.run == 'true'
uses: actions/setup-go@v6
with:
go-version: "1.25.4"
cache: false # We'll use custom cache for more control

- name: Download generated proto code
if: steps.should-run.outputs.run == 'true'
uses: actions/download-artifact@v7
with:
name: proto-generated
path: pkg/plugin/gen/

- name: Cache Go dependencies and build cache
if: steps.should-run.outputs.run == 'true'
uses: actions/cache@v5
with:
path: |
Expand All @@ -635,22 +747,23 @@ jobs:
${{ runner.os }}-go-

- name: Download dependencies for acceptance tests
if: matrix.type == 'acceptance' && matrix.pattern_dir != ''
if: steps.should-run.outputs.run == 'true' && matrix.type == 'acceptance' && matrix.pattern_dir != ''
run: |
if [ -d "${{ matrix.pattern_dir }}" ]; then
cd ${{ matrix.pattern_dir }}
go mod download
fi

- name: Build pattern module for acceptance tests
if: matrix.type == 'acceptance' && matrix.pattern_dir != ''
if: steps.should-run.outputs.run == 'true' && matrix.type == 'acceptance' && matrix.pattern_dir != ''
run: |
if [ -d "${{ matrix.pattern_dir }}" ]; then
cd ${{ matrix.pattern_dir }}
go build ./...
fi

- name: Run tests with coverage for ${{ matrix.name }}
if: steps.should-run.outputs.run == 'true'
run: |
cd ${{ matrix.path }}
go test -v -race -coverprofile=coverage.out -covermode=atomic -timeout 15m ./...
Expand All @@ -659,31 +772,60 @@ jobs:
PRISM_TEST_QUIET: "1"

- name: Upload coverage
if: steps.should-run.outputs.run == 'true'
uses: actions/upload-artifact@v6
with:
name: coverage-${{ matrix.artifact }}
path: ${{ matrix.path }}/coverage.out
retention-days: 7

# Job 4: Integration tests
# Note: Job always runs to provide visibility. Steps skip when tests aren't needed.
test-integration:
name: Integration Tests
runs-on: ubuntu-latest
timeout-minutes: 10
needs: [generate-matrix, generate-proto]
if: needs.generate-matrix.outputs.run_full == 'true' || needs.generate-matrix.outputs.has_test == 'true'
# No job-level 'if' - provides consistent visibility in CI UI

steps:
- name: Check if integration tests should run
id: should-run
run: |
# Debug: Show execution decision
{
echo "## Integration Tests Configuration"
echo ""
echo "| Input | Value |"
echo "|-------|-------|"
echo "| run_full | \`${{ needs.generate-matrix.outputs.run_full }}\` |"
echo "| has_test | \`${{ needs.generate-matrix.outputs.has_test }}\` |"
echo ""
} >> "$GITHUB_STEP_SUMMARY"

if [[ "${{ needs.generate-matrix.outputs.run_full }}" == "true" ]] || \
[[ "${{ needs.generate-matrix.outputs.has_test }}" == "true" ]]; then
echo "run=true" >> "$GITHUB_OUTPUT"
echo "**Decision:** Running integration tests" >> "$GITHUB_STEP_SUMMARY"
else
echo "run=false" >> "$GITHUB_OUTPUT"
echo "**Decision:** Skipping integration tests (no test-related changes detected)" >> "$GITHUB_STEP_SUMMARY"
echo "::notice::Skipping integration tests - no test-related changes detected"
fi

- name: Checkout
if: steps.should-run.outputs.run == 'true'
uses: actions/checkout@v6

- name: Setup Go
if: steps.should-run.outputs.run == 'true'
uses: actions/setup-go@v6
with:
go-version: "1.25.4"
cache: false # We'll use custom cache for more control

- name: Cache Go dependencies and build cache
if: steps.should-run.outputs.run == 'true'
uses: actions/cache@v5
with:
path: |
Expand All @@ -695,18 +837,21 @@ jobs:
${{ runner.os }}-go-

- name: Download generated proto code
if: steps.should-run.outputs.run == 'true'
uses: actions/download-artifact@v7
with:
name: proto-generated
path: pkg/plugin/gen/

- name: Run integration tests with coverage
if: steps.should-run.outputs.run == 'true'
run: |
cd tests/integration
go test -v -race -coverprofile=coverage.out -covermode=atomic -timeout 5m ./...
go tool cover -func=coverage.out | grep total | awk '{print "Integration Coverage: " $3}'

- name: Upload coverage
if: steps.should-run.outputs.run == 'true'
uses: actions/upload-artifact@v6
with:
name: coverage-integration
Expand Down
30 changes: 25 additions & 5 deletions tooling/ci_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,25 +155,40 @@ def _fallback_detection(self, file_path: str) -> set[str]:
affected.add("docs-validate")
return affected

# Integration tests - changes to tests/integration/ should trigger integration tests
if file_path.startswith("tests/integration/"):
affected.add("test:integration")
affected.add("lint-go")
return affected

# Shared packages affect dependent tests
if file_path.startswith("pkg/"):
# pkg/plugin affects all patterns
# pkg/plugin affects all patterns AND integration tests
if "pkg/plugin" in file_path:
affected.update(self._get_all_pattern_tests())
# pkg/drivers affects specific driver tests
affected.add("test:integration") # Core changes need integration testing
# pkg/drivers affects specific driver tests AND integration tests
elif "pkg/drivers/redis" in file_path:
affected.add("test:unit-redis")
affected.add("test:integration")
affected.add("lint-go")
elif "pkg/drivers/nats" in file_path:
affected.add("test:unit-nats")
affected.add("test:integration")
affected.add("lint-go")
elif "pkg/drivers/memstore" in file_path:
affected.add("test:unit-memstore")
affected.add("test:integration")
affected.add("lint-go")
elif "pkg/drivers/" in file_path:
# Generic driver change
affected.add("test:integration")
affected.add("lint-go")

# Pattern changes
# Pattern changes - patterns are tested via acceptance tests and integration tests
if file_path.startswith("patterns/"):
affected.add("lint-go")
affected.add("test:integration") # Pattern changes should trigger integration tests
# Extract pattern name
parts = file_path.split("/")
if len(parts) >= 2:
Expand All @@ -186,19 +201,24 @@ def _fallback_detection(self, file_path: str) -> set[str]:
# Fallback to core tests if pattern-specific tests don't exist
affected.add("test:unit-core")

# Command changes
# Command changes - commands should be integration tested
if file_path.startswith("cmd/"):
affected.add("lint-go")
affected.add("test:integration") # Command changes affect system behavior
# Extract command name
parts = file_path.split("/")
if len(parts) >= 2:
cmd_name = parts[1]
affected.add(cmd_name) # Build task

# Rust proxy
# Rust proxy - trigger both unit and integration tests
if file_path.startswith("prism-proxy/"):
affected.add("lint-rust")
affected.add("proxy")
affected.add("test:unit-proxy") # Rust unit tests
# Integration tests for test files or core source changes
if file_path.startswith(("prism-proxy/tests/", "prism-proxy/src/")):
affected.add("test:integration-rust")

# Python tooling
if file_path.startswith("tooling/") and file_path.endswith(".py"):
Expand Down
Loading