feat: 100% tests coverage infrastructure #13
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: CI/CD Pipeline | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| workflow_dispatch: | |
| inputs: | |
| bump_type: | |
| description: 'Version bump type' | |
| required: true | |
| type: choice | |
| options: | |
| - patch | |
| - minor | |
| - major | |
| description: | |
| description: 'Release description (optional)' | |
| required: false | |
| type: string | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| CARGO_TERM_COLOR: always | |
| jobs: | |
| # REQUIRED CI CHECKS - All must pass before release | |
| # These jobs ensure code quality and tests pass before any release | |
| # Linting and formatting | |
| lint: | |
| name: Lint and Format Check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: nightly-2022-08-22 | |
| components: rustfmt, clippy | |
| - name: Cache cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo- | |
| - name: Check formatting | |
| run: cargo fmt --all -- --check | |
| - name: Run Clippy | |
| run: cargo clippy --all-targets --all-features | |
| - name: Check file size limit | |
| run: python3 scripts/check_file_size.py | |
| # Test on multiple OS | |
| test: | |
| name: Test (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: nightly-2022-08-22 | |
| - name: Cache cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo- | |
| - name: Run tests | |
| run: cargo test --all-features --verbose | |
| - name: Run doc tests | |
| run: cargo test --doc --verbose | |
| # Test coverage - ensures we maintain high test coverage | |
| coverage: | |
| name: Test Coverage | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Rust nightly | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: nightly-2022-08-22 | |
| # Install tarpaulin using pre-built binary (avoids compilation issues) | |
| - name: Install cargo-tarpaulin | |
| run: | | |
| curl -sL https://github.com/xd009642/tarpaulin/releases/download/0.31.5/cargo-tarpaulin-x86_64-unknown-linux-gnu.tar.gz | tar xz | |
| chmod +x cargo-tarpaulin | |
| sudo mv cargo-tarpaulin /usr/local/bin/ | |
| - name: Cache cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-cargo-coverage-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-coverage- | |
| - name: Run coverage | |
| run: cargo-tarpaulin --out Xml --output-dir coverage | |
| - name: Check coverage threshold | |
| run: | | |
| # Extract coverage percentage from output | |
| COVERAGE=$(cargo-tarpaulin --out Stdout 2>&1 | grep -oP '\d+\.\d+(?=% coverage)' | tail -1) | |
| echo "Coverage: $COVERAGE%" | |
| # Check if coverage meets threshold (90%) | |
| if (( $(echo "$COVERAGE < 90" | bc -l) )); then | |
| echo "::error::Coverage ($COVERAGE%) is below 90% threshold" | |
| exit 1 | |
| fi | |
| echo "Coverage check passed: $COVERAGE% >= 90%" | |
| # Build package - only runs if lint, test, and coverage pass | |
| build: | |
| name: Build Package | |
| runs-on: ubuntu-latest | |
| needs: [lint, test, coverage] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: nightly-2022-08-22 | |
| - name: Cache cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-build- | |
| - name: Build release | |
| run: cargo build --release --verbose | |
| - name: Check package | |
| run: cargo package --list | |
| # Check for changelog fragments in PRs | |
| changelog: | |
| name: Changelog Fragment Check | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check for changelog fragments | |
| run: | | |
| # Get list of fragment files (excluding README and template) | |
| FRAGMENTS=$(find changelog.d -name "*.md" ! -name "README.md" 2>/dev/null | wc -l) | |
| # Get changed files in PR | |
| CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) | |
| # Check if any source files changed (excluding docs and config) | |
| SOURCE_CHANGED=$(echo "$CHANGED_FILES" | grep -E "^(src/|tests/|scripts/|examples/)" | wc -l) | |
| if [ "$SOURCE_CHANGED" -gt 0 ] && [ "$FRAGMENTS" -eq 0 ]; then | |
| echo "::warning::No changelog fragment found. Please add a changelog entry in changelog.d/" | |
| echo "" | |
| echo "To create a changelog fragment:" | |
| echo " Create a new .md file in changelog.d/ with your changes" | |
| echo "" | |
| echo "See changelog.d/README.md for more information." | |
| # Note: This is a warning, not a failure, to allow flexibility | |
| # Change 'exit 0' to 'exit 1' to make it required | |
| exit 0 | |
| fi | |
| echo "Changelog check passed" | |
| # Automatic release on push to main (if version changed) | |
| auto-release: | |
| name: Auto Release | |
| needs: [lint, test, build] | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: nightly-2022-08-22 | |
| - name: Check if version changed | |
| id: version_check | |
| run: | | |
| # Get current version from Cargo.toml | |
| CURRENT_VERSION=$(grep -Po '(?<=^version = ")[^"]*' Cargo.toml) | |
| echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT | |
| # Check if tag exists | |
| if git rev-parse "v$CURRENT_VERSION" >/dev/null 2>&1; then | |
| echo "Tag v$CURRENT_VERSION already exists, skipping release" | |
| echo "should_release=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "New version detected: $CURRENT_VERSION" | |
| echo "should_release=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Build release | |
| if: steps.version_check.outputs.should_release == 'true' | |
| run: cargo build --release | |
| - name: Create GitHub Release | |
| if: steps.version_check.outputs.should_release == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| python3 scripts/create_github_release.py \ | |
| --version "${{ steps.version_check.outputs.current_version }}" \ | |
| --repository "${{ github.repository }}" | |
| # Manual release via workflow_dispatch - only after CI passes | |
| manual-release: | |
| name: Manual Release | |
| needs: [lint, test, build] | |
| if: github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: nightly-2022-08-22 | |
| - name: Configure git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Collect changelog fragments | |
| run: | | |
| # Check if there are any fragments to collect | |
| FRAGMENTS=$(find changelog.d -name "*.md" ! -name "README.md" 2>/dev/null | wc -l) | |
| if [ "$FRAGMENTS" -gt 0 ]; then | |
| echo "Found $FRAGMENTS changelog fragment(s), collecting..." | |
| python3 scripts/collect_changelog.py | |
| else | |
| echo "No changelog fragments found, skipping collection" | |
| fi | |
| - name: Version and commit | |
| id: version | |
| run: | | |
| python3 scripts/version_and_commit.py \ | |
| --bump-type "${{ github.event.inputs.bump_type }}" \ | |
| --description "${{ github.event.inputs.description }}" | |
| - name: Build release | |
| if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' | |
| run: cargo build --release | |
| - name: Create GitHub Release | |
| if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| python3 scripts/create_github_release.py \ | |
| --version "${{ steps.version.outputs.new_version }}" \ | |
| --repository "${{ github.repository }}" |