name: traffic # nigig-traffic CI: the MTB driving-theory game (sim + 3D view). # # Gates, in failure order cost: # 1. source-scanning greps (no toolchain): no unwrap/expect in src, # no lingering TODO/FIXME markers. # 2. compilation of lib AND bin (--all-targets: a binary that never # compiled once is how this repo got burned before, see email.yml). # 3. the lib unit suite plus a floor so it cannot silently shrink. # 4. the headless integration suite (ui_basic). # 5. formatting (hard gate, zero baseline). # 6. clippy ratchet, traffic-owned diagnostics only, baseline 0. on: push: paths: - 'crates/apps/nigig-traffic/**' - 'Cargo.lock' - 'Cargo.toml' - 'rust-toolchain.toml' - '.forgejo/workflows/traffic.yml' pull_request: paths: - 'crates/apps/nigig-traffic/**' - 'Cargo.lock' - 'Cargo.toml' - 'rust-toolchain.toml' - '.forgejo/workflows/traffic.yml' jobs: gates: runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 # Frame paths must never panic: no unwrap/expect anywhere in src. # Parse-back tests legitimately need fallible access, so if such a # test is ever added, this gate forces it to use explicit control # flow (match / let-else) instead of weakening the rule. - name: No unwrap or expect in traffic src run: | set -euo pipefail hits=$(grep -rnE '\.(unwrap|expect)\(' \ crates/apps/nigig-traffic/src || true) if [ -n "$hits" ]; then echo "$hits" echo echo "ERROR: unwrap()/expect() in frame-path code. A failed" echo "lookup in the render or tick path panics the app. Use" echo "Option combinators or explicit control flow instead." exit 1 fi echo "OK" # No unsafe in the game crate: nothing here justifies it. - name: No unsafe blocks in traffic src run: | set -euo pipefail hits=$(grep -rnE '(^|[^a-zA-Z_:])unsafe[[:space:]]*(\{|!)' \ crates/apps/nigig-traffic/src || true) if [ -n "$hits" ]; then echo "$hits" echo echo "ERROR: unsafe in nigig-traffic. Justify it in review or remove it." exit 1 fi echo "OK" # No TODO/FIXME debt markers: file an issue or do the work. - name: No TODO or FIXME markers in traffic src run: | set -euo pipefail hits=$(grep -rnE 'TODO|FIXME|todo!|unimplemented!' \ crates/apps/nigig-traffic/src || true) if [ -n "$hits" ]; then echo "$hits" echo echo "ERROR: debt markers above. Track the work in an issue" echo "instead of in comments nobody greps." exit 1 fi echo "OK" nigig-traffic: runs-on: ubuntu-latest timeout-minutes: 60 steps: - uses: actions/checkout@v4 - name: Install native dependencies run: | sudo apt-get update -qq sudo apt-get install -y -qq \ pkg-config libwayland-dev libxcursor-dev libxrandr-dev \ libxi-dev libx11-dev libgl1-mesa-dev libasound2-dev \ libglib2.0-dev libssl-dev libsqlite3-dev libudev-dev \ libpulse-dev libxkbcommon-dev - name: Install the declared toolchain run: | set -e version="$(sed -n 's/^[[:space:]]*channel[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \ rust-toolchain.toml | head -n 1)" curl --fail --location --proto '=https' --tlsv1.2 https://sh.rustup.rs -o /tmp/rustup-init chmod 700 /tmp/rustup-init /tmp/rustup-init -y --profile minimal --default-toolchain "$version" \ --component rustfmt --component clippy --no-modify-path echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" # --all-targets on purpose: the lib can pass while main.rs rots. - name: Check (lib AND bin AND tests) run: cargo check --locked -p nigig-traffic --all-targets - name: Unit tests run: cargo test --locked -p nigig-traffic --lib -- --test-threads=1 # A floor, not a ratchet: cheap, pure, and the number should only # grow. 35 at the Phase 0–7 completion; the floor sits at 30 so # ordinary test renames don't trip it while real loss does. - name: The unit suite must not shrink run: | set -euo pipefail FLOOR=30 out="$(cargo test --locked -p nigig-traffic --lib -- --test-threads=1 2>&1)" echo "$out" | tail -3 n="$(echo "$out" | grep -E '^test result: ok\.' | head -1 \ | sed -E 's/.* ([0-9]+) passed.*/\1/')" if [ -z "$n" ] || [ "$n" -lt "$FLOOR" ]; then echo "ERROR: $n tests passed, floor is $FLOOR." exit 1 fi echo "OK ($n >= $FLOOR)" - name: Headless integration tests run: cargo test --locked -p nigig-traffic --test ui_basic -- --test-threads=1 - name: Formatting run: | cargo fmt -p nigig-traffic -- --check \ || { echo "run: cargo fmt -p nigig-traffic"; exit 1; } # Ratchet at the measured baseline, which is ZERO. # Traffic-owned diagnostics only (deduped on rendered text, as # --all-targets compiles lib and lib-test and duplicates each). - name: Clippy ratchet (nigig-traffic-owned diagnostics only) run: | set -euo pipefail BASELINE=0 cargo clippy --locked -p nigig-traffic --all-targets \ --message-format=json > /tmp/clippy-traffic.json 2>/tmp/clippy-traffic.err || true cat /tmp/clippy-traffic.err || true BASELINE="$BASELINE" python3 - <<'PY' import json, os, sys baseline = int(os.environ['BASELINE']) owned, seen = [], set() with open('/tmp/clippy-traffic.json') as fh: for line in fh: try: m = json.loads(line) except ValueError: continue if m.get('reason') != 'compiler-message': continue if 'nigig-traffic' not in m.get('package_id', ''): continue msg = m['message'] if msg.get('level') not in ('warning', 'error'): continue key = msg.get('rendered', '') if key in seen: continue seen.add(key) owned.append(msg) n = len(owned) print("found %d nigig-traffic diagnostics, baseline %d" % (n, baseline)) if n > baseline: for msg in owned: sys.stdout.write(msg.get('rendered', '')) print() print("ERROR: %d diagnostics, up from %d." % (n, baseline)) sys.exit(1) if n < baseline: print() print("Good: down to %d. Lower BASELINE in this file to %d " "so the progress cannot be undone." % (n, n)) sys.exit(1) print("OK") PY supply-chain: runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v4 - name: Lockfile must be committed and current run: | set -euo pipefail test -f Cargo.lock || { echo "ERROR: Cargo.lock is not committed."; exit 1; } git diff --exit-code -- Cargo.lock - name: Reject whitespace errors run: git diff --check "$(git rev-list --max-parents=0 HEAD | tail -1)"..HEAD || git diff --check