name: Quality Checks
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
lint:
name: Lint (Clippy + Rustfmt)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }}
- name: Run rustfmt check
run: cargo fmt --all -- --check
- name: Run clippy
run: cargo clippy --all-features --all-targets -- -D warnings
bashrs:
name: bashrs Shell Safety
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install bashrs
run: cargo install bashrs --version ">=6.0.0" || cargo install bashrs
- name: Lint Makefile
run: bashrs make lint Makefile
- name: Lint shell scripts
run: |
for script in $(find . -type f -name "*.sh" ! -path "./target/*" ! -path "./.git/*"); do
echo "Linting $script..."
bashrs lint "$script" || true
done
test:
name: Test Suite
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }}
- name: Run tests
run: cargo test --all-features --verbose
coverage:
name: Code Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-cargo-coverage-${{ hashFiles('**/Cargo.lock') }}
- name: Generate coverage
run: cargo llvm-cov --all-features --lcov --output-path lcov.info
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: lcov.info
fail_ci_if_error: false
- name: Check coverage threshold
run: |
COVERAGE=$(cargo llvm-cov --all-features | grep -oP '\d+\.\d+(?=%)' | head -1)
echo "Coverage: ${COVERAGE}%"
if (( $(echo "$COVERAGE < 90.0" | bc -l) )); then
echo "❌ Coverage ${COVERAGE}% is below 90% threshold"
exit 1
fi
echo "✅ Coverage ${COVERAGE}% meets ≥90% threshold"
pmat:
name: PMAT Quality Analysis
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Install PMAT
run: cargo install pmat || echo "Using existing pmat"
- name: Check for SATD
run: |
pmat analyze satd --path . --fail-on-any || exit 1
- name: Complexity Analysis
run: |
pmat analyze complexity --fail-above 20 --path . || exit 1
- name: Dead Code Analysis
run: |
pmat analyze dead-code --path . || true
build:
name: Build (Debug + Release)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }}
- name: Build debug
run: cargo build --all-features --verbose
- name: Build release
run: cargo build --all-features --release --verbose
quality-gate:
name: Quality Gate
runs-on: ubuntu-latest
needs: [lint, bashrs, test, coverage, pmat, build]
if: always()
steps:
- name: Check all jobs passed
run: |
if [[ "${{ needs.lint.result }}" != "success" ]] ||
[[ "${{ needs.bashrs.result }}" != "success" ]] ||
[[ "${{ needs.test.result }}" != "success" ]] ||
[[ "${{ needs.coverage.result }}" != "success" ]] ||
[[ "${{ needs.pmat.result }}" != "success" ]] ||
[[ "${{ needs.build.result }}" != "success" ]]; then
echo "❌ One or more quality checks failed"
exit 1
fi
echo "✅ All quality gates passed"