Skip to content

CI

CI #1406

Workflow file for this run

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"