test(dev): emulation + linting harness (Wokwi, native tests, CI, docs)
Answers "how do we emulate/lint this?" with runnable tooling for every part, and factors the pure firmware logic out so it is unit-testable on the host. What changed: - firmware/src/wav.h, firmware/src/httprange.h: pure, header-only WAV header builder and HTTP Range parser (no Arduino deps), so the tricky byte-layout and range logic can be tested on a PC. storage.cpp and api_http.cpp refactored to use them. - firmware/test/test_pure/: Unity tests for the WAV header fields and Range parsing (start-end, open-ended, clamped, unsatisfiable, non-bytes). Run: pio test -e native. - firmware/platformio.ini: add [env:native] (host tests) and [env:esp32s3_wokwi] (emulator build with default WiFi = Wokwi-GUEST so the API comes up in the sim). - firmware/wokwi.toml + firmware/diagram.json: Wokwi emulator harness (ESP32-S3 + microSD + button + LED). Note: Wokwi has no I2S mic part, so audio isn't emulated; the harness targets boot + WiFi + REST API. - firmware/src/main.cpp + config.cpp: bring up WiFi + API even if audio/SD init fails (device stays reachable, reports the fault via GET /device); compile-time default WiFi honoured when NVS is empty (used by the Wokwi build). - .forgejo/workflows/ci.yml: add native tests + cppcheck to the firmware job; new openapi job (openapi-spec-validator) and emulator job (Wokwi build, plus a full run when WOKWI_CLI_TOKEN is set). - docs/testing.md: the full emulate/lint guide for firmware, server, API, app, case. - state/: NOTES points at the guide; TODO reflects this branch. Why: - The firmware can't be flashed yet (no parts) and doesn't build on this dev host, so we need host-runnable checks. Pure-logic unit tests + OpenAPI validation run anywhere; Wokwi emulates boot/WiFi/API; CI compiles the real firmware. Verified locally: the OpenAPI spec validates (12 paths, 10 schemas). Notes: - Native tests and cppcheck run in CI (no compiler on the dev host). The Wokwi full run is skipped unless a WOKWI_CLI_TOKEN secret is present; the build is still verified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
72633f02c3
commit
f84bbca80b
14 changed files with 445 additions and 56 deletions
132
docs/testing.md
Normal file
132
docs/testing.md
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
<!-- SPDX-License-Identifier: CC-BY-SA-4.0 -->
|
||||
# Emulating and linting OpenScribe
|
||||
|
||||
How to check each part of the project without (and with) the physical device. Everything
|
||||
here also runs in CI (`.forgejo/workflows/ci.yml`).
|
||||
|
||||
## TL;DR
|
||||
|
||||
| Part | Lint / static check | Compile / build | Emulate / run |
|
||||
|------|--------------------|------------------|----------------|
|
||||
| Firmware | `pio check` (cppcheck) | `pio run -e esp32s3_dev` | Wokwi (`pio run -e esp32s3_wokwi` + wokwi-cli) |
|
||||
| Firmware logic | - | `pio test -e native` | native unit tests (host) |
|
||||
| Server | `ruff check app` | - | `uvicorn app.main:app` + hit `/docs` |
|
||||
| API spec | `openapi-spec-validator` | - | Swagger UI at `/docs` |
|
||||
| App | `flutter analyze` | `flutter build` | `flutter run` / emulator |
|
||||
| Case | - | `openscad -o out.stl ...` | OpenSCAD preview (F5) |
|
||||
|
||||
## Firmware (ESP32-S3)
|
||||
|
||||
You do not need the board to catch most problems.
|
||||
|
||||
### 1. Compile-check (the strongest lint for C++)
|
||||
|
||||
Install PlatformIO once (`pip install platformio`), then from `firmware/`:
|
||||
|
||||
```bash
|
||||
pio run -e esp32s3_dev # dev board profile
|
||||
pio run -e xiao_esp32s3 # compact profile
|
||||
```
|
||||
|
||||
A clean build type-checks every file and every library call. This is what CI does.
|
||||
|
||||
### 2. Static analysis
|
||||
|
||||
```bash
|
||||
pio check -e esp32s3_dev --fail-on-defect high --skip-packages
|
||||
```
|
||||
|
||||
Runs cppcheck across the sources. Add clang-tidy locally with `--tool clangtidy` if
|
||||
installed.
|
||||
|
||||
### 3. Native unit tests (no hardware, fast)
|
||||
|
||||
The hardware-independent logic is factored into header-only files (`src/wav.h`,
|
||||
`src/httprange.h`) so it compiles and runs on your PC:
|
||||
|
||||
```bash
|
||||
pio test -e native
|
||||
```
|
||||
|
||||
This builds and runs `test/test_pure/` against desktop GCC and checks the WAV header
|
||||
byte layout and the HTTP Range parser. No board, no toolchain download for Xtensa.
|
||||
|
||||
### 4. Emulate the running firmware with Wokwi
|
||||
|
||||
[Wokwi](https://wokwi.com) simulates the ESP32-S3 including virtual WiFi, so you can boot
|
||||
the firmware, watch the serial log, and reach the REST API without hardware.
|
||||
|
||||
Build the emulator profile (its WiFi defaults to the open `Wokwi-GUEST` network so the API
|
||||
comes up):
|
||||
|
||||
```bash
|
||||
pio run -e esp32s3_wokwi
|
||||
```
|
||||
|
||||
Then either:
|
||||
|
||||
- **VS Code:** install the "Wokwi Simulator" extension, open `firmware/`, press play. It
|
||||
reads `wokwi.toml` + `diagram.json`.
|
||||
- **CLI / CI:** with a free `WOKWI_CLI_TOKEN`,
|
||||
`wokwi-cli --timeout 15000 --expect-text "API at http" firmware`.
|
||||
|
||||
Wokwi limitations to know: there is no I2S MEMS microphone part, so audio capture is not
|
||||
emulated (the mic reads nothing); microSD support is basic. The harness is for boot, WiFi,
|
||||
and the REST API surface, which is exactly what the emulator profile targets. Because the
|
||||
firmware brings the API up even when audio/SD init fails, `GET /api/v1/device` is reachable
|
||||
in the sim and reports the fault.
|
||||
|
||||
## Server (FastAPI)
|
||||
|
||||
From `server/`:
|
||||
|
||||
```bash
|
||||
python -m venv .venv && . .venv/Scripts/activate # or .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
ruff check app # lint
|
||||
uvicorn app.main:app --reload # run; browse http://localhost:8000/docs
|
||||
pytest # tests (added as endpoints gain logic in M5+)
|
||||
```
|
||||
|
||||
`GET /health` and the stubbed recording routes are live now; the AI pipeline arrives in
|
||||
M5/M6.
|
||||
|
||||
## API spec (OpenAPI)
|
||||
|
||||
`api/openapi.yaml` is the contract both the device and server implement. Validate it:
|
||||
|
||||
```bash
|
||||
pip install openapi-spec-validator pyyaml
|
||||
python -c "from openapi_spec_validator import validate; import yaml; \
|
||||
validate(yaml.safe_load(open('api/openapi.yaml'))); print('OpenAPI valid')"
|
||||
```
|
||||
|
||||
Browse it interactively by running the server (`/docs`) or pasting the file into
|
||||
https://editor.swagger.io.
|
||||
|
||||
## App (Flutter, from M7)
|
||||
|
||||
Once the app project exists:
|
||||
|
||||
```bash
|
||||
cd app
|
||||
flutter pub get
|
||||
flutter analyze # lint + type-check
|
||||
flutter test # widget/unit tests
|
||||
flutter run # on an Android/iOS emulator or device
|
||||
```
|
||||
|
||||
## Case (OpenSCAD)
|
||||
|
||||
```bash
|
||||
openscad -o case_top.stl -D 'part="top"' case/openscribe_case.scad
|
||||
```
|
||||
|
||||
`openscad --check-parameters` and a headless render catch syntax/geometry errors; the GUI
|
||||
preview (F5) is the visual check.
|
||||
|
||||
## What CI runs
|
||||
|
||||
`.forgejo/workflows/ci.yml` on the self-hosted runner: firmware build (both profiles) +
|
||||
native tests + cppcheck; OpenAPI validation; a Wokwi build (and a full emulator run if
|
||||
`WOKWI_CLI_TOKEN` is set); server lint + import check.
|
||||
Loading…
Add table
Add a link
Reference in a new issue