diff --git a/.github/actions/ratchet-coverage/action.yml b/.github/actions/ratchet-coverage/action.yml index f16ca31a..0dce453e 100644 --- a/.github/actions/ratchet-coverage/action.yml +++ b/.github/actions/ratchet-coverage/action.yml @@ -33,7 +33,7 @@ runs: restore-keys: | ${{ runner.os }}-llvmcov- - name: Install cargo-llvm-cov - run: cargo install cargo-llvm-cov + run: uv run --script scripts/ratchet_coverage/install_cargo_llvm_cov.py shell: bash - if: runner.os == 'Windows' name: Install bc (MSYS2) @@ -43,40 +43,12 @@ runs: path-type: inherit - name: Run coverage id: cov - run: | - set -euo pipefail - output=$(cargo llvm-cov --summary-only ${{ inputs.args }}) - echo "$output" - percent=$(echo "$output" | grep -oEm1 '[0-9]+(\.[0-9]+)?%' | tr -d '%') - echo "percent=$percent" >> "$GITHUB_OUTPUT" + run: uv run --script scripts/ratchet_coverage/run_coverage.py 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 ! echo "$current" | grep -Eq '^[0-9]+(\.[0-9]+)?$'; then - echo "Invalid coverage value: $current" >&2 - exit 1 - fi - if ! echo "$baseline" | grep -Eq '^[0-9]+(\.[0-9]+)?$'; then - baseline=0 - fi - - if (( $(bc -l <<<"$current < $baseline") )); then - echo "Coverage decreased" >&2 - exit 1 - fi - - mkdir -p "$(dirname "$file")" - printf '%.2f' "$current" > "$file" + run: uv run --script scripts/ratchet_coverage/ratchet_coverage.py + env: + CURRENT_PERCENT: ${{ steps.cov.outputs.percent }} shell: bash - name: Save baseline if: success() diff --git a/scripts/ratchet_coverage/install_cargo_llvm_cov.py b/scripts/ratchet_coverage/install_cargo_llvm_cov.py new file mode 100755 index 00000000..36f614ea --- /dev/null +++ b/scripts/ratchet_coverage/install_cargo_llvm_cov.py @@ -0,0 +1,20 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.12" +# dependencies = ["plumbum", "typer"] +# /// +from plumbum.cmd import cargo +import typer + + +def main() -> None: + try: + cargo["install", "cargo-llvm-cov"]() + typer.echo("cargo-llvm-cov installed successfully") + except Exception as e: + typer.echo(f"Failed to install cargo-llvm-cov: {e}", err=True) + raise typer.Exit(code=1) + + +if __name__ == "__main__": + typer.run(main) diff --git a/scripts/ratchet_coverage/ratchet_coverage.py b/scripts/ratchet_coverage/ratchet_coverage.py new file mode 100755 index 00000000..e3a683ef --- /dev/null +++ b/scripts/ratchet_coverage/ratchet_coverage.py @@ -0,0 +1,40 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.12" +# dependencies = ["plumbum", "typer"] +# /// +from pathlib import Path +import typer + + +def read_baseline(file: Path) -> float: + """Return the stored baseline coverage or 0.0 if missing/invalid.""" + if file.is_file(): + try: + return float(file.read_text().strip()) + except ValueError: + return 0.0 + return 0.0 + + +def main( + baseline_file: Path = typer.Option( + Path(".coverage-baseline"), envvar="INPUT_BASELINE_FILE" + ), + current: float = typer.Option(..., envvar="CURRENT_PERCENT"), +) -> None: + baseline = read_baseline(baseline_file) + + typer.echo(f"Current coverage: {current}%") + typer.echo(f"Baseline coverage: {baseline}%") + + if current < baseline: + typer.echo("Coverage decreased", err=True) + raise typer.Exit(code=1) + + baseline_file.parent.mkdir(parents=True, exist_ok=True) + baseline_file.write_text(f"{current:.2f}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/scripts/ratchet_coverage/run_coverage.py b/scripts/ratchet_coverage/run_coverage.py new file mode 100755 index 00000000..204144c8 --- /dev/null +++ b/scripts/ratchet_coverage/run_coverage.py @@ -0,0 +1,36 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.12" +# dependencies = ["plumbum", "typer"] +# /// +from pathlib import Path +import re +import shlex +import typer +from plumbum.cmd import cargo + + +def extract_percent(output: str) -> str: + match = re.search(r"([0-9]+(?:\.[0-9]+)?)%", output) + if not match: + typer.echo("Could not parse coverage percent", err=True) + raise typer.Exit(code=1) + return match[1] + + +def main( + args: str = typer.Option("", envvar="INPUT_ARGS"), + github_output: Path = typer.Option(..., envvar="GITHUB_OUTPUT"), +) -> None: + cmd = cargo["llvm-cov", "--summary-only"] + if args: + cmd = cmd[shlex.split(args)] + output = cmd() + typer.echo(output) + percent = extract_percent(output) + with github_output.open("a") as fh: + fh.write(f"percent={percent}\n") + + +if __name__ == "__main__": + typer.run(main)