delaunay 0.7.6

D-dimensional Delaunay triangulations and convex hulls in Rust, with exact predicates, multi-level validation, and bistellar flips
Documentation
name: Codecov
concurrency:
  # This concurrency group ensures that only one Codecov analysis runs at a time
  group: codecov-${{ github.ref_name }}
  cancel-in-progress: true

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

# Least-privilege permissions
permissions:
  contents: read
  checks: write
  pull-requests: write

jobs:
  coverage:
    name: Code Coverage
    runs-on: ubuntu-latest
    env:
      CARGO_LLVM_COV_VERSION: "0.8.5"
    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0 # Needed for codecov to analyze diff

      - name: Install Rust toolchain
        uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
        with:
          cache: true
          # toolchain, components, etc. are specified in rust-toolchain.toml

      - name: Install LLVM coverage tools
        run: rustup component add llvm-tools-preview

      - name: Cache cargo-llvm-cov
        uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
        with:
          path: ~/.cargo/bin/cargo-llvm-cov
          key: cargo-llvm-cov-${{ runner.os }}-${{ env.CARGO_LLVM_COV_VERSION }}
          restore-keys: |
            cargo-llvm-cov-${{ runner.os }}-

      - name: Install cargo-llvm-cov
        run: |
          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
        uses: taiki-e/install-action@055f5df8c3f65ea01cd41e9dc855becd88953486 # v2.75.18
        with:
          tool: just

      - name: Install nextest
        uses: taiki-e/install-action@055f5df8c3f65ea01cd41e9dc855becd88953486 # v2.75.18
        with:
          tool: nextest

      - name: Run tests with nextest (for JUnit XML)
        run: |
          # Note: We run tests twice in this workflow (nextest + cargo-llvm-cov).
          # This is intentional and necessary because:
          # 1. nextest: Fast parallel execution → high-quality JUnit XML for test analytics
          # 2. cargo-llvm-cov: LLVM-instrumented execution → accurate code coverage metrics
          # Trade-off: ~1-2 extra minutes of CI time for significantly better data quality.

          echo "::group::Running tests with nextest"
          # Generate JUnit XML for Codecov Test Analytics
          # JUnit path is configured in .config/nextest.toml [profile.ci.junit]
          # Release mode is required: exact-predicate arithmetic makes 3D+ proptests
          # exceed timeout limits in debug mode (>60s debug vs <1s release).
          # Avoid --all-features: it enables slow-tests and count-allocations which
          # are special-purpose features not suitable for general test runs.
          cargo nextest run --release --profile ci
          echo "::endgroup::"

          # Verify JUnit XML was generated at the expected location
          # Nextest outputs to target/nextest/<profile>/<path-from-config>
          if [ ! -f target/nextest/ci/test-results/junit.xml ]; then
            echo "::error::target/nextest/ci/test-results/junit.xml not found"
            exit 2
          else
            echo "::notice::Test results generated: $(wc -l < target/nextest/ci/test-results/junit.xml) lines"
          fi
        env:
          RUST_BACKTRACE: 1

      - name: Run coverage
        run: |
          # Create coverage directory with proper permissions
          mkdir -p coverage
          chmod 755 coverage

          echo "::group::Running coverage"
          # Use just coverage-ci for single source of truth
          # Configuration is maintained in justfile to avoid duplication
          just coverage-ci
          echo "::endgroup::"

          # Detailed sanity check: verify coverage report was generated
          echo "::group::Coverage verification"
          echo "Directory listing:"
          ls -la coverage/ || echo "Coverage directory not found"

          printf "\nSearching for XML files:\n"
          find . -name "*.xml" -type f -ls 2>/dev/null || echo "No XML files found"

          printf "\nSearching for cobertura files:\n"
          find . -name "*cobertura*" -type f -ls 2>/dev/null || echo "No cobertura files found"

          if [ ! -f coverage/cobertura.xml ]; then
            echo "::error::coverage/cobertura.xml not found. cargo-llvm-cov failed to generate XML output."
            echo "::error::Check cargo-llvm-cov logs above for errors."
            exit 2
          else
            coverage_bytes=$(wc -c < coverage/cobertura.xml)
            coverage_newlines=$(wc -l < coverage/cobertura.xml)
            coverage_lines_covered=$(
              grep -oE 'lines-covered="[0-9]+"' coverage/cobertura.xml \
                | head -n1 \
                | cut -d'"' -f2 || true
            )
            coverage_lines_valid=$(
              grep -oE 'lines-valid="[0-9]+"' coverage/cobertura.xml \
                | head -n1 \
                | cut -d'"' -f2 || true
            )
            coverage_line_rate=$(
              grep -oE 'line-rate="[0-9.]+"' coverage/cobertura.xml \
                | head -n1 \
                | cut -d'"' -f2 || true
            )

            if [ -n "${coverage_lines_covered}" ] \
              && [ -n "${coverage_lines_valid}" ] \
              && [ -n "${coverage_line_rate}" ]; then
              echo "::notice::Coverage report generated successfully:"
              echo "::notice::  bytes=${coverage_bytes}, xml_newlines=${coverage_newlines}"
              echo "::notice::  covered=${coverage_lines_covered}, valid=${coverage_lines_valid}"
              echo "::notice::  rate=${coverage_line_rate}"
            else
              echo "::warning::Coverage report generated (${coverage_bytes} bytes),"
              echo "::warning::  could not parse Cobertura metrics; XML newlines=${coverage_newlines}"
            fi
          fi
          # Sanity: ensure benches/examples aren't present (defense-in-depth with .codecov.yml)
          if command -v rg >/dev/null 2>&1; then
            if rg -n '(^|/)(benches|examples)/' coverage/cobertura.xml; then
              echo "::warning::benches/ or examples/ paths detected in coverage report"
            fi
          else
            if grep -nE '(^|/)(benches|examples)/' coverage/cobertura.xml; then
              echo "::warning::benches/ or examples/ paths detected in coverage report"
            fi
          fi
          echo "::endgroup::"
        env:
          RUST_BACKTRACE: 1
          DELAUNAY_PROPTEST_COVERAGE_LOGS: 1

      - name: Upload coverage to Codecov
        if: ${{ success() && hashFiles('coverage/cobertura.xml') != '' }}
        uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
        with:
          files: coverage/cobertura.xml
          flags: unittests
          name: codecov-umbrella
          fail_ci_if_error: false
        env:
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

      - name: Upload test results to Codecov
        if: ${{ success() && hashFiles('target/nextest/ci/test-results/junit.xml') != '' }}
        uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
        with:
          files: target/nextest/ci/test-results/junit.xml
          flags: unittests
          name: test-results
          report_type: test_results
          fail_ci_if_error: false
        env:
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

      - name: Upload coverage to Codacy
        if: ${{ success() && hashFiles('coverage/cobertura.xml') != '' }}
        uses: codacy/codacy-coverage-reporter-action@89d6c85cfafaec52c72b6c5e8b2878d33104c699 # v1.3.0
        with:
          project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
          coverage-reports: coverage/cobertura.xml
          language: rust
        continue-on-error: true

      - name: Archive coverage results
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        if: always()
        with:
          name: coverage-report
          path: coverage/

      - name: Archive test results
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        if: always()
        with:
          name: test-results
          path: target/nextest/ci/test-results/