# Blackbox Test Harness (`e2e/`) This directory is the **public-boundary** test harness for `gps-denied-onboard`. It is owned by the `blackbox_tests` cross-cutting entry in `_docs/02_document/module-layout.md` and implements task **AZ-406** (Test Infrastructure Bootstrap) plus its downstream test-task siblings (AZ-407..AZ-446). The harness runs in two execution tiers (`environment.md` § Two-tier execution profile): - **Tier-1** — workstation Docker. `cd e2e/docker && docker compose -f docker-compose.test.yml up --build --abort-on-container-exit e2e-runner` - **Tier-2** — Jetson Orin Nano Super hardware loop. `./e2e/jetson/run-tier2.sh --fc-adapter --vio-strategy ` Both tiers emit the same CSV report format (one row per test) per `environment.md` § Reporting. ## Layout ``` e2e/ ├── docker/ Tier-1 entrypoint (docker-compose.test.yml + Tier-2 bridge override + secrets mount) ├── jetson/ Tier-2 entrypoint (run-tier2.sh + systemd unit + tegrastats/jtop parsers) ├── runner/ e2e-runner image (Dockerfile, conftest, pytest plugins, helpers, requirements) ├── fixtures/ Fixture builders (tile-cache, age-injector, injectors/, mock-suite-sat, secrets, security) ├── tests/ Pytest target — `positive/`, `negative/`, `performance/`, `resilience/`, `security/`, `resource_limit/` └── _unit_tests/ Out-of-container unit tests for the harness internals (run as part of the project test suite) ``` ## Public-Boundary Discipline (hard rule) The e2e-runner image **MUST NOT** import any module from the SUT source tree (`src/gps_denied_onboard/**`). The only legal interaction surfaces are: - MAVLink (ArduPilot SITL — UDP 14550) - MSP2 (iNav SITL — TCP 5760) - HTTP/JSON (mock-suite-sat-service — port 8080) - Filesystem read of the FDR archive after a run (`fdr-output` volume) This rule is enforced by: 1. The runner `Dockerfile` building from a base image that does NOT install the SUT package. 2. Layout discipline: no `import gps_denied_onboard.*` in any file under `e2e/`. 3. Compose `e2e-net.internal: true` — no external network egress (RESTRICT-SAT-1, NFT-SEC-02). See `_docs/02_document/tests/environment.md` for the full per-service spec. ## RUN_ID and report paths Each invocation must set `RUN_ID` (defaults to `local-${USER}-${EPOCH}` in development; CI sets it from the workflow run id). Reports land at: - `e2e-results/run-${RUN_ID}/report.csv` - `e2e-results/run-${RUN_ID}/evidence/` (per-run `.tlog`, FDR archives, screenshots, profiler traces, tegrastats CSV, jtop CSV) The `e2e-results/` directory is gitignored. ## How to add a new blackbox scenario 1. Decompose the scenario into a task spec under `_docs/02_tasks/todo/`. 2. Implement the test under the appropriate `e2e/tests//` folder. 3. The conftest's session-scoped `(fc_adapter, vio_strategy)` parameterization automatically applies — opt out with `@pytest.mark.parametrize` overrides. 4. Trace the scenario to the AC/RESTRICT IDs it exercises via the `traces_to` pytest marker — the CSV reporter emits this verbatim. ## How to add a new fixture builder Fixture builders live under `e2e/fixtures/` and may be standalone Python modules (for runtime injectors) or Dockerized helpers (for tile-cache / mock-suite-sat). Each builder must: - Be reproducible — given the same input, produce bit-identical output. - Document its output volume / path in `_docs/02_document/tests/test-data.md`. - Have a corresponding unit test under `e2e/_unit_tests/fixtures/`. ## Out-of-container unit tests The harness's internal Python — CSV reporter, helpers, parsers, mock app, conftest skip rules — is unit-tested under `e2e/_unit_tests/`. These tests do NOT require Docker, SITL, or any external service and run as part of the project's main pytest invocation (`testpaths` extension in `pyproject.toml`).