name: CI on: push: branches: ["**"] pull_request: workflow_dispatch: inputs: skip_integration: description: "Skip integration tests" type: boolean default: false permissions: checks: write contents: read env: CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 SOURCE_DATE_EPOCH: 0 # overridden per-step after checkout # ───────────────────────────────────────────────────────────────────────────── # Job 1 – Build matrix # # Builds on Linux x86_64, Linux aarch64, and macOS. # ───────────────────────────────────────────────────────────────────────────── jobs: fmt: name: Format check runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - run: cargo fmt --check build: name: Build (${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: - os: ubuntu-latest - os: ubuntu-24.04-arm - os: macos-latest - os: windows-latest steps: - uses: actions/checkout@v4 - name: Set SOURCE_DATE_EPOCH from git (Unix) if: runner.os != 'Windows' run: echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> "$GITHUB_ENV" - name: Set SOURCE_DATE_EPOCH from git (Windows) if: runner.os == 'Windows' shell: pwsh run: | $epoch = git log -1 --format=%ct echo "SOURCE_DATE_EPOCH=$epoch" >> $env:GITHUB_ENV - name: Install system dependencies (Linux only) if: runner.os == 'Linux' run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Cache Cargo registry + build uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git target key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} restore-keys: | ${{ runner.os }}-cargo- - name: Build run: cargo build --release ${{ (runner.os == 'macOS' || runner.os == 'Windows') && '--no-default-features --features tui' || '--features gateway' }} - name: SHA-256 hashes (Linux) if: runner.os == 'Linux' run: sha256sum target/release/fips target/release/fipsctl target/release/fipstop target/release/fips-gateway - name: SHA-256 hashes (macOS) if: runner.os == 'macOS' run: shasum -a 256 target/release/fips target/release/fipsctl target/release/fipstop - name: SHA-256 hashes (Windows) if: runner.os == 'Windows' shell: pwsh run: Get-FileHash target\release\fips.exe, target\release\fipsctl.exe, target\release\fipstop.exe -Algorithm SHA256 # Upload the Linux binary so integration jobs can use it without rebuilding - name: Upload Linux binary if: matrix.os == 'ubuntu-latest' uses: actions/upload-artifact@v4 with: name: fips-linux path: | target/release/fips target/release/fipsctl target/release/fipstop target/release/fips-gateway retention-days: 1 # ───────────────────────────────────────────────────────────────────────────── # Job 2 – Unit tests # # Runs `cargo test` on Linux. Gated on the build matrix completing so we # don't waste runner time if compilation is broken. # ───────────────────────────────────────────────────────────────────────────── test: name: Unit tests runs-on: ubuntu-latest needs: [build] steps: - uses: actions/checkout@v4 - name: Set SOURCE_DATE_EPOCH from git run: echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> "$GITHUB_ENV" - name: Install system dependencies run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Cache Cargo registry + build uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git target key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} restore-keys: | ${{ runner.os }}-cargo- - name: Install cargo-nextest uses: taiki-e/install-action@nextest - name: Run unit tests run: cargo nextest run --all --profile ci --features gateway - name: Publish test report (Checks tab) uses: dorny/test-reporter@v2 if: always() with: name: Unit Tests path: target/nextest/ci/junit.xml reporter: java-junit fail-on-error: false - name: Publish test report (run summary) uses: mikepenz/action-junit-report@v4 if: always() with: report_paths: target/nextest/ci/junit.xml check_name: Unit Tests Summary fail_on_failure: false # ───────────────────────────────────────────────────────────────────────────── # Job 2b – Unit tests (macOS) # ───────────────────────────────────────────────────────────────────────────── test-macos: name: Unit tests (macOS) runs-on: macos-latest needs: [build] steps: - uses: actions/checkout@v4 - name: Set SOURCE_DATE_EPOCH from git run: echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> "$GITHUB_ENV" - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Cache Cargo registry + build uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git target key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} restore-keys: | ${{ runner.os }}-cargo- - name: Install cargo-nextest uses: taiki-e/install-action@nextest - name: Run unit tests run: cargo nextest run --all --profile ci --no-default-features --features tui # ───────────────────────────────────────────────────────────────────────────── # Job 2c – Unit tests (Windows) # ───────────────────────────────────────────────────────────────────────────── test-windows: name: Unit tests (Windows) runs-on: windows-latest steps: - uses: actions/checkout@v4 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Cache Cargo registry + build uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git target key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} restore-keys: | ${{ runner.os }}-cargo- - name: Install cargo-nextest uses: taiki-e/install-action@nextest - name: Run unit tests run: cargo nextest run --all --profile ci --no-default-features --features tui # ───────────────────────────────────────────────────────────────────────────── # Job 3 – Integration tests (static mesh + chaos simulation) # # Runs only when both build and test succeed. Each topology / scenario is a # separate matrix entry so they run in parallel. # # All harnesses share a single Docker image (fips-test:latest) built once # in the setup step from testing/docker/. # ───────────────────────────────────────────────────────────────────────────── integration: name: Integration (${{ matrix.suite }}) runs-on: ubuntu-latest needs: [build, test] if: ${{ !inputs.skip_integration }} strategy: fail-fast: false matrix: include: # ── Static mesh topologies ───────────────────────────────────────── - suite: static-mesh type: static topology: mesh - suite: static-chain type: static topology: chain # ── Rekey integration test ────────────────────────────────────────── - suite: rekey type: rekey topology: rekey # ── Chaos / stochastic scenarios ─────────────────────────────────── - suite: chaos-smoke-10 type: chaos scenario: smoke-10 - suite: churn-mixed-10 type: chaos scenario: churn-mixed chaos_flags: "--nodes 10 --duration 120" - suite: ethernet-mesh type: chaos scenario: ethernet-mesh - suite: ethernet-only type: chaos scenario: ethernet-only - suite: tcp-mesh type: chaos scenario: tcp-mesh - suite: bottleneck-parent type: chaos scenario: bottleneck-parent - suite: cost-avoidance type: chaos scenario: cost-avoidance - suite: cost-reeval type: chaos scenario: cost-reeval - suite: cost-stability type: chaos scenario: cost-stability - suite: depth-vs-cost type: chaos scenario: depth-vs-cost - suite: mixed-technology type: chaos scenario: mixed-technology - suite: congestion-stress type: chaos scenario: congestion-stress # ── Sidecar deployment ────────────────────────────────────────── - suite: sidecar type: sidecar steps: - uses: actions/checkout@v4 # Fetch the pre-built Linux binary from job 1 - name: Download Linux binary uses: actions/download-artifact@v4 with: name: fips-linux path: _bin # Install binaries to unified docker context and build shared image - name: Install binaries and build Docker image run: | chmod +x _bin/fips _bin/fipsctl [ -f _bin/fipstop ] && chmod +x _bin/fipstop || true [ -f _bin/fips-gateway ] && chmod +x _bin/fips-gateway || true cp _bin/fips testing/docker/fips cp _bin/fipsctl testing/docker/fipsctl [ -f _bin/fipstop ] && cp _bin/fipstop testing/docker/fipstop || true [ -f _bin/fips-gateway ] && cp _bin/fips-gateway testing/docker/fips-gateway || true docker build -t fips-test:latest testing/docker docker build -t fips-test-app:latest -f testing/docker/Dockerfile.app testing/docker # ── Static topology ──────────────────────────────────────────────────── - name: Generate configs (static) if: matrix.type == 'static' run: bash testing/static/scripts/generate-configs.sh ${{ matrix.topology }} - name: Start containers (static) if: matrix.type == 'static' run: | docker compose -f testing/static/docker-compose.yml \ --profile ${{ matrix.topology }} up -d - name: Run ping test (static) if: matrix.type == 'static' run: bash testing/static/scripts/ping-test.sh ${{ matrix.topology }} - name: Collect logs on failure (static) if: matrix.type == 'static' && failure() run: | docker compose -f testing/static/docker-compose.yml \ --profile ${{ matrix.topology }} logs --no-color - name: Stop containers (static) if: matrix.type == 'static' && always() run: | docker compose -f testing/static/docker-compose.yml \ --profile ${{ matrix.topology }} down --volumes --remove-orphans # ── Rekey integration test ────────────────────────────────────────────── - name: Generate and inject configs (rekey) if: matrix.type == 'rekey' run: | bash testing/static/scripts/generate-configs.sh rekey bash testing/static/scripts/rekey-test.sh inject-config - name: Start containers (rekey) if: matrix.type == 'rekey' run: | docker compose -f testing/static/docker-compose.yml \ --profile rekey up -d - name: Run rekey test if: matrix.type == 'rekey' run: bash testing/static/scripts/rekey-test.sh - name: Collect logs on failure (rekey) if: matrix.type == 'rekey' && failure() run: | docker compose -f testing/static/docker-compose.yml \ --profile rekey logs --no-color - name: Stop containers (rekey) if: matrix.type == 'rekey' && always() run: | docker compose -f testing/static/docker-compose.yml \ --profile rekey down --volumes --remove-orphans # ── Chaos simulation ─────────────────────────────────────────────────── - name: Install Python deps (chaos) if: matrix.type == 'chaos' run: pip3 install --quiet pyyaml jinja2 - name: Run chaos scenario if: matrix.type == 'chaos' run: bash testing/chaos/scripts/chaos.sh ${{ matrix.scenario }} ${{ matrix.chaos_flags }} - name: Upload sim results on failure (chaos) if: matrix.type == 'chaos' && failure() uses: actions/upload-artifact@v4 with: name: sim-results-${{ matrix.scenario }} path: testing/chaos/sim-results/ retention-days: 7 # ── Sidecar deployment ────────────────────────────────────────────── - name: Run sidecar integration test if: matrix.type == 'sidecar' run: bash testing/sidecar/scripts/test-sidecar.sh --skip-build - name: Collect logs on failure (sidecar) if: matrix.type == 'sidecar' && failure() run: | for node in a b c; do echo "--- sidecar-${node} logs ---" docker logs "sidecar-${node}-fips-1" 2>&1 || true echo "" done