Coverage #21
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: Coverage | |
| on: | |
| # Only run coverage on release tags to reduce CI/CD load | |
| push: | |
| tags: | |
| - 'v*.*.*' | |
| # Manual trigger for testing | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Version tag (e.g., v0.4.6)' | |
| required: false | |
| type: string | |
| # Cancel outdated workflow runs | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUST_BACKTRACE: 1 | |
| # Reduce disk usage during builds | |
| CARGO_INCREMENTAL: 0 | |
| # Minimize debug info to save disk space | |
| CARGO_PROFILE_DEV_DEBUG: 0 | |
| jobs: | |
| coverage: | |
| name: Code Coverage | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Free up disk space | |
| run: | | |
| echo "=== Initial disk space ===" | |
| df -h / | |
| echo "" | |
| echo "=== Removing unnecessary packages ===" | |
| # Remove large unnecessary packages | |
| sudo rm -rf /usr/share/dotnet | |
| sudo rm -rf /usr/local/lib/android | |
| sudo rm -rf /opt/ghc | |
| sudo rm -rf /opt/hostedtoolcache/CodeQL | |
| sudo apt-get autoremove -y | |
| sudo apt-get clean | |
| echo "" | |
| echo "=== Disk space after cleanup ===" | |
| df -h / | |
| - uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: llvm-tools-preview | |
| - name: Install system dependencies | |
| run: sudo apt-get update && sudo apt-get install -y libpcap-dev pkg-config | |
| # Use Swatinem/rust-cache for optimal Rust caching (same as CI workflow) | |
| - name: Cache dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "coverage" | |
| cache-targets: "false" | |
| cache-on-failure: "false" | |
| - name: Install cargo-tarpaulin | |
| run: | | |
| # Check if already installed to save time | |
| if ! command -v cargo-tarpaulin &> /dev/null; then | |
| cargo install cargo-tarpaulin | |
| else | |
| echo "cargo-tarpaulin already installed" | |
| fi | |
| - name: Generate coverage report | |
| id: tarpaulin | |
| run: | | |
| echo "=== Disk space before coverage ===" | |
| df -h / | |
| # Run tarpaulin with settings to prevent hangs while producing coverage: | |
| # - --target-dir: Separate directory avoids incremental compilation issues | |
| # - --timeout 600: 10-minute per-test timeout (generous for slow/async tests) | |
| # - --skip-clean: Don't clean between runs (reduces build time, helps stability) | |
| # - --no-fail-fast: Continue even if some tests fail | |
| # - --test-threads 1: Sequential tests prevent race conditions and ptrace issues | |
| # Note: Using default ptrace engine (not LLVM) as LLVM doesn't produce output | |
| # See: https://github.com/xd009642/tarpaulin/issues/971 | |
| OUTPUT=$(cargo tarpaulin --workspace \ | |
| --target-dir target-cov \ | |
| --timeout 600 \ | |
| --skip-clean \ | |
| --no-fail-fast \ | |
| --out Lcov \ | |
| --output-dir coverage \ | |
| --exclude-files "crates/prtip-cli/src/main.rs" \ | |
| --exclude-files "*/tests/*" \ | |
| --exclude-files "*/benches/*" \ | |
| -- --test-threads 1 2>&1) || true | |
| # Display the output | |
| echo "$OUTPUT" | |
| echo "=== Disk space after coverage ===" | |
| df -h / | |
| # Extract coverage percentage from output (format: "XX.XX% coverage, N/M lines covered") | |
| COVERAGE=$(echo "$OUTPUT" | grep -oP '\d+\.\d+(?=% coverage)' | tail -1) | |
| if [ -z "$COVERAGE" ]; then | |
| echo "Warning: Could not extract coverage percentage, defaulting to 0" | |
| COVERAGE="0.0" | |
| fi | |
| echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT | |
| echo "Extracted coverage: $COVERAGE%" | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| files: coverage/lcov.info | |
| flags: rust | |
| name: codecov-prtip | |
| fail_ci_if_error: false | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| - name: Upload coverage artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-report | |
| path: coverage/lcov.info | |
| retention-days: 14 | |
| - name: Extract coverage percentage | |
| id: coverage | |
| run: | | |
| # Get coverage from tarpaulin step output | |
| COVERAGE=${{ steps.tarpaulin.outputs.coverage }} | |
| echo "percentage=$COVERAGE" >> $GITHUB_OUTPUT | |
| echo "Current coverage: $COVERAGE%" | |
| - name: Check coverage threshold | |
| run: | | |
| COVERAGE=${{ steps.coverage.outputs.percentage }} | |
| THRESHOLD=50.0 | |
| echo "Current coverage: $COVERAGE%" | |
| echo "Required threshold: $THRESHOLD%" | |
| # Use awk for floating point comparison (bc not always available) | |
| if awk -v cov="$COVERAGE" -v thr="$THRESHOLD" 'BEGIN {exit !(cov < thr)}'; then | |
| echo "❌ Coverage $COVERAGE% is below threshold $THRESHOLD%" | |
| echo "::error::Coverage regression detected. Current: $COVERAGE%, Required: $THRESHOLD%" | |
| exit 1 | |
| fi | |
| echo "✅ Coverage $COVERAGE% meets threshold $THRESHOLD%" | |
| echo "::notice::Coverage check passed: $COVERAGE% >= $THRESHOLD%" | |
| - name: Comment PR with coverage | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v6 | |
| with: | |
| script: | | |
| const coverage = '${{ steps.coverage.outputs.percentage }}'; | |
| const threshold = '50.0'; | |
| const passed = parseFloat(coverage) >= parseFloat(threshold); | |
| const emoji = passed ? '✅' : '❌'; | |
| const comment = `## ${emoji} Coverage Report | |
| **Current Coverage:** ${coverage}% | |
| **Threshold:** ${threshold}% | |
| **Status:** ${passed ? 'PASSED' : 'FAILED'} | |
| ${passed ? | |
| '✅ Coverage meets the minimum threshold.' : | |
| '❌ Coverage is below the minimum threshold. Please add more tests.'} | |
| 📊 [View detailed coverage report in artifacts](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) | |
| `; | |
| github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: comment | |
| }); |