CI #1412
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [ main, develop, 'release/**' ] | |
| pull_request: | |
| branches: [ main ] | |
| paths: | |
| - '**/*.rs' | |
| - '**/Cargo.toml' | |
| - 'Cargo.lock' | |
| - '.github/workflows/**' | |
| - 'clippy.toml' | |
| - 'deny.toml' | |
| - 'specs/**' | |
| schedule: | |
| - cron: '0 3 * * *' # 3 AM UTC daily (regular jobs + nightly fuzz) | |
| - cron: '0 6 * * 0' # 6 AM UTC Sunday (weekly mutation testing) | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event_name == 'schedule' && github.run_id || github.ref }} | |
| cancel-in-progress: ${{ github.event_name != 'schedule' }} | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUST_BACKTRACE: 1 | |
| jobs: | |
| validate: | |
| name: Validation Pipeline | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: 1.92.0 | |
| components: rustfmt, clippy | |
| - name: Cache dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Install Linux HID build deps | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libudev-dev | |
| - name: Run validation pipeline | |
| run: cargo xtask validate | |
| - name: Verify COMPATIBILITY.md is up-to-date | |
| run: | | |
| cargo xtask gen-compat | |
| if ! git diff --exit-code COMPATIBILITY.md; then | |
| echo "❌ COMPATIBILITY.md is stale — run 'cargo xtask gen-compat' and commit the result" | |
| exit 1 | |
| fi | |
| echo "✅ COMPATIBILITY.md is up-to-date" | |
| - name: Cross-check spec_ledger vs feature files | |
| if: always() | |
| run: | | |
| REQ_COUNT=$(grep -cE '^\s*- id: REQ-' specs/spec_ledger.yaml || echo 0) | |
| FEATURE_COUNT=$(find specs/features -name '*.feature' | wc -l) | |
| echo "spec_ledger REQ IDs: $REQ_COUNT" | |
| echo ".feature files: $FEATURE_COUNT" | |
| if [ "$FEATURE_COUNT" -eq 0 ]; then | |
| echo "⚠️ No .feature files found — skipping divergence check" | |
| elif [ "$REQ_COUNT" -gt 0 ]; then | |
| RATIO=$(( FEATURE_COUNT * 100 / REQ_COUNT )) | |
| if [ "$RATIO" -lt 50 ] || [ "$RATIO" -gt 200 ]; then | |
| echo "⚠️ Significant divergence: $REQ_COUNT REQ IDs vs $FEATURE_COUNT .feature files (ratio ${RATIO}%)" | |
| echo " Consider reviewing whether spec_ledger.yaml and specs/features/ are in sync." | |
| else | |
| echo "✅ spec_ledger and feature files are reasonably aligned (ratio ${RATIO}%)" | |
| fi | |
| fi | |
| - name: Upload validation report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: validation-report | |
| path: docs/validation_report.md | |
| - name: Upload feature status | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: feature-status | |
| path: | | |
| docs/feature_status.md | |
| docs/bdd_metrics.json | |
| bdd-tests: | |
| name: BDD Specification Tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| needs: validate | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: 1.92.0 | |
| - name: Cache dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Run BDD tests | |
| run: cargo test -p specs | |
| - name: Upload BDD test results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: bdd-test-results | |
| path: target/cucumber-report.json | |
| test: | |
| name: Test Suite | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: ${{ matrix.os == 'windows-latest' && 30 || 20 }} | |
| needs: validate | |
| defaults: | |
| run: | |
| shell: bash | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest] | |
| rust: [stable, 1.92.0] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| id: rust-toolchain | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: ${{ matrix.rust }} | |
| components: rustfmt, clippy | |
| - name: Get toolchain hash | |
| id: toolchain-hash | |
| shell: bash | |
| run: echo "cachekey=$(rustc -Vv | sha256sum | cut -d' ' -f1)" >> $GITHUB_OUTPUT | |
| - name: Cache dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: ${{ runner.os }}-${{ steps.toolchain-hash.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }} | |
| - name: Install Linux HID build deps | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libudev-dev | |
| - name: Check formatting | |
| run: cargo fmt --all -- --check | |
| - name: Run clippy (general - warnings allowed for dev ergonomics) | |
| run: cargo clippy --workspace --all-features --lib --tests --exclude flight-hub-examples | |
| - name: Run clippy with strict warnings for core crates | |
| run: | | |
| echo "🔍 Running strict clippy checks for core crates..." | |
| STRICT_FLAGS="-D warnings -A clippy::manual-range-contains -A clippy::type-complexity -A clippy::field-reassign-with-default -A clippy::let-and-return -A clippy::derivable-impls" | |
| # Apply strict warnings to core crates that must maintain high quality | |
| cargo clippy -p flight-core --lib -- $STRICT_FLAGS | |
| cargo clippy -p flight-axis --lib -- $STRICT_FLAGS | |
| cargo clippy -p flight-bus --lib -- $STRICT_FLAGS | |
| cargo clippy -p flight-hid --lib -- $STRICT_FLAGS | |
| cargo clippy -p flight-ipc --lib -- $STRICT_FLAGS | |
| cargo clippy -p flight-service --lib -- $STRICT_FLAGS | |
| cargo clippy -p flight-simconnect --lib -- $STRICT_FLAGS | |
| cargo clippy -p flight-panels --lib -- $STRICT_FLAGS | |
| - name: Run file descriptor safety tests for public API crates | |
| run: | | |
| echo "🔍 Running file descriptor safety tests for public API crates..." | |
| # Test that public API crates use typed file descriptors instead of RawFd | |
| cargo test -p flight-hid fd_safety_tests | |
| cargo test -p flight-ipc fd_safety_tests | |
| cargo test -p flight-service fd_safety_tests | |
| - name: Verify critical patterns are fixed | |
| run: | | |
| echo "🔍 Verifying critical patterns are fixed..." | |
| # Check Profile::merge is replaced with Profile::merge_with (source code only) | |
| if git grep -n "Profile::merge(" -- 'crates/*/src/' | grep -v "Profile::merge_with"; then | |
| echo "❌ Found Profile::merge( calls - should be Profile::merge_with" | |
| exit 1 | |
| fi | |
| # Check BlackboxWriter::new doesn't have ? operator inappropriately | |
| if git grep -n "BlackboxWriter::new.*?" -- 'crates/*/src/'; then | |
| echo "❌ Found BlackboxWriter::new with ? operator" | |
| exit 1 | |
| fi | |
| # Check Engine::new has correct signature (2 arguments) | |
| if git grep -n "Engine::new(" -- 'crates/*/src/' | grep -v ","; then | |
| echo "❌ Found Engine::new with incorrect signature" | |
| exit 1 | |
| fi | |
| # Check for criterion::black_box usage | |
| if git grep -n "criterion::black_box" -- 'crates/' 'benches/'; then | |
| echo "❌ Found criterion::black_box - should be std::hint::black_box" | |
| exit 1 | |
| fi | |
| echo "✅ All critical patterns verified" | |
| - name: Run tests | |
| run: cargo test --all-features --workspace --lib --tests --exclude flight-hub-examples | |
| - name: Run flight-integration-tests explicitly | |
| run: cargo test -p flight-integration-tests | |
| - name: Run doc tests | |
| run: cargo test --doc --workspace | |
| - name: Validate ADR links | |
| run: | | |
| echo "🔍 Validating ADR files exist..." | |
| for adr in docs/explanation/adr/001-rt-spine-architecture.md docs/explanation/adr/002-writers-as-data.md docs/explanation/adr/003-plugin-classes.md docs/explanation/adr/004-zero-allocation-constraint.md docs/explanation/adr/005-pll-timing-discipline.md; do | |
| if [ ! -f "$adr" ]; then | |
| echo "❌ Missing ADR: $adr" | |
| exit 1 | |
| else | |
| echo "✅ Found: $adr" | |
| fi | |
| done | |
| echo "✅ All referenced ADRs exist" | |
| performance-check: | |
| name: Performance Regression Check | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| if: github.event_name == 'pull_request' | |
| continue-on-error: true # Advisory only for now | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Install Linux HID build deps | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libudev-dev | |
| - name: Run performance regression check | |
| run: cargo xtask bench-compare --threshold 500 | |
| msrv-check: | |
| name: MSRV Check (Rust 1.92.0) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust 1.92.0 | |
| id: rust-toolchain | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: 1.92.0 | |
| components: clippy | |
| - name: Get toolchain hash | |
| id: toolchain-hash | |
| shell: bash | |
| run: echo "cachekey=$(rustc -Vv | sha256sum | cut -d' ' -f1)" >> $GITHUB_OUTPUT | |
| - name: Cache dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: ${{ runner.os }}-${{ steps.toolchain-hash.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }} | |
| - name: Install Linux HID build deps | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libudev-dev | |
| - name: Build with MSRV | |
| run: cargo build --workspace --all-features --exclude flight-hub-examples | |
| - name: Run clippy with MSRV | |
| run: | | |
| STRICT_FLAGS="-D warnings -A clippy::manual-range-contains -A clippy::type-complexity -A clippy::field-reassign-with-default -A clippy::let-and-return -A clippy::derivable-impls" | |
| cargo clippy -p flight-core --lib -- $STRICT_FLAGS | |
| cargo clippy -p flight-axis --lib -- $STRICT_FLAGS | |
| cargo clippy -p flight-bus --lib -- $STRICT_FLAGS | |
| cargo clippy -p flight-hid --lib -- $STRICT_FLAGS | |
| cargo clippy -p flight-ipc --lib -- $STRICT_FLAGS | |
| cargo clippy -p flight-service --lib -- $STRICT_FLAGS | |
| cargo clippy -p flight-simconnect --lib -- $STRICT_FLAGS | |
| cargo clippy -p flight-panels --lib -- $STRICT_FLAGS | |
| path-filter: | |
| name: Path Filter | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request' | |
| outputs: | |
| ipc: ${{ steps.filter.outputs.ipc }} | |
| hid: ${{ steps.filter.outputs.hid }} | |
| core: ${{ steps.filter.outputs.core }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Filter paths | |
| uses: dorny/paths-filter@v3 | |
| id: filter | |
| with: | |
| filters: | | |
| ipc: | |
| - 'crates/flight-ipc/**' | |
| hid: | |
| - 'crates/flight-hid/**' | |
| core: | |
| - 'crates/flight-core/**' | |
| - 'Cargo.toml' | |
| - 'clippy.toml' | |
| clippy-core: | |
| name: Clippy - flight-core | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: ${{ matrix.os == 'windows-latest' && 45 || 30 }} | |
| needs: path-filter | |
| if: | | |
| always() && | |
| (github.event_name != 'pull_request' || | |
| needs.path-filter.outputs.core == 'true') | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| id: rust-toolchain | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: 1.92.0 | |
| components: rustfmt, clippy | |
| - name: Get toolchain hash | |
| id: toolchain-hash | |
| shell: bash | |
| run: echo "cachekey=$(rustc -Vv | sha256sum | cut -d' ' -f1)" >> $GITHUB_OUTPUT | |
| - name: Cache cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cargo/registry | |
| key: ${{ runner.os }}-${{ steps.toolchain-hash.outputs.cachekey }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-registry- | |
| - name: Cache cargo index | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cargo/git | |
| key: ${{ runner.os }}-${{ steps.toolchain-hash.outputs.cachekey }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-index- | |
| - name: Cache target directory | |
| uses: actions/cache@v4 | |
| with: | |
| path: target | |
| key: ${{ runner.os }}-${{ steps.toolchain-hash.outputs.cachekey }}-target-clippy-core-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-target-clippy-core- | |
| - name: Check formatting | |
| run: cargo fmt --all -- --check | |
| - name: Run clippy on flight-core | |
| run: cargo clippy -p flight-core -- -Dwarnings | |
| clippy-ipc-benches: | |
| name: Clippy - IPC Benches (${{ matrix.mode }}) | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: ${{ matrix.os == 'windows-latest' && 45 || 30 }} | |
| needs: path-filter | |
| if: | | |
| always() && | |
| (github.event_name != 'pull_request' || | |
| needs.path-filter.outputs.ipc == 'true' || | |
| needs.path-filter.outputs.core == 'true') | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest] | |
| mode: [strict] | |
| include: | |
| # Add unblock mode only when label is present | |
| - os: ubuntu-latest | |
| mode: unblock | |
| - os: windows-latest | |
| mode: unblock | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Check if unblock mode should run | |
| if: matrix.mode == 'unblock' | |
| id: check-label | |
| run: | | |
| if [[ "${{ contains(github.event.pull_request.labels.*.name, 'clippy-unblock') }}" == "true" ]]; then | |
| echo "run=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "run=false" >> $GITHUB_OUTPUT | |
| echo "Skipping unblock mode - label 'clippy-unblock' not present" | |
| fi | |
| shell: bash | |
| - name: Exit early if unblock mode not needed | |
| if: matrix.mode == 'unblock' && steps.check-label.outputs.run != 'true' | |
| run: exit 0 | |
| - name: Install Rust toolchain | |
| if: matrix.mode == 'strict' || steps.check-label.outputs.run == 'true' | |
| id: rust-toolchain | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: 1.92.0 | |
| components: clippy | |
| - name: Install Linux HID build deps | |
| if: (matrix.mode == 'strict' || steps.check-label.outputs.run == 'true') && runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libudev-dev | |
| - name: Get toolchain hash | |
| if: matrix.mode == 'strict' || steps.check-label.outputs.run == 'true' | |
| id: toolchain-hash | |
| shell: bash | |
| run: echo "cachekey=$(rustc -Vv | sha256sum | cut -d' ' -f1)" >> $GITHUB_OUTPUT | |
| - name: Cache cargo registry | |
| if: matrix.mode == 'strict' || steps.check-label.outputs.run == 'true' | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cargo/registry | |
| key: ${{ runner.os }}-${{ steps.toolchain-hash.outputs.cachekey }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-registry- | |
| - name: Cache cargo index | |
| if: matrix.mode == 'strict' || steps.check-label.outputs.run == 'true' | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cargo/git | |
| key: ${{ runner.os }}-${{ steps.toolchain-hash.outputs.cachekey }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-index- | |
| - name: Cache target directory | |
| if: matrix.mode == 'strict' || steps.check-label.outputs.run == 'true' | |
| uses: actions/cache@v4 | |
| with: | |
| path: target | |
| key: ${{ runner.os }}-${{ steps.toolchain-hash.outputs.cachekey }}-target-clippy-ipc-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-target-clippy-ipc- | |
| - name: Clippy IPC benches (strict - with deps) | |
| if: matrix.mode == 'strict' | |
| run: | | |
| cargo clippy -p flight-ipc --benches --features ipc-bench -- -Dwarnings | |
| cargo clippy -p flight-ipc --benches --features "ipc-bench,ipc-bench-serde" -- -Dwarnings | |
| - name: Clippy IPC benches (unblock - no deps) | |
| if: matrix.mode == 'unblock' && steps.check-label.outputs.run == 'true' | |
| run: | | |
| cargo clippy -p flight-ipc --no-deps --benches --features ipc-bench -- -Dwarnings | |
| cargo clippy -p flight-ipc --no-deps --benches --features "ipc-bench,ipc-bench-serde" -- -Dwarnings | |
| public-api-check: | |
| name: Public API Guard | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| continue-on-error: true # Gracefully degrade when cargo-public-api can't parse rustdoc JSON | |
| if: github.event_name == 'pull_request' | |
| needs: path-filter | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install Rust nightly toolchain | |
| id: rust-toolchain | |
| uses: dtolnay/rust-toolchain@nightly | |
| - name: Install Rust stable toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Get toolchain hash | |
| id: toolchain-hash | |
| shell: bash | |
| run: echo "cachekey=$(rustc +nightly -Vv | sha256sum | cut -d' ' -f1)" >> $GITHUB_OUTPUT | |
| - name: Cache cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cargo/registry | |
| key: ${{ runner.os }}-${{ steps.toolchain-hash.outputs.cachekey }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-registry- | |
| - name: Cache cargo index | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cargo/git | |
| key: ${{ runner.os }}-${{ steps.toolchain-hash.outputs.cachekey }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-index- | |
| - name: Cache target directory | |
| uses: actions/cache@v4 | |
| with: | |
| path: target | |
| key: ${{ runner.os }}-${{ steps.toolchain-hash.outputs.cachekey }}-target-public-api-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-target-public-api- | |
| - name: Install Linux HID build deps | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libudev-dev | |
| - name: Install cargo-public-api (latest) | |
| run: | | |
| cargo install --locked cargo-public-api || { | |
| echo "::warning::Failed to install cargo-public-api — skipping public API checks" | |
| exit 0 | |
| } | |
| - name: Check flight-core public API | |
| if: needs.path-filter.outputs.core == 'true' | |
| continue-on-error: true | |
| run: | | |
| cargo +nightly public-api -p flight-core diff origin/main..HEAD 2>&1 || { | |
| echo "::warning::flight-core public API check failed (possible rustdoc JSON format mismatch)" | |
| } | |
| - name: Check flight-ipc public API | |
| if: needs.path-filter.outputs.ipc == 'true' | |
| continue-on-error: true | |
| run: | | |
| cargo +nightly public-api -p flight-ipc diff origin/main..HEAD 2>&1 || { | |
| echo "::warning::flight-ipc public API check failed (possible rustdoc JSON format mismatch)" | |
| } | |
| - name: Check flight-hid public API | |
| if: needs.path-filter.outputs.hid == 'true' | |
| continue-on-error: true | |
| run: | | |
| cargo +nightly public-api -p flight-hid diff origin/main..HEAD 2>&1 || { | |
| echo "::warning::flight-hid public API check failed (possible rustdoc JSON format mismatch)" | |
| } | |
| gated-ipc-smoke: | |
| name: Gated IPC Smoke Tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| needs: path-filter | |
| if: | | |
| always() && | |
| (github.event_name == 'schedule' || | |
| github.event_name == 'workflow_dispatch' || | |
| contains(github.event.pull_request.labels.*.name, 'run-gated') || | |
| needs.path-filter.outputs.ipc == 'true') | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Install Linux HID build deps | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libudev-dev | |
| - name: Smoke test IPC benchmarks | |
| run: cargo bench --no-run -p flight-ipc --features ipc-bench | |
| - name: Smoke test IPC benchmarks with serde | |
| run: cargo bench --no-run -p flight-ipc --features "ipc-bench,ipc-bench-serde" | |
| - name: Clippy on IPC benchmarks | |
| run: cargo clippy -p flight-ipc --benches --features ipc-bench -- -Dwarnings | |
| - name: Clippy on IPC benchmarks with serde | |
| run: cargo clippy -p flight-ipc --benches --features "ipc-bench,ipc-bench-serde" -- -Dwarnings | |
| gated-hid-smoke: | |
| name: Gated HID Smoke Tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| needs: path-filter | |
| if: | | |
| always() && | |
| (github.event_name == 'schedule' || | |
| github.event_name == 'workflow_dispatch' || | |
| contains(github.event.pull_request.labels.*.name, 'run-gated') || | |
| needs.path-filter.outputs.hid == 'true') | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Install Linux HID build deps | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libudev-dev | |
| - name: Smoke test HID with ofp1-tests | |
| run: cargo test --no-run -p flight-hid --features ofp1-tests | |
| - name: Clippy on HID tests | |
| run: cargo clippy -p flight-hid --tests -- -Dwarnings | |
| cross-platform: | |
| name: Cross-Platform Verification | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: ${{ matrix.os == 'windows-latest' && 45 || 30 }} | |
| if: github.event_name == 'schedule' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt, clippy | |
| - name: Cache dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: cross-platform-${{ matrix.os }} | |
| - name: Install Linux HID build deps | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libudev-dev | |
| - name: Check workspace | |
| run: cargo check --workspace | |
| - name: Clippy workspace (strict) | |
| run: cargo clippy --workspace --all-targets -- -Dwarnings | |
| - name: Check formatting | |
| run: cargo fmt --all -- --check | |
| security: | |
| name: Security Audit | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| continue-on-error: true # RUSTSEC-2023-0089 (atomic-polyfill) — no safe upgrade available | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Install cargo-audit | |
| run: cargo install cargo-audit | |
| - name: Install cargo-deny | |
| run: cargo install cargo-deny | |
| - name: Run security audit | |
| run: cargo audit --deny warnings | |
| - name: Run cargo-deny | |
| run: cargo deny check --hide-inclusion-graph | |
| - name: Validate HTTP stack unification | |
| run: | | |
| echo "🔍 Validating HTTP stack unification..." | |
| # Function to check if a package exists in the dependency tree | |
| check_banned_package() { | |
| local package=$1 | |
| echo "Checking for $package dependencies..." | |
| # Run cargo tree and capture both stdout and stderr | |
| output=$(cargo tree -i "$package" 2>&1 || true) | |
| # If output contains actual dependency info (not error message), fail | |
| if echo "$output" | grep -q "^$package v" || echo "$output" | grep -q "├──\|└──"; then | |
| echo "❌ Found $package dependencies:" | |
| echo "$output" | |
| return 1 | |
| else | |
| echo "✅ No $package dependencies found" | |
| return 0 | |
| fi | |
| } | |
| # Check banned packages | |
| check_banned_package "native-tls" | |
| check_banned_package "hyper-tls" | |
| check_banned_package "openssl" | |
| # Verify only hyper v1.x versions present | |
| echo "Checking hyper versions..." | |
| hyper_output=$(cargo tree -i hyper 2>&1 || true) | |
| if echo "$hyper_output" | grep -q "did not match any packages"; then | |
| echo "✅ No hyper dependencies found" | |
| elif echo "$hyper_output" | grep -q "^hyper v"; then | |
| hyper_versions=$(echo "$hyper_output" | grep "^hyper v" | awk '{print $2}' | sort -u) | |
| echo "Found hyper versions: $hyper_versions" | |
| # Check if any version is not v1.x | |
| if echo "$hyper_versions" | grep -v '^v1\.'; then | |
| echo "❌ Found non-v1.x hyper versions" | |
| exit 1 | |
| else | |
| echo "✅ All hyper versions are v1.x" | |
| fi | |
| else | |
| echo "❌ Unexpected hyper tree output:" | |
| echo "$hyper_output" | |
| exit 1 | |
| fi | |
| echo "✅ HTTP stack unification validation passed" | |
| build: | |
| name: Build Release | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: ${{ matrix.os == 'windows-latest' && 45 || 30 }} | |
| needs: validate | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: release-${{ matrix.os }} | |
| - name: Install Linux HID build deps | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libudev-dev | |
| - name: Build release | |
| run: cargo build --release --workspace --exclude flight-xplane-plugin | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: flight-hub-${{ matrix.os }} | |
| path: | | |
| target/release/flightd* | |
| target/release/flightctl* | |
| feature-powerset: | |
| name: Feature Powerset Testing | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| needs: validate | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Install Linux HID build deps | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libudev-dev | |
| - name: Install cargo-hack | |
| run: cargo install cargo-hack | |
| - name: Run feature powerset testing | |
| run: | | |
| echo "🔍 Running feature powerset testing with depth 2..." | |
| cargo hack check --workspace --exclude flight-hub-examples --feature-powerset --depth 2 | |
| - name: Verify workspace dependency alignment | |
| run: | | |
| echo "🔍 Verifying workspace dependency alignment..." | |
| # Check that all crates use workspace dependencies for common crates | |
| echo "Checking tokio dependencies..." | |
| if grep -r "tokio.*=" crates/*/Cargo.toml | grep -v "workspace = true" | grep -v "features\|optional"; then | |
| echo "❌ Found non-workspace tokio dependencies" | |
| exit 1 | |
| fi | |
| echo "Checking futures dependencies..." | |
| if grep -r "futures.*=" crates/*/Cargo.toml | grep -v "workspace = true" | grep -v "features\|optional"; then | |
| echo "❌ Found non-workspace futures dependencies" | |
| exit 1 | |
| fi | |
| echo "Checking tonic/tonic-build version alignment..." | |
| tonic_versions=$(grep -r "tonic.*=" Cargo.toml crates/*/Cargo.toml | grep version | awk -F'"' '{print $2}' | sort -u) | |
| if [ $(echo "$tonic_versions" | wc -l) -gt 1 ]; then | |
| echo "❌ Found misaligned tonic versions: $tonic_versions" | |
| exit 1 | |
| fi | |
| echo "✅ Workspace dependency alignment verified" | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| # Code Coverage (core logic crates; runs on PRs to main, schedule, and on-demand) | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| coverage: | |
| name: Code Coverage (Core Crates) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| needs: validate | |
| continue-on-error: true | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: llvm-tools-preview | |
| - name: Cache dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Install Linux build deps | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libudev-dev | |
| - name: Install cargo-llvm-cov | |
| uses: taiki-e/install-action@cargo-llvm-cov | |
| - name: Generate coverage (core logic crates only) | |
| run: | | |
| cargo llvm-cov \ | |
| -p flight-core \ | |
| -p flight-axis \ | |
| -p flight-bus \ | |
| -p flight-profile \ | |
| -p flight-rules \ | |
| -p flight-session \ | |
| --html \ | |
| --output-dir coverage-report | |
| - name: Generate LCOV report | |
| run: | | |
| cargo llvm-cov \ | |
| -p flight-core \ | |
| -p flight-axis \ | |
| -p flight-bus \ | |
| -p flight-profile \ | |
| -p flight-rules \ | |
| -p flight-session \ | |
| --lcov --output-path lcov.info | |
| - name: Report coverage summary | |
| run: | | |
| cargo llvm-cov \ | |
| -p flight-core \ | |
| -p flight-axis \ | |
| -p flight-bus \ | |
| -p flight-profile \ | |
| -p flight-rules \ | |
| -p flight-session \ | |
| --summary-only | |
| - name: Upload HTML coverage report | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-report | |
| path: coverage-report/ | |
| - name: Upload LCOV coverage report | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-lcov | |
| path: lcov.info | |
| performance: | |
| name: Performance Tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| needs: validate | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Install Linux HID build deps | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libudev-dev | |
| - name: Run benchmarks | |
| run: cargo bench --workspace --exclude flight-hub-examples --no-run | |
| - name: Performance regression check | |
| run: | | |
| # Verify key axis processing crates compile in release mode with bench harness | |
| # Full regression gating (criterion baselines) is tracked in QG-RT-JITTER and | |
| # QG-HID-LATENCY hardware gates (see ROADMAP.md). | |
| echo "Benchmark compilation check passed" | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| # PR Fuzz Smoke (runs on every PR to catch obvious panics quickly) | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| pr-fuzz-smoke: | |
| name: PR Fuzz Smoke (Device Parsers) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| needs: validate | |
| if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install nightly Rust toolchain | |
| uses: dtolnay/rust-toolchain@nightly | |
| - name: Cache dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Install Linux build deps | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libudev-dev | |
| - name: Install cargo-fuzz | |
| run: cargo install cargo-fuzz | |
| - name: Smoke fuzz flight-hotas-virpil parsers (5s) | |
| run: | | |
| cd crates/flight-hotas-virpil | |
| cargo +nightly fuzz run fuzz_virpil_parsers -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-hotas-thrustmaster parsers (5s) | |
| run: | | |
| cd crates/flight-hotas-thrustmaster | |
| cargo +nightly fuzz run fuzz_thrustmaster_parsers -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-hotas-honeycomb parsers (5s) | |
| run: | | |
| cd crates/flight-hotas-honeycomb | |
| cargo +nightly fuzz run fuzz_honeycomb_parsers -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-hotas-logitech parsers (5s) | |
| run: | | |
| cd crates/flight-hotas-logitech | |
| cargo +nightly fuzz run fuzz_logitech_parsers -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-hotas-winwing report (5s) | |
| run: | | |
| cd crates/flight-hotas-winwing | |
| cargo +nightly fuzz run fuzz_winwing_report -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-hotas-vkb Gladiator report (5s) | |
| run: | | |
| cd crates/flight-hotas-vkb | |
| cargo +nightly fuzz run fuzz_vkb_gladiator_report -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-hotas-saitek input report (5s) | |
| run: | | |
| cd crates/flight-hotas-saitek | |
| cargo +nightly fuzz run fuzz_saitek_input_report -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-hotas-brunner CLS-E report (5s) | |
| run: | | |
| cd crates/flight-hotas-brunner | |
| cargo +nightly fuzz run fuzz_brunner_cls_e -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-hotas-vpforce Rhino report (5s) | |
| run: | | |
| cd crates/flight-hotas-vpforce | |
| cargo +nightly fuzz run fuzz_vpforce_rhino -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-cloud-profiles profile parse (5s) | |
| run: | | |
| cd crates/flight-cloud-profiles | |
| cargo +nightly fuzz run fuzz_cloud_profile_parse -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-open-hardware input report (5s) | |
| run: | | |
| cd crates/flight-open-hardware | |
| cargo +nightly fuzz run fuzz_input_report -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-writers JSON patch (5s) | |
| run: | | |
| cd crates/flight-writers | |
| cargo +nightly fuzz run fuzz_json_patch -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-writers diff config parse (5s) | |
| run: | | |
| cd crates/flight-writers | |
| cargo +nightly fuzz run fuzz_diff_apply -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-xplane UDP DATA packet parser (5s) | |
| run: | | |
| cd crates/flight-xplane | |
| cargo +nightly fuzz run fuzz_udp -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-dcs-export message parser (5s) | |
| run: | | |
| cd crates/flight-dcs-export | |
| cargo +nightly fuzz run fuzz_dcs_message -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-dcs-export telemetry batch parser (5s) | |
| run: | | |
| cd crates/flight-dcs-export | |
| cargo +nightly fuzz run fuzz_telemetry_batch -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-rules DSL parser (5s) | |
| run: | | |
| cd crates/flight-rules | |
| cargo +nightly fuzz run fuzz_rules_parser -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-rules condition parser (5s) | |
| run: | | |
| cd crates/flight-rules | |
| cargo +nightly fuzz run fuzz_condition -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-rules action parser (5s) | |
| run: | | |
| cd crates/flight-rules | |
| cargo +nightly fuzz run fuzz_action -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-profile JSON parser (5s) | |
| run: | | |
| cd crates/flight-profile | |
| cargo +nightly fuzz run fuzz_profile_parse -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-profile merge path (5s) | |
| run: | | |
| cd crates/flight-profile | |
| cargo +nightly fuzz run fuzz_profile_merge -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-profile TOML parser (5s) | |
| run: | | |
| cd crates/flight-profile | |
| cargo +nightly fuzz run fuzz_profile_toml -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-axis config parsing (5s) | |
| run: | | |
| cd crates/flight-axis | |
| cargo +nightly fuzz run fuzz_axis_config -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-axis RT pipeline (5s) | |
| run: | | |
| cd crates/flight-axis | |
| cargo +nightly fuzz run fuzz_axis_pipeline -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-hid descriptor parsing (5s) | |
| run: | | |
| cd crates/flight-hid | |
| cargo +nightly fuzz run fuzz_hid_descriptor -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-core aircraft type parsing (5s) | |
| run: | | |
| cd crates/flight-core | |
| cargo +nightly fuzz run fuzz_aircraft_type -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-core profile deserialization (5s) | |
| run: | | |
| cd crates/flight-core | |
| cargo +nightly fuzz run fuzz_profile_deser -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| - name: Smoke fuzz flight-bus snapshot deserialization (5s) | |
| run: | | |
| cd crates/flight-bus | |
| cargo +nightly fuzz run fuzz_bus_snapshot -- -max_total_time=5 -timeout=5 | |
| continue-on-error: false | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| # Nightly Fuzz Gates (runs daily at 03:00 UTC, or on-demand) | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| nightly-fuzz: | |
| name: Nightly Fuzz Targets | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 120 | |
| if: | | |
| github.event_name == 'workflow_dispatch' || | |
| (github.event_name == 'schedule' && github.event.schedule == '0 3 * * *') | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install nightly Rust toolchain | |
| uses: dtolnay/rust-toolchain@nightly | |
| - name: Cache dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Install Linux build deps | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libudev-dev | |
| - name: Install cargo-fuzz | |
| run: cargo install cargo-fuzz | |
| - name: Fuzz flight-rules DSL parser (60s) | |
| run: | | |
| cd crates/flight-rules | |
| cargo +nightly fuzz run fuzz_rules_parser -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-profile JSON parser (60s) | |
| run: | | |
| cd crates/flight-profile | |
| cargo +nightly fuzz run fuzz_profile_parse -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-profile merge path (60s) | |
| run: | | |
| cd crates/flight-profile | |
| cargo +nightly fuzz run fuzz_profile_merge -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-dcs-export message parser (60s) | |
| run: | | |
| cd crates/flight-dcs-export | |
| cargo +nightly fuzz run fuzz_dcs_message -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-dcs-export telemetry batch parser (60s) | |
| run: | | |
| cd crates/flight-dcs-export | |
| cargo +nightly fuzz run fuzz_telemetry_batch -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-xplane plugin message parser (60s) | |
| run: | | |
| cd crates/flight-xplane | |
| cargo +nightly fuzz run fuzz_xplane_plugin_msg -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-xplane DataRef parser (60s) | |
| run: | | |
| cd crates/flight-xplane | |
| cargo +nightly fuzz run fuzz_xplane_dataref -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-xplane UDP DATA packet parser (60s) | |
| run: | | |
| cd crates/flight-xplane | |
| cargo +nightly fuzz run fuzz_udp -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-ffb-vpforce HID report parser (60s) | |
| run: | | |
| cd crates/flight-ffb-vpforce | |
| cargo +nightly fuzz run fuzz_rhino_report -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-ffb-moza HID report parser (60s) | |
| run: | | |
| cd crates/flight-ffb-moza | |
| cargo +nightly fuzz run fuzz_moza_ab9_report -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-ksp kRPC protobuf decoder (60s) | |
| run: | | |
| cd crates/flight-ksp | |
| cargo +nightly fuzz run fuzz_ksp_response -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-simconnect session fixture parser (60s) | |
| run: | | |
| cd crates/flight-simconnect | |
| cargo +nightly fuzz run fuzz_simconnect_fixture -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-warthunder telemetry decoder (60s) | |
| run: | | |
| cd crates/flight-warthunder | |
| cargo +nightly fuzz run fuzz_wt_telemetry -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-ipc packet parser (60s) | |
| run: | | |
| cd crates/flight-ipc | |
| cargo +nightly fuzz run fuzz_ipc_packet -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-ac7-protocol telemetry packet parser (60s) | |
| run: | | |
| cd crates/flight-ac7-protocol | |
| cargo +nightly fuzz run fuzz_telemetry_packet -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-ac7-protocol JSON string parser (60s) | |
| run: | | |
| cd crates/flight-ac7-protocol | |
| cargo +nightly fuzz run fuzz_json_str -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-ac7-input render block decoder (60s) | |
| run: | | |
| cd crates/flight-ac7-input | |
| cargo +nightly fuzz run fuzz_render_block -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-ac7-input profile applicator (60s) | |
| run: | | |
| cd crates/flight-ac7-input | |
| cargo +nightly fuzz run fuzz_apply_profile -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-elite journal event parser (60s) | |
| run: | | |
| cd crates/flight-elite | |
| cargo +nightly fuzz run fuzz_elite_journal -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-cloud-profiles JSON deserialization (60s) | |
| run: | | |
| cd crates/flight-cloud-profiles | |
| cargo +nightly fuzz run fuzz_cloud_profiles -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-vkb Gladiator report parser (60s) | |
| run: | | |
| cd crates/flight-hotas-vkb | |
| cargo +nightly fuzz run fuzz_vkb_gladiator_report -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-saitek input report parser (60s) | |
| run: | | |
| cd crates/flight-hotas-saitek | |
| cargo +nightly fuzz run fuzz_saitek_input_report -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-winwing report parsers (60s) | |
| run: | | |
| cd crates/flight-hotas-winwing | |
| cargo +nightly fuzz run fuzz_winwing_report -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-virpil HID report parsers (60s) | |
| run: | | |
| cd crates/flight-hotas-virpil | |
| cargo +nightly fuzz run fuzz_virpil_parsers -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-thrustmaster HID report parsers (60s) | |
| run: | | |
| cd crates/flight-hotas-thrustmaster | |
| cargo +nightly fuzz run fuzz_thrustmaster_parsers -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-honeycomb HID report parsers (60s) | |
| run: | | |
| cd crates/flight-hotas-honeycomb | |
| cargo +nightly fuzz run fuzz_honeycomb_parsers -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-logitech HID report parser (60s) | |
| run: | | |
| cd crates/flight-hotas-logitech | |
| cargo +nightly fuzz run fuzz_logitech_parsers -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-brunner CLS-E report parser (60s) | |
| run: | | |
| cd crates/flight-hotas-brunner | |
| cargo +nightly fuzz run fuzz_brunner_cls_e -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-vpforce Rhino report parser (60s) | |
| run: | | |
| cd crates/flight-hotas-vpforce | |
| cargo +nightly fuzz run fuzz_vpforce_rhino -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-cloud-profiles profile parse (60s) | |
| run: | | |
| cd crates/flight-cloud-profiles | |
| cargo +nightly fuzz run fuzz_cloud_profile_parse -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-open-hardware input report parser (60s) | |
| run: | | |
| cd crates/flight-open-hardware | |
| cargo +nightly fuzz run fuzz_input_report -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-writers JSON patch operations (60s) | |
| run: | | |
| cd crates/flight-writers | |
| cargo +nightly fuzz run fuzz_json_patch -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-writers diff config parsing (60s) | |
| run: | | |
| cd crates/flight-writers | |
| cargo +nightly fuzz run fuzz_diff_apply -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-axis config parsing (60s) | |
| run: | | |
| cd crates/flight-axis | |
| cargo +nightly fuzz run fuzz_axis_config -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-axis RT pipeline (60s) | |
| run: | | |
| cd crates/flight-axis | |
| cargo +nightly fuzz run fuzz_axis_pipeline -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hid descriptor parsing (60s) | |
| run: | | |
| cd crates/flight-hid | |
| cargo +nightly fuzz run fuzz_hid_descriptor -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-core aircraft type parsing (60s) | |
| run: | | |
| cd crates/flight-core | |
| cargo +nightly fuzz run fuzz_aircraft_type -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-core profile deserialization (60s) | |
| run: | | |
| cd crates/flight-core | |
| cargo +nightly fuzz run fuzz_profile_deser -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-bus snapshot deserialization (60s) | |
| run: | | |
| cd crates/flight-bus | |
| cargo +nightly fuzz run fuzz_bus_snapshot -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-aerofly packet parser (60s) | |
| run: | | |
| cd crates/flight-aerofly | |
| cargo +nightly fuzz run fuzz_aerofly_packet -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-axis calibration smooth trim (60s) | |
| run: | | |
| cd crates/flight-axis | |
| cargo +nightly fuzz run fuzz_cal_smooth_trim -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-falcon-bms flight data parser (60s) | |
| run: | | |
| cd crates/flight-falcon-bms | |
| cargo +nightly fuzz run fuzz_flight_data -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-ch FighterStick report (60s) | |
| run: | | |
| cd crates/flight-hotas-ch | |
| cargo +nightly fuzz run fuzz_fighterstick -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-ch Pro Throttle report (60s) | |
| run: | | |
| cd crates/flight-hotas-ch | |
| cargo +nightly fuzz run fuzz_pro_throttle -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-ch Pro Pedals report (60s) | |
| run: | | |
| cd crates/flight-hotas-ch | |
| cargo +nightly fuzz run fuzz_pro_pedals -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-logitech Extreme 3D Pro (60s) | |
| run: | | |
| cd crates/flight-hotas-logitech | |
| cargo +nightly fuzz run fuzz_extreme3dpro -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-logitech G Flight Yoke (60s) | |
| run: | | |
| cd crates/flight-hotas-logitech | |
| cargo +nightly fuzz run fuzz_g_flight_yoke -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-logitech G940 joystick (60s) | |
| run: | | |
| cd crates/flight-hotas-logitech | |
| cargo +nightly fuzz run fuzz_g940_joystick -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-logitech-wheel G29 report (60s) | |
| run: | | |
| cd crates/flight-hotas-logitech-wheel | |
| cargo +nightly fuzz run fuzz_g29 -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-logitech-wheel G27 report (60s) | |
| run: | | |
| cd crates/flight-hotas-logitech-wheel | |
| cargo +nightly fuzz run fuzz_g27 -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-sony report parser (60s) | |
| run: | | |
| cd crates/flight-hotas-sony | |
| cargo +nightly fuzz run fuzz_sony_report -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-thrustmaster Warthog stick (60s) | |
| run: | | |
| cd crates/flight-hotas-thrustmaster | |
| cargo +nightly fuzz run fuzz_warthog_stick -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-thrustmaster T.16000M (60s) | |
| run: | | |
| cd crates/flight-hotas-thrustmaster | |
| cargo +nightly fuzz run fuzz_t16000m -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-turtlebeach VelocityOne (60s) | |
| run: | | |
| cd crates/flight-hotas-turtlebeach | |
| cargo +nightly fuzz run fuzz_velocityone -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-vkb Gladiator NXT EVO (60s) | |
| run: | | |
| cd crates/flight-hotas-vkb | |
| cargo +nightly fuzz run fuzz_gladiator_nxt_evo -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-vkb STECS throttle (60s) | |
| run: | | |
| cd crates/flight-hotas-vkb | |
| cargo +nightly fuzz run fuzz_stecs -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-virpil Alpha grip (60s) | |
| run: | | |
| cd crates/flight-hotas-virpil | |
| cargo +nightly fuzz run fuzz_alpha -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-virpil MongoosT base (60s) | |
| run: | | |
| cd crates/flight-hotas-virpil | |
| cargo +nightly fuzz run fuzz_mongoost -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-winwing Orion2 stick (60s) | |
| run: | | |
| cd crates/flight-hotas-winwing | |
| cargo +nightly fuzz run fuzz_orion2_stick -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-winwing Orion2 throttle (60s) | |
| run: | | |
| cd crates/flight-hotas-winwing | |
| cargo +nightly fuzz run fuzz_orion2_throttle -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-winwing TFRP rudder (60s) | |
| run: | | |
| cd crates/flight-hotas-winwing | |
| cargo +nightly fuzz run fuzz_tfrp_rudder -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-winwing throttle (60s) | |
| run: | | |
| cd crates/flight-hotas-winwing | |
| cargo +nightly fuzz run fuzz_winwing_throttle -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-hotas-winwing joystick (60s) | |
| run: | | |
| cd crates/flight-hotas-winwing | |
| cargo +nightly fuzz run fuzz_winwing_joystick -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-il2 packet parser (60s) | |
| run: | | |
| cd crates/flight-il2 | |
| cargo +nightly fuzz run fuzz_il2_packet -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-panels-goflight report parser (60s) | |
| run: | | |
| cd crates/flight-panels-goflight | |
| cargo +nightly fuzz run fuzz_goflight_report -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-profile YAML parser (60s) | |
| run: | | |
| cd crates/flight-profile | |
| cargo +nightly fuzz run fuzz_profile_yaml -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-profile TOML parser (60s) | |
| run: | | |
| cd crates/flight-profile | |
| cargo +nightly fuzz run fuzz_profile_toml -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-rules condition evaluator (60s) | |
| run: | | |
| cd crates/flight-rules | |
| cargo +nightly fuzz run fuzz_condition -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-rules action executor (60s) | |
| run: | | |
| cd crates/flight-rules | |
| cargo +nightly fuzz run fuzz_action -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-rules schema validation (60s) | |
| run: | | |
| cd crates/flight-rules | |
| cargo +nightly fuzz run fuzz_rule_schema -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-sim-racing generic UDP parser (60s) | |
| run: | | |
| cd crates/flight-sim-racing | |
| cargo +nightly fuzz run fuzz_generic_udp -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-trackir packet parser (60s) | |
| run: | | |
| cd crates/flight-trackir | |
| cargo +nightly fuzz run fuzz_trackir_packet -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Fuzz flight-warthunder unit conversion (60s) | |
| run: | | |
| cd crates/flight-warthunder | |
| cargo +nightly fuzz run fuzz_wt_convert -- -max_total_time=60 -timeout=10 | |
| continue-on-error: true | |
| - name: Upload fuzz crash artifacts | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: fuzz-crashes | |
| path: crates/*/fuzz/artifacts/ | |
| if-no-files-found: ignore | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| # Weekly Mutation Testing Gate (runs Sundays at 06:00 UTC, or on-demand) | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| mutation-testing: | |
| name: Mutation Testing (Weekly) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 120 | |
| if: | | |
| github.event_name == 'workflow_dispatch' || | |
| (github.event_name == 'schedule' && github.event.schedule == '0 6 * * 0') | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: 1.92.0 | |
| - name: Cache dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Install Linux build deps | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libudev-dev | |
| - name: Install cargo-mutants | |
| run: cargo install cargo-mutants --locked | |
| - name: Run mutation testing on core crates | |
| run: | | |
| cargo mutants \ | |
| -p flight-core \ | |
| -p flight-axis \ | |
| -p flight-bus \ | |
| --timeout 120 \ | |
| --output mutants-out | |
| continue-on-error: true | |
| - name: Check mutation kill rate (≥50% survivors fail CI) | |
| run: | | |
| if [ -d mutants-out ]; then | |
| if [ -f mutants-out/outcomes.json ]; then | |
| # Use JSON output for accurate counts (one entry per mutation) | |
| missed=$(jq '[.[] | select(.summary == "MissedMutant")] | length' mutants-out/outcomes.json 2>/dev/null || echo 0) | |
| caught=$(jq '[.[] | select(.summary == "CaughtMutant")] | length' mutants-out/outcomes.json 2>/dev/null || echo 0) | |
| else | |
| # Fallback: line-count from text files (one mutation per line) | |
| missed=0; caught=0 | |
| [ -f mutants-out/missed.txt ] && missed=$(grep -c . mutants-out/missed.txt 2>/dev/null || echo 0) | |
| [ -f mutants-out/caught.txt ] && caught=$(grep -c . mutants-out/caught.txt 2>/dev/null || echo 0) | |
| fi | |
| total=$((missed + caught)) | |
| if [ "$total" -gt 0 ]; then | |
| kill_rate=$(( caught * 100 / total )) | |
| echo "Kill rate: ${kill_rate}% (${caught}/${total})" | |
| if [ "$kill_rate" -lt 50 ]; then | |
| echo "❌ Mutation kill rate ${kill_rate}% is below the 50% threshold" | |
| echo " This means tests are not catching enough logic mutations." | |
| echo " See mutants-out/ for details." | |
| exit 1 | |
| fi | |
| echo "✅ Mutation kill rate ${kill_rate}% passes threshold (≥50%)" | |
| else | |
| echo "⚠️ No mutations found to test" | |
| fi | |
| else | |
| echo "⚠️ mutants-out directory not found — mutation testing may have failed to run" | |
| fi | |
| - name: Upload mutation results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: mutation-results | |
| path: mutants-out/ | |
| - name: Write mutation summary to GitHub step summary | |
| if: always() | |
| run: | | |
| { | |
| echo "## Mutation Testing Results" | |
| if [ -f mutants-out/outcomes.json ]; then | |
| missed=$(jq '[.[] | select(.summary == "MissedMutant")] | length' mutants-out/outcomes.json 2>/dev/null || echo 0) | |
| caught=$(jq '[.[] | select(.summary == "CaughtMutant")] | length' mutants-out/outcomes.json 2>/dev/null || echo 0) | |
| total=$((missed + caught)) | |
| if [ "$total" -gt 0 ]; then | |
| kill_rate=$(( caught * 100 / total )) | |
| echo "- **Crates tested**: flight-core, flight-axis, flight-bus" | |
| echo "- **Kill rate**: ${kill_rate}% (${caught}/${total} mutants caught)" | |
| echo "- **Missed mutants**: ${missed}" | |
| else | |
| echo "- No mutations found to test" | |
| fi | |
| else | |
| echo "- outcomes.json not found — mutation run may have failed" | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" |