diff --git a/.github/actions/ratchet-coverage/CHANGELOG.md b/.github/actions/ratchet-coverage/CHANGELOG.md new file mode 100644 index 00000000..b999a6db --- /dev/null +++ b/.github/actions/ratchet-coverage/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## v1.0.0 + +- Initial version with caching and baseline ratcheting. diff --git a/.github/actions/ratchet-coverage/README.md b/.github/actions/ratchet-coverage/README.md new file mode 100644 index 00000000..2387c701 --- /dev/null +++ b/.github/actions/ratchet-coverage/README.md @@ -0,0 +1,40 @@ +# Ratchet coverage + +Generate code coverage using `cargo tarpaulin` and fail the workflow if the +coverage percentage falls below a stored baseline. + +## Inputs + +| Name | Description | Required | Default | +| --- | --- | --- | --- | +| baseline-file | File used to persist the baseline coverage percentage between runs | no | `.coverage-baseline` | +| args | Additional arguments passed to `cargo tarpaulin` | no | *(none)* | + +## Outputs + +| Name | Description | +| --- | --- | +| percent | Coverage percentage reported by `cargo tarpaulin` | + +## Example + +```yaml +- uses: ./.github/actions/setup-rust@v1 +- uses: ./.github/actions/ratchet-coverage@v1 + with: + baseline-file: .cache/coverage-baseline + args: --workspace +``` + +`cargo tarpaulin` only runs on Linux hosts, so use this action on +`ubuntu-latest` runners. + +### How it works + +The action restores the previous coverage baseline using `actions/cache` and +installs `cargo-tarpaulin` if necessary. After running the coverage command, it +compares the new percentage with the stored baseline. The job fails if coverage +has dropped. On success, the baseline file is updated and saved back to the +cache using a branch-specific key for future runs. + +Release history is available in [CHANGELOG](CHANGELOG.md). diff --git a/.github/actions/ratchet-coverage/action.yml b/.github/actions/ratchet-coverage/action.yml new file mode 100644 index 00000000..3cdc26d8 --- /dev/null +++ b/.github/actions/ratchet-coverage/action.yml @@ -0,0 +1,84 @@ +name: Ratchet coverage +description: Run cargo tarpaulin and fail if coverage decreases +inputs: + baseline-file: + description: Path to store the coverage baseline + required: false + default: .coverage-baseline + args: + description: Additional arguments passed to cargo tarpaulin + required: false + default: '' +outputs: + percent: + description: Coverage percentage reported by cargo tarpaulin + value: ${{ steps.cov.outputs.percent }} +runs: + using: composite + steps: + - name: Restore baseline + uses: actions/cache@v4 + with: + path: ${{ inputs.baseline-file }} + key: ratchet-baseline-${{ runner.os }}-${{ github.ref_name }} + - name: Cache cargo artifacts + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/cargo-tarpaulin + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-tarpaulin-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-tarpaulin- + - name: Install cargo-tarpaulin + run: cargo install cargo-tarpaulin + shell: bash + - name: Run coverage + id: cov + run: | + set -euo pipefail + output=$(cargo tarpaulin ${{ inputs.args }} --out lcov 2>&1) + echo "$output" + percent=$(echo "$output" | grep -oP '[0-9]+(?:\.[0-9]+)?(?=%)' | head -n1) + if [ -z "$percent" ]; then + echo "Unable to extract coverage percentage" >&2 + exit 1 + fi + echo "percent=$percent" >> "$GITHUB_OUTPUT" + shell: bash + - name: Ratchet coverage + run: | + set -euo pipefail + file="${{ inputs.baseline-file }}" + current="${{ steps.cov.outputs.percent }}" + baseline=0 + if [ -f "$file" ]; then + baseline=$(cat "$file") + fi + echo "Current coverage: $current%" + echo "Baseline coverage: $baseline%" + if ! [[ "$current" =~ ^[0-9]+(\.[0-9]+)?$ ]] || \ + ! [[ "$baseline" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then + echo "Invalid coverage values" >&2 + exit 1 + fi + # Ensure we're on Linux where `bc` is guaranteed to be installed + if [[ "$RUNNER_OS" != "Linux" ]]; then + echo "This action only supports Linux runners for float comparisons." >&2 + exit 1 + fi + + if [ "$(echo "$current < $baseline" | bc -l)" = "1" ]; then + echo "Coverage decreased" >&2 + exit 1 + fi + printf '%.2f' "$current" > "$file" + shell: bash + - name: Save baseline + if: success() + uses: actions/cache@v4 + with: + path: ${{ inputs.baseline-file }} + key: ratchet-baseline-${{ runner.os }}-${{ github.ref_name }} diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000..39a02310 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,5 @@ +.github/actions/export-postgres-url/ @leynos +.github/actions/generate-coverage/ @leynos +.github/actions/setup-rust/ @leynos +.github/actions/upload-codescene-coverage/ @leynos +.github/actions/ratchet-coverage/ @leynos diff --git a/README.md b/README.md index 2ebf87ca..4e3b69f6 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,4 @@ GitHub Actions | Generate coverage | `.github/actions/generate-coverage` | v1 | | Setup Rust | `.github/actions/setup-rust` | v1 | | Upload CodeScene Coverage | `.github/actions/upload-codescene-coverage` | v1 | +| Ratchet coverage | `.github/actions/ratchet-coverage` | v1 |