name: CI
on:
push:
branches: [main, master]
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -D warnings
jobs:
check:
name: Check
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
components: rustfmt, clippy
- name: Cache cargo registry and build
uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6
- name: Check formatting
run: cargo fmt --all -- --check
- name: Run Clippy (no features)
run: cargo clippy --all-targets -- -D warnings
- name: Run Clippy (all features)
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Build (no features)
run: cargo build --all-targets
- name: Build (all features)
run: cargo build --all-targets --all-features
- name: Run tests (no features)
run: cargo test -- --nocapture 2>&1 | tee test-output-default.log
continue-on-error: true
- name: Run tests (all features)
run: cargo test --all-features -- --nocapture 2>&1 | tee test-output-all-features.log
continue-on-error: true
- name: Check test results
run: |
cargo test 2>/dev/null
cargo test --all-features 2>/dev/null
- name: Upload test logs
if: always()
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 with:
name: test-logs
path: |
test-output-*.log
**/snapshots/*.snap.new
retention-days: 30
conformance-fixtures:
name: Conformance (Python fixtures)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
- name: Cache cargo registry and build
uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 with:
key: conformance-fixtures
- name: Run fixture-based conformance tests
run: cargo test --test conformance_python --features "conformance_test,syntax,markdown,json"
feature-matrix:
name: Test Features (${{ matrix.features || 'default' }})
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
features:
- ""
- "syntax"
- "markdown"
- "json"
- "full"
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
- name: Cache cargo registry and build
uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 with:
key: features-${{ matrix.features }}
- name: Build with features
run: |
if [ -z "${{ matrix.features }}" ]; then
cargo build --all-targets
else
cargo build --all-targets --features "${{ matrix.features }}"
fi
- name: Test with features
run: |
FEATURE_SUFFIX="${{ matrix.features || 'default' }}"
if [ -z "${{ matrix.features }}" ]; then
cargo test -- --nocapture 2>&1 | tee "test-output-${FEATURE_SUFFIX}.log"
else
cargo test --features "${{ matrix.features }}" -- --nocapture 2>&1 | tee "test-output-${FEATURE_SUFFIX}.log"
fi
- name: Upload test logs on failure
if: failure()
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 with:
name: test-logs-features-${{ matrix.features || 'default' }}
path: |
test-output-*.log
**/snapshots/*.snap.new
retention-days: 14
test-platform:
name: Test (${{ matrix.name }})
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- name: Linux x64
os: ubuntu-latest
- name: Linux ARM64
os: ubuntu-24.04-arm
- name: macOS ARM64
os: macos-14
- name: macOS x64
os: macos-15
- name: Windows x64
os: windows-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
- name: Cache cargo registry and build
uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 with:
key: ${{ matrix.os }}
- name: Run tests
shell: bash
run: cargo test --all-features -- --nocapture 2>&1 | tee test-output-${{ matrix.name }}.log
- name: Upload test logs on failure
if: failure()
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 with:
name: test-logs-${{ matrix.name }}
path: |
test-output-*.log
**/snapshots/*.snap.new
retention-days: 14
msrv:
name: MSRV Check
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
- name: Cache cargo registry and build
uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 with:
key: msrv
- name: Check compilation
run: cargo check --all-features
security:
name: Security Audit
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
- name: Install cargo-audit
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 with:
tool: cargo-audit
- name: Run security audit
run: cargo audit
continue-on-error: true
docs:
name: Documentation
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
- name: Cache cargo registry and build
uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 with:
key: docs
- name: Build documentation
run: cargo doc --all-features --no-deps
env:
RUSTDOCFLAGS: -D warnings
- name: Run doc tests
run: cargo test --doc --all-features
benchmarks:
name: Benchmarks
runs-on: ubuntu-latest
timeout-minutes: 30
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
- name: Cache cargo registry and build
uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 with:
key: benchmarks
- name: Run benchmarks
run: cargo bench --all-features -- --noplot 2>&1 | tee bench-output.log
- name: Upload benchmark results
if: always()
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 with:
name: benchmark-results
path: |
bench-output.log
target/criterion
retention-days: 90
coverage:
name: Coverage
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
components: llvm-tools-preview
- name: Cache cargo registry and build
uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 with:
key: coverage
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 with:
tool: cargo-llvm-cov
- name: Generate coverage report
run: |
cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info
cargo llvm-cov report --html --output-dir coverage-html
- name: Generate per-module coverage summary
run: |
echo "## Coverage Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cargo llvm-cov report --all-features 2>/dev/null | head -50 >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
- name: Check coverage threshold
id: coverage_check
run: |
COVERAGE=$(cargo llvm-cov report --all-features 2>/dev/null | grep -E "^TOTAL" | awk '{print $4}' | tr -d '%' || echo "0")
echo "coverage=${COVERAGE}" >> $GITHUB_OUTPUT
echo "Overall coverage: ${COVERAGE}%"
# Threshold check (informational, doesn't fail CI)
THRESHOLD=80
if (( $(echo "$COVERAGE < $THRESHOLD" | bc -l) )); then
echo "::warning::Coverage ${COVERAGE}% is below target ${THRESHOLD}%"
else
echo "✓ Coverage ${COVERAGE}% meets target ${THRESHOLD}%"
fi
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
files: lcov.info
fail_ci_if_error: false
verbose: true
- name: Upload coverage HTML report
if: always()
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 with:
name: coverage-report
path: coverage-html
retention-days: 30
- name: Upload lcov for historical tracking
if: always()
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 with:
name: lcov-info
path: lcov.info
retention-days: 90