# The `test` command — hardware self-test suite

`test` runs modular hardware self-tests on the real gimbal, writes a plain-text
report to disk, and compares each run against a pinned baseline and the previous
run so both outright failures **and** slow drift are visible. It replaces the old
firmware-only `gimbal diag` (the firmware motor self-test is now one leaf inside
`test gimbal encoder`).

Implementation: [`TestRunner`](../src/core/TestRunner.cpp) (worker thread, mirrors
`CalibrationRoutine`) + the pure [`TestReport`](../src/core/TestReport.cpp) model
(`formatTestReport`/`parseTestReport`/`applyComparison`) and
[`HostMetrics`](../src/core/HostMetrics.cpp) host reads.

## Usage

```
test [<subsystem> [<leaf>]] [<profile>]
test list            # list configured profiles
test baseline        # pin the most recent report from this session as the baseline
```

The command is a three-level hierarchy; omitting a level widens scope. A trailing
token that names a configured profile selects the profile at any position.

| Command | Runs |
|---------|------|
| `test` | everything |
| `test gimbal` | all gimbal leaves |
| `test gimbal homing` | just homing |
| `test imu drift` | just IMU yaw drift |
| `test host` | host thermal/disk/memory/load (no hardware needed) |
| `test gimbal encoder strict` | one leaf, `strict` profile |
| `test strict` | everything, `strict` profile |

While a test runs, interfering commands (moves, homing, a second test, capture,
`refresh`) are ignored; **Esc** (or `stop`) cancels. Pure-UI keys (`p`, `i`, `c`,
`g`, `?`) keep working.

## Subsystems and leaves

| Subsystem | Leaf | Checks |
|-----------|------|--------|
| `gimbal` | `homing` | endstop-count + travel + homing-time reproducibility |
| `gimbal` | `encoder` | firmware DIAG tracking quality (repeated) + hold-corrector drift |
| `gimbal` | `friction` | full-range traverse time + motor load per direction |
| `gimbal` | `backlash` | reversal dead-band (encoder error) at small steps |
| `gimbal` | `balance` | pitch up-vs-down per-step peak current (center-of-mass symmetry) |
| `imu` | `config` | device identity, output mode, sample rate, active XKF profile |
| `imu` | `health` | rate, dropped samples, angle noise, accel-norm, temperature |
| `imu` | `drift` | yaw drift rate (deg/min) over a long static window |
| `host` | `thermal` | hottest CPU thermal zone (`/sys/class/thermal`) |
| `host` | `disk` | free space on the image partition (`statvfs`) |
| `host` | `memory` | available RAM / swap (`/proc/meminfo`) |
| `host` | `load` | load average vs CPU count |

Gimbal leaves require a homed gimbal (`home` first); IMU leaves require the IMU
(`[Features] enable_imu`). `host` needs neither, so `test host` doubles as a fast
non-intrusive smoke check.

> Some metrics are most accurate with firmware support (a homing-report `HM` line
> and a load-capture `LP` primitive); until those land the modules fall back to
> sampling the `ST` telemetry stream and note it in the report. See
> [test-roadmap.md](test-roadmap.md).

## Profiles

A profile is a named bundle of repetition counts, sampling windows and pass
thresholds, configured in `config.ini` under `[Test]` and `[TestProfile.<name>]`
(see [configuration.md](configuration.md) and the example config). The default is
`standard` (5 reps). Unknown keys are ignored, so new tests add knobs without code
changes. Ship profiles like `quick` (fewer reps) and `strict` (more reps, tighter
drift gate).

## Pass / fail and comparison

Each metric can carry an **absolute threshold** from the profile. Independently,
every metric is compared to the **pinned baseline** and the **previous run**; a
move worse than `drift_warn_pct` is flagged, and if `drift_gates_pass = true` a
flagged metric also fails. A section fails if any of its metrics fail; the run
fails if any section fails.

## Reports

Each run writes `logs/test_YYYYMMDD-HHMMSS.log` (under the per-user log dir). The
format is human-readable and re-parseable:

```
[host_disk]  PASS  0 ms  # image partition free space
  free_gb = 3.65 GB  PASS  base=3.66  prev=3.66  drift=-0.001%
  used_pct = 2.65 %  PASS
```

`test baseline` copies the most recent report to the baseline file
(`[Test] baseline_file`, default `<logdir>/test_baseline.txt`); subsequent runs
show `base=` columns against it.
