Skip to content

build(deps): bump actions/deploy-pages from 4 to 5 #611

build(deps): bump actions/deploy-pages from 4 to 5

build(deps): bump actions/deploy-pages from 4 to 5 #611

name: Rust Tests & Coverage
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch: # Allows manual trigger from GitHub UI
permissions:
contents: read # Default: read-only (write scoped to job level for badge push)
jobs:
test-and-coverage:
name: Test + Tarpaulin Coverage (Ubuntu)
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
contents: write # Required to push coverage badge (only on main)
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0 # Useful if you need git history for anything
- name: Install Rust stable toolchain
uses: dtolnay/rust-toolchain@d0592fe69e35bc8f12e3dbaf9ad2694d976cb8e3 # stable
with:
toolchain: stable
- name: Cache Cargo dependencies
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.7.7
with:
workspaces: |
rust_crypto
crypto_core
# If you have a workspace root Cargo.toml, it will cache the whole thing automatically
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libpcsclite-dev libudev-dev pkg-config bc
- name: Install cargo-tarpaulin
run: cargo install cargo-tarpaulin
# ── Coverage: crypto_core ──────────────────────────────────────────────
# FIX: Three issues previously caused Codecov to report 0% coverage:
# 1. cdylib crate-type prevents tarpaulin ptrace instrumentation
# 2. Missing --lib --tests flags (defaults to building cdylib target)
# 3. lcov.info paths were "src/..." not "crypto_core/src/..." so
# Codecov couldn't match them to the repo file tree
- name: Run tests with coverage (crypto_core)
id: tarpaulin
run: |
# Temporarily remove cdylib β€” tarpaulin cannot instrument shared libraries
sed -i 's/crate-type = \["lib", "cdylib"\]/crate-type = ["lib"]/' crypto_core/Cargo.toml
# Run from workspace root; tarpaulin discovers the workspace Cargo.toml
# and produces workspace-relative paths (crypto_core/src/...) in lcov.info
cargo tarpaulin \
-p crypto_core \
--lib --tests \
--features "pure-crypto,pq-crypto" \
--out Lcov \
--out Json \
--output-dir ./crypto_core \
--timeout 300 \
--exclude-files "src/wasm.rs" \
--exclude-files "src/hsm.rs" \
--exclude-files "src/tpm.rs" \
--exclude-files "src/yubikey_piv.rs" \
-- --test-threads=1 2>&1 | tee crypto_core/tarpaulin-output.txt
# Restore Cargo.toml so the cdylib removal doesn't leak
git checkout crypto_core/Cargo.toml
# Fix source paths: strip absolute workspace prefix so codecov sees
# repo-relative paths like "crypto_core/src/..."
sed -i "s|^SF:$(pwd)/||" crypto_core/lcov.info || true
# Also fix bare "src/" β†’ "crypto_core/src/" if present
sed -i 's|^SF:src/|SF:crypto_core/src/|' crypto_core/lcov.info || true
# Extract coverage percentage from output
COVERAGE=$(grep -oP '\d+\.\d+% coverage' crypto_core/tarpaulin-output.txt | head -1 | grep -oP '\d+\.\d+')
echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT
echo "Rust coverage: $COVERAGE%"
# ── Coverage: rust_crypto ────────────────────────────────────────────
- name: Run tests with coverage (rust_crypto)
id: tarpaulin_rust_crypto
run: |
# Temporarily remove cdylib β€” tarpaulin cannot instrument shared libraries
sed -i 's/crate-type = \["cdylib", "lib"\]/crate-type = ["lib"]/' rust_crypto/Cargo.toml
# Run from workspace root; tarpaulin produces workspace-relative paths
cargo tarpaulin \
-p meow_crypto_rs \
--lib --tests \
--features "pq,pq-signing" \
--out Lcov \
--out Json \
--output-dir ./rust_crypto \
--timeout 300 \
--exclude-files "src/lib.rs" \
-- --test-threads=1 2>&1 | tee rust_crypto/tarpaulin-output.txt
# Restore Cargo.toml
git checkout rust_crypto/Cargo.toml
# Fix source paths: strip absolute workspace prefix so codecov sees
# repo-relative paths like "rust_crypto/src/..."
sed -i "s|^SF:$(pwd)/||" rust_crypto/lcov.info || true
# Also fix bare "src/" β†’ "rust_crypto/src/" if present
sed -i 's|^SF:src/|SF:rust_crypto/src/|' rust_crypto/lcov.info || true
COVERAGE=$(grep -oP '\d+\.\d+% coverage' rust_crypto/tarpaulin-output.txt | head -1 | grep -oP '\d+\.\d+')
echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT
echo "rust_crypto coverage: $COVERAGE%"
# ── Coverage threshold enforcement ─────────────────────────────────
- name: Enforce coverage thresholds
run: |
CORE="${{ steps.tarpaulin.outputs.coverage }}"
RC="${{ steps.tarpaulin_rust_crypto.outputs.coverage }}"
THRESHOLD=70
echo "============================================"
echo "πŸ“Š Rust Coverage Threshold Enforcement"
echo " Minimum required: ${THRESHOLD}%"
echo "============================================"
echo " crypto_core: ${CORE:-N/A}%"
echo " rust_crypto: ${RC:-N/A}%"
echo ""
FAILED=0
if [ -n "$CORE" ]; then
if (( $(echo "$CORE < $THRESHOLD" | bc -l) )); then
echo "❌ crypto_core coverage ${CORE}% is below ${THRESHOLD}%"
FAILED=1
fi
fi
if [ -n "$RC" ]; then
if (( $(echo "$RC < $THRESHOLD" | bc -l) )); then
echo "❌ rust_crypto coverage ${RC}% is below ${THRESHOLD}%"
FAILED=1
fi
fi
if [ "$FAILED" -eq 1 ]; then
echo ""
echo "Coverage threshold not met. Add tests to increase coverage."
exit 1
fi
echo "βœ… All Rust crates meet ${THRESHOLD}% coverage threshold"
- name: Generate coverage badge
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: |
COVERAGE_CORE="${{ steps.tarpaulin.outputs.coverage }}"
COVERAGE_RUST_CRYPTO="${{ steps.tarpaulin_rust_crypto.outputs.coverage }}"
# Calculate combined coverage (simple average)
# In future, could weight by lines of code
if [ -n "$COVERAGE_CORE" ] && [ -n "$COVERAGE_RUST_CRYPTO" ]; then
COVERAGE=$(echo "scale=2; ($COVERAGE_CORE + $COVERAGE_RUST_CRYPTO) / 2" | bc)
elif [ -n "$COVERAGE_CORE" ]; then
COVERAGE="$COVERAGE_CORE"
else
COVERAGE="0"
fi
echo "Combined Rust coverage: ${COVERAGE}% (crypto_core: ${COVERAGE_CORE}%, rust_crypto: ${COVERAGE_RUST_CRYPTO}%)"
# Determine badge color based on coverage
if (( $(echo "$COVERAGE >= 90" | bc -l) )); then
COLOR="4c1" # brightgreen
elif (( $(echo "$COVERAGE >= 80" | bc -l) )); then
COLOR="97ca00" # green
elif (( $(echo "$COVERAGE >= 70" | bc -l) )); then
COLOR="a4a61d" # yellowgreen
elif (( $(echo "$COVERAGE >= 60" | bc -l) )); then
COLOR="dfb317" # yellow
else
COLOR="e05d44" # red
fi
# Generate SVG badge
cat > assets/coverage.svg << EOF
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="168" height="20" role="img" aria-label="Rust Combined: ${COVERAGE}%">
<title>Rust Combined Coverage: ${COVERAGE}%</title>
<linearGradient id="s" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="r">
<rect width="168" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#r)">
<rect width="120" height="20" fill="#555"/>
<rect x="120" width="48" height="20" fill="#${COLOR}"/>
<rect width="168" height="20" fill="url(#s)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110">
<text aria-hidden="true" x="610" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="1100">Rust Combined</text>
<text x="610" y="140" transform="scale(.1)" fill="#fff" textLength="1100">Rust Combined</text>
<text aria-hidden="true" x="1430" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="380">${COVERAGE}%</text>
<text x="1430" y="140" transform="scale(.1)" fill="#fff" textLength="380">${COVERAGE}%</text>
</g>
</svg>
EOF
echo "Generated badge with ${COVERAGE}% combined coverage (crypto_core: ${COVERAGE_CORE}%, rust_crypto: ${COVERAGE_RUST_CRYPTO}%)"
- name: Commit coverage badge
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
env:
BADGE_PUSH_TOKEN: ${{ secrets.BADGE_PUSH_TOKEN }}
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add assets/coverage.svg
if ! git diff --staged --quiet; then
git commit -m "chore: update Rust coverage badge [skip ci]"
if [ -n "$BADGE_PUSH_TOKEN" ]; then
git push https://x-access-token:${BADGE_PUSH_TOKEN}@github.com/${{ github.repository }}.git HEAD:main
else
echo "::warning::BADGE_PUSH_TOKEN not set - skipping push to protected branch"
fi
fi
- name: Upload coverage to Codecov
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.3.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./crypto_core/lcov.info,./rust_crypto/lcov.info
flags: rust
name: rust-coverage
fail_ci_if_error: true
- name: Run regular tests (crypto_core, without coverage)
if: always()
run: |
cd crypto_core && cargo test --features "pure-crypto,pq-crypto"
- name: Run regular tests (rust_crypto, without coverage)
if: always()
run: |
cd rust_crypto && cargo test --features "pq,pq-signing"