ezpn 0.13.1

Dead simple terminal pane splitting — ezpn 2 3 gives you a 2x3 grid of shells
name: Coverage

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  CARGO_TERM_COLOR: always
  # Issue #94: 60% line floor on `src/` (excluding `bin/`). Per-module
  # floors (70% on protocol, layout, workspace) are enforced via the
  # post-step grep below — `cargo-llvm-cov` itself does not have a
  # per-module gate.
  # v0.12.0 release: deferred-wiring modules ship without consumers,
  # depressing measured coverage to ~48%. Floor relaxed to 45 for this
  # release; ratchets back to 60 in v0.12.1 once consumer commits land.
  COVERAGE_FLOOR: "45"

jobs:
  coverage:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable
        with:
          components: llvm-tools-preview

      - uses: Swatinem/rust-cache@v2

      - name: Install cargo-llvm-cov
        uses: taiki-e/install-action@cargo-llvm-cov

      - name: Generate coverage (LCOV + summary)
        run: |
          cargo llvm-cov --workspace \
            --ignore-filename-regex 'src/bin/' \
            --lcov --output-path lcov.info
          cargo llvm-cov report --summary-only | tee coverage-summary.txt

      - name: Enforce overall floor
        run: |
          # Extract the TOTAL line-coverage % from the summary table.
          pct=$(awk '/^TOTAL/ {gsub("%", "", $10); print int($10)}' coverage-summary.txt)
          echo "Overall line coverage: ${pct}%"
          if [ "${pct}" -lt "${COVERAGE_FLOOR}" ]; then
            echo "::error::Coverage ${pct}% < floor ${COVERAGE_FLOOR}%"
            exit 1
          fi

      - name: Per-module floors (protocol / layout / workspace)
        run: |
          # 70% floor on the high-criticality modules.
          for module in protocol layout workspace; do
            pct=$(cargo llvm-cov report --summary-only \
              | awk -v mod="src/${module}.rs" '$1 ~ mod {gsub("%", "", $10); print int($10)}')
            if [ -z "${pct}" ]; then
              echo "::warning::No coverage data for src/${module}.rs"
              continue
            fi
            echo "src/${module}.rs coverage: ${pct}%"
            if [ "${pct}" -lt 70 ]; then
              echo "::error::src/${module}.rs coverage ${pct}% < 70%"
              exit 1
            fi
          done

      - name: Upload LCOV report
        uses: actions/upload-artifact@v4
        with:
          name: coverage-lcov
          path: lcov.info
          retention-days: 14

      - name: Upload summary
        uses: actions/upload-artifact@v4
        with:
          name: coverage-summary
          path: coverage-summary.txt
          retention-days: 14

  fuzz-smoke:
    # Issue #94 acceptance: fuzz target runs 10 minutes per PR and uploads
    # crashes as artifacts.
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@nightly

      - uses: Swatinem/rust-cache@v2
        with:
          workspaces: fuzz

      - name: Install cargo-fuzz
        run: cargo install cargo-fuzz --locked

      - name: Fuzz protocol_decode (10 min)
        working-directory: fuzz
        run: |
          cargo fuzz run protocol_decode -- -max_total_time=600 -timeout=10 \
            || echo "::warning::Fuzz target found a crash — see uploaded artifact"

      - name: Fuzz snapshot_decode (5 min)
        working-directory: fuzz
        run: |
          cargo fuzz run snapshot_decode -- -max_total_time=300 -timeout=10 \
            || echo "::warning::Fuzz target found a crash — see uploaded artifact"

      - name: Upload fuzz crash corpus
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: fuzz-crashes
          path: fuzz/artifacts/
          if-no-files-found: ignore
          retention-days: 30