test: add portal OTA A/B contract coverage

This commit is contained in:
2026-09-17 12:10:48 +10:00
parent 7305ea8827
commit 228e1252f4
21 changed files with 954 additions and 33 deletions
+67 -9
View File
@@ -9,19 +9,57 @@ lib_deps =
## Target pins
The ESP32 test environments pin the pioarduino `51.03.05` platform package,
which selects Arduino-ESP32 3.0.5 / ESP-IDF 5.1.4+. This is a test-target
contract, not a library-manifest dependency: a consuming application chooses
its own `platform` and must validate the complete framework/toolchain stack.
Core 3 Wi-Fi builds need the C++14, `SOC_WIFI_SUPPORTED`, `Network/src`, and
ESP8266-transport ignore settings in this repository's `platformio.ini`; keep
those settings together when adding an ESP32 environment.
WiFiManager currently has two explicit ESP32 test lanes:
| Lane | pioarduino platform | Purpose |
| --- | --- | --- |
| `esp32` | `51.03.05` / Arduino-ESP32 3.0.5 | temporary compatibility contract |
| `esp32_core_3_3_11` / CLI `esp32-current` | `55.03.311` / Arduino-ESP32 3.3.11 | maintained current validation lane |
This is a test-target contract, not a library-manifest dependency: a consuming
application chooses its own `platform` and must validate the complete
framework/toolchain stack. Do not let a shared global PlatformIO cache choose
framework metadata or a compiler implicitly, and do not override just the
toolchain to repair a cache mismatch. Each pioarduino platform owns its
matching framework, uploader, and compiler package set.
Core 3 Wi-Fi builds need the C++14, `SOC_WIFI_SUPPORTED`, and `Network/src`
settings in this repository's `platformio.ini`; keep those settings together
when adding an ESP32 environment. The portal OTA fixture uses the current
3.3.11 lane for ESP32 even while the 3.0.5 compatibility lane remains
available.
ESP8266 test environments pin framework commit `521ae60` for the upstream
Postmortem large-jump linker fix. The exact rationale and update rule are in
the shared [ESP8266 linker-workaround note](https://github.com/alexhopeoconnor/arduino-home-assistant/blob/main/docs/ESP8266-LINKER-WORKAROUND.md).
For the pioarduino release-to-Core mapping and the scoped repair for a stale
global PlatformIO tool package, see [DeviceFramework's toolchain guide](https://github.com/alexhopeoconnor/DeviceFramework/blob/main/docs/TOOLCHAINS.md).
For the pioarduino release-to-Core mapping and cache-collision diagnosis, see
[DeviceFramework's toolchain guide](https://github.com/alexhopeoconnor/DeviceFramework/blob/main/docs/TOOLCHAINS.md).
`./scripts/test.sh` automatically places the `esp32-current` lane, and
`./tools/portal-hardware ota --platform esp32` places its current A/B fixture,
in a dedicated PlatformIO Core/cache directory, defaulting to
`${XDG_CACHE_HOME:-$HOME/.cache}/wifimanager-platformio/core-3.3.11`. That
keeps pioarduino's package-form `esptool` and generated environment separate
from the legacy 3.0.5 `tool-esptoolpy` graph. Override the location with
`WIFIMANAGER_PLATFORMIO_CORE_DIR`,
`WIFIMANAGER_PLATFORMIO_PACKAGES_DIR`, and
`WIFIMANAGER_PLATFORMIO_CACHE_DIR` when space belongs elsewhere. The first
clean install is several GiB; reserve at least 4 GiB plus cache headroom. It
is a deliberate quarantine, not a reason to delete or override packages in the
shared PlatformIO installation.
For a disposable cache investigation, point that variable at an exact temporary
directory, run the affected command, inspect the resolved graph, then remove
only that directory:
```bash
wm_pio_core="$(mktemp -d /tmp/wifimanager-pio-XXXXXX)"
WIFIMANAGER_PLATFORMIO_CORE_DIR="$wm_pio_core" \
./scripts/test.sh compile --platform esp32-current
WIFIMANAGER_PLATFORMIO_CORE_DIR="$wm_pio_core" \
./scripts/test.sh packages --platform esp32-current
rm -rf -- "$wm_pio_core"
```
Start a release with `bump-version.sh`. It updates package metadata and canonical installation snippets, then creates the changelog section. Replace its generated TODO with the release summary and update any behavioural documentation before running:
@@ -31,8 +69,15 @@ Start a release with `bump-version.sh`. It updates package metadata and canonica
./scripts/check-docs.sh
./scripts/test.sh compile --platform esp8266
./scripts/test.sh compile --platform esp32
./scripts/test.sh compile --platform esp32-current
./scripts/test.sh unity --platform esp8266
./scripts/test.sh unity --platform esp32
./scripts/test.sh unity --platform esp32-current
./scripts/test.sh packages --platform esp32-current
./scripts/test.sh examples --platform esp8266
./scripts/test.sh examples --platform esp32
./scripts/test.sh ota-fixtures --platform esp8266
./scripts/test.sh ota-fixtures --platform esp32-current
./scripts/prepare-release.sh vMAJOR.MINOR.PATCH --tag
```
@@ -57,6 +102,19 @@ for browser/API testing:
See [Testing](TESTING.md#docker-portal-contract) for cleanup, artifacts, and
optional station handoff credentials.
Run the portal HTTP OTA A/B contract separately when a spare adapter and 4 MB
test board are available. It erases the selected board's flash, serial-flashes
A, and uses the real browser update form to upload B; do not replace its
automatic-reboot assertion with a manual reset:
```bash
./tools/portal-hardware ota --platform esp8266 --port /dev/serial/by-id/usb-... \
--client-interface wlx74da385d4165
```
See [Portal HTTP OTA A/B contract](TESTING.md#portal-http-ota-ab-contract) for
the partition, artifact, final-board-state, and adapter rules.
Push the branch and annotated tag. GitHub Actions repeats the board-free compile checks, validates the package, and creates a GitHub Release using that version’s changelog section. The workflow does not publish to the PlatformIO Registry.
Back to [documentation](README.md) · [project overview](../README.md).
+83 -5
View File
@@ -4,10 +4,19 @@ WiFiManager separates repeatable package checks from opt-in tests that flash a
real board or join a captive portal. The normal commands never need a board,
local Wi-Fi credentials, browser binary, or sibling checkout.
## Clean consumer and example builds
| Physical contract | Transport | Host adapter | Secret source | Required proof |
| --- | --- | --- | --- | --- |
| Portal lifecycle suite | Serial flash + captive-portal HTTP/browser | Named secondary adapter | safe fixture AP password | Unity/lifecycle checks and portal UI/API contract |
| Portal HTTP OTA | WiFiManager multipart `POST /u` | Named secondary adapter | safe fixture AP password | A → rendered browser upload B → automatic reboot → B twice |
The clean-consumer check builds a project that declares only WiFiManager. It
proves the package manifest resolves DFTE, ESPAsyncWebServer, and the correct
The selected secondary adapter is intentionally never used for normal LAN
testing. It is `never-default`, so the host's ordinary route remains intact.
## Board-free fixture, consumer, and example builds
The Unity compile check builds WiFiManager's own fixture without a board. The
clean-consumer check builds a project that declares only WiFiManager. Together
they prove the package manifest resolves DFTE, ESPAsyncWebServer, and the correct
ESP8266 or ESP32 TCP dependency without a sibling checkout. The runner removes
a prior local package link before each check, so dependency resolution uses the
current manifest rather than a stale `.pio` copy.
@@ -15,13 +24,24 @@ current manifest rather than a stale `.pio` copy.
```bash
./scripts/test.sh compile --platform esp8266
./scripts/test.sh compile --platform esp32
./scripts/test.sh compile --platform esp32-current
./scripts/test.sh unity --platform esp8266
./scripts/test.sh unity --platform esp32
./scripts/test.sh unity --platform esp32-current
./scripts/test.sh examples --platform esp8266
./scripts/test.sh examples --platform esp32
./scripts/test.sh ota-fixtures --platform esp8266
./scripts/test.sh ota-fixtures --platform esp32-current
```
CI runs these board-free checks for pull requests and pushes to the maintained
branch. It intentionally does not require attached hardware, a local network,
or Docker.
branch. `esp32` remains the explicit Arduino-ESP32 3.0.5 compatibility lane;
`esp32-current` is the clean-consumer Arduino-ESP32 3.3.11 validation lane.
The OTA fixture builds also use 3.3.11 for ESP32 and compile both immutable A
and B images against their tracked OTA partition layout. CI rejects equal A/B
artifacts, an ESP32 image larger than either 0x1F0000-byte app slot, or a
partition-table edit that breaks the required two-slot/no-filesystem layout. These checks
intentionally do not require attached hardware, a local network, or Docker.
## Local hardware lifecycle tests
@@ -155,4 +175,62 @@ README tour is readable; it does not change normal browser-contract timing.
ESP8266 remains covered by the normal hardware and browser contract but does
not produce duplicate README media.
## Portal HTTP OTA A/B contract
`portal-hardware ota` is a separate opt-in physical test for WiFiManager's
built-in HTTP update path. It exercises the rendered firmware-update page and
its real multipart `POST /u` request; it is not an ArduinoOTA/UDP test.
```bash
./tools/portal-hardware ota \
--platform esp8266 \
--port /dev/serial/by-id/usb-... \
--client-interface wlx74da385d4165
```
The selected `--client-interface` has exactly the same safety rules as the
normal portal contract: it must be the explicitly named secondary adapter and
cannot be the host default-route interface. The test never attaches that
adapter to a normal station network. The fixture AP uses the safe local
`default1` WPA password; this is an AP-access test, not a claim that `/u` has
HTTP route authentication.
The runner performs the following complete contract:
1. Builds immutable A and B fixture images. Their marker is compiled into the
binary, not saved in WiFiManager settings or EEPROM.
2. Checks both ESP32 images against the explicit matching `app0`/`app1` slots;
ESP8266 validates B after A has booted against the exact aligned capacity
passed to `Update.begin()`.
3. Erases the explicitly selected test board's flash, then flashes A over
serial and starts its captive portal.
4. Joins that portal only through the named secondary adapter and requires the
A marker at `/api/test/firmware-marker`.
5. Mounts B read-only into the Playwright container, chooses it in the real
`#wm-ota-file` browser input, and submits the rendered form.
6. Requires the real `POST /u` success response, an automatic portal outage,
automatic restart, and two independent B-marker responses.
The fixture marker endpoint exists only in `test/portal-harness`; it is not a
WiFiManager library route or a product-firmware pattern. The test does not
issue a manual reset. A board which boots B only after intervention is a
failure, even if B later appears.
Both fixture images are built with explicit OTA-capable layouts:
| Platform | Fixture layout | Capacity check |
| --- | --- | --- |
| ESP8266 | `eagle.flash.4m1m.ld` | A's live `ESP.getFreeSketchSpace()` response |
| ESP32 | two `0x1F0000` A/B app slots, no filesystem | tracked CSV `app1` size |
These are 4 MB fixture layouts (`d1_mini` for ESP8266 and `esp32dev` for
ESP32). Do not run this command against a board with another flash size unless
its matching explicit A/B layout and capacity checks have been added first.
The final board state is firmware B in the portal-only fixture: it clears
saved station settings on every boot and leaves no developer Wi-Fi credential
on the device. By default the temporary NetworkManager connection is removed
when the test exits. Pass `--keep` only for interactive diagnosis, then run
`./tools/portal-hardware down` to remove that named temporary connection.
Back to [documentation](README.md) · [project overview](../README.md).