diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 2f3d096..55cd693 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -21,7 +21,7 @@ jobs: name: Code Coverage runs-on: ubuntu-latest env: - TARPAULIN_VERSION: "0.32.8" + CARGO_LLVM_COV_VERSION: "0.8.5" steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -33,18 +33,25 @@ jobs: with: cache: true # toolchain/components are specified in rust-toolchain.toml - - name: Cache tarpaulin + - name: Install LLVM coverage tools + run: rustup component add llvm-tools-preview + + - name: Cache cargo-llvm-cov uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: - path: ~/.cargo/bin/cargo-tarpaulin - key: tarpaulin-${{ runner.os }}-${{ env.TARPAULIN_VERSION }} - restore-keys: | - tarpaulin-${{ runner.os }}- + path: ~/.cargo/bin/cargo-llvm-cov + key: cargo-llvm-cov-${{ runner.os }}-${{ env.CARGO_LLVM_COV_VERSION }} - - name: Install tarpaulin + - name: Install cargo-llvm-cov run: | - if ! command -v cargo-tarpaulin &> /dev/null; then - cargo install cargo-tarpaulin --locked --version "${TARPAULIN_VERSION}" + installed_version="" + if command -v cargo-llvm-cov &> /dev/null; then + installed_version="$(cargo llvm-cov --version)" + installed_version="${installed_version#cargo-llvm-cov }" + fi + + if [ "$installed_version" != "$CARGO_LLVM_COV_VERSION" ]; then + cargo install cargo-llvm-cov --locked --version "${CARGO_LLVM_COV_VERSION}" fi - name: Install just @@ -66,7 +73,7 @@ jobs: ls -la coverage/ || true if [ ! -f coverage/cobertura.xml ]; then - echo "::error::coverage/cobertura.xml not found. Tarpaulin failed to generate XML output." + echo "::error::coverage/cobertura.xml not found. cargo-llvm-cov failed to generate XML output." exit 2 fi echo "::notice::Coverage report generated successfully: $(wc -l < coverage/cobertura.xml) lines" diff --git a/README.md b/README.md index faa751f..043f74c 100644 --- a/README.md +++ b/README.md @@ -304,6 +304,7 @@ just fix # apply auto-fixes (mutating) just ci # lint + tests + examples + bench compile ``` +For coverage commands and report locations, see [`docs/COVERAGE.md`](docs/COVERAGE.md). For the full set of developer commands, see `just --list` and `AGENTS.md`. ## 📝 Citation diff --git a/docs/COVERAGE.md b/docs/COVERAGE.md new file mode 100644 index 0000000..056c1f3 --- /dev/null +++ b/docs/COVERAGE.md @@ -0,0 +1,62 @@ +# Coverage + +la-stack uses `cargo-llvm-cov` for local and CI coverage. Coverage runs use +Rust's LLVM source-based instrumentation with the same core test selection in +both environments: + +```bash +cargo llvm-cov --features exact --workspace --lib --tests +``` + +## Local HTML + +Generate the local developer report with: + +```bash +just coverage +``` + +The HTML report is written to: + +```text +target/llvm-cov/html/index.html +``` + +The report opens automatically after generation. + +## CI XML + +Generate the CI-compatible Cobertura report with: + +```bash +just coverage-ci +``` + +The XML report is written to: + +```text +coverage/cobertura.xml +``` + +The Codecov workflow installs Rust's `llvm-tools-preview` component, installs +`cargo-llvm-cov`, caches the installed cargo binary by version, runs +`just coverage-ci`, verifies `coverage/cobertura.xml`, uploads that file to +Codecov, and archives the full `coverage/` directory. Local setup via +`just setup-tools` installs the same Rust component and cargo subcommand. + +## Migration Notes + +- Keep `just coverage-ci` as the single source of truth for CI coverage + arguments; workflows should install tools and upload artifacts, not duplicate + the coverage command. +- Use `--cobertura --output-path coverage/cobertura.xml` for services that + consume Cobertura XML. +- Use `--open --output-dir target/llvm-cov` for local reports. +- Preserve the crate's full coverage surface with `--features exact + --workspace --lib --tests`. +- `cargo-llvm-cov` excludes workspace `tests/`, `examples/`, and `benches/` + source files from reports by default, while still allowing integration tests + to exercise library code. This matches the intended reporting surface here: + library implementation coverage, not test harness coverage. +- Doc-test coverage remains intentionally disabled because `cargo-llvm-cov` + marks that path as unstable. diff --git a/justfile b/justfile index 81ca5f7..522d6b9 100644 --- a/justfile +++ b/justfile @@ -6,12 +6,23 @@ # Use bash with strict error handling for all recipes set shell := ["bash", "-euo", "pipefail", "-c"] +cargo_llvm_cov_version := "0.8.5" + # Internal helpers: ensure external tooling is installed _ensure-actionlint: #!/usr/bin/env bash set -euo pipefail command -v actionlint >/dev/null || { echo "❌ 'actionlint' not found. See 'just setup' or https://github.com/rhysd/actionlint"; exit 1; } +_ensure-cargo-llvm-cov: + #!/usr/bin/env bash + set -euo pipefail + if ! command -v cargo-llvm-cov >/dev/null; then + echo "❌ 'cargo-llvm-cov' not found. See 'just setup-tools' or install:" + echo " cargo install --locked cargo-llvm-cov --version {{cargo_llvm_cov_version}}" + exit 1 + fi + _ensure-git-cliff: #!/usr/bin/env bash set -euo pipefail @@ -172,7 +183,7 @@ ci: check bench-compile test-all examples # Clean build artifacts clean: cargo clean - rm -rf target/tarpaulin + rm -rf target/llvm-cov rm -rf coverage # Code quality and formatting @@ -183,41 +194,29 @@ clippy: clippy-exact: cargo clippy --features exact --all-targets -- -D warnings -W clippy::pedantic -# Coverage (cargo-tarpaulin) +# Coverage (cargo-llvm-cov) # -# Common tarpaulin arguments for all coverage runs -# Note: -t 300 sets per-test timeout to 5 minutes (needed for slow CI environments) -_coverage_base_args := '''--exclude-files 'benches/*' --exclude-files 'examples/*' \ - --features exact \ +# Common cargo-llvm-cov arguments for all coverage runs. +_coverage_base_args := '''--features exact \ --workspace --lib --tests \ - -t 300 --verbose --implicit-test-threads''' + --verbose''' # Coverage analysis for local development (HTML output) -coverage: +coverage: _ensure-cargo-llvm-cov #!/usr/bin/env bash set -euo pipefail - if ! command -v cargo-tarpaulin >/dev/null 2>&1; then - echo "cargo-tarpaulin not found. Install with: cargo install cargo-tarpaulin" - exit 1 - fi - - mkdir -p target/tarpaulin - cargo tarpaulin {{_coverage_base_args}} --out Html --output-dir target/tarpaulin - echo "Coverage report generated: target/tarpaulin/tarpaulin-report.html" + mkdir -p target/llvm-cov + cargo llvm-cov {{_coverage_base_args}} --open --output-dir target/llvm-cov + echo "Coverage report generated: target/llvm-cov/html/index.html" # Coverage analysis for CI (XML output for codecov/codacy) -coverage-ci: +coverage-ci: _ensure-cargo-llvm-cov #!/usr/bin/env bash set -euo pipefail - if ! command -v cargo-tarpaulin >/dev/null 2>&1; then - echo "cargo-tarpaulin not found. Install with: cargo install cargo-tarpaulin" - exit 1 - fi - mkdir -p coverage - cargo tarpaulin {{_coverage_base_args}} --out Xml --output-dir coverage + cargo llvm-cov {{_coverage_base_args}} --cobertura --output-path coverage/cobertura.xml # Default recipe shows available commands default: @@ -429,7 +428,7 @@ setup-tools: echo "❌ 'rustup' not found. Install Rust via https://rustup.rs and re-run: just setup-tools" exit 1 fi - rustup component add clippy rustfmt rust-docs rust-src + rustup component add clippy rustfmt rust-docs rust-src llvm-tools-preview echo "" echo "Ensuring cargo tools..." @@ -454,21 +453,17 @@ setup-tools: echo " ✓ typos" fi - if ! have cargo-tarpaulin; then - if [[ "$os" == "Linux" ]]; then - echo " ⏳ Installing cargo-tarpaulin (cargo)..." - cargo install --locked cargo-tarpaulin - else - echo " ⚠️ Skipping cargo-tarpaulin install on $os (coverage is typically Linux-only)" - fi + if ! have cargo-llvm-cov; then + echo " ⏳ Installing cargo-llvm-cov {{cargo_llvm_cov_version}} (cargo)..." + cargo install --locked cargo-llvm-cov --version {{cargo_llvm_cov_version}} else - echo " ✓ cargo-tarpaulin" + echo " ✓ cargo-llvm-cov" fi echo "" echo "Verifying required commands are available..." missing=0 - for cmd in uv jq taplo yamllint shfmt shellcheck actionlint node npx typos git-cliff; do + for cmd in uv jq taplo yamllint shfmt shellcheck actionlint node npx typos git-cliff cargo-llvm-cov; do if have "$cmd"; then echo " ✓ $cmd" else