test: e2e firmware update, and restarts told apart by boot time

With --reboot, a restore of the radio alone brings it back as it was:
on a board without Ethernet it read the radio from the wrong place.

--firmware <firmware.bin> updates the device through /updateFirmware and
checks it still runs a minute later, after it marks the update good.

wait_for_restart compared uptimes, so a test run right after another
restart could see more uptime than before and fail.  It compares boot
times, and waits while the device hasn't restarted yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cjkas 2026-10-29 16:59:53 +01:00
parent 64f166490f
commit b525a800b0
5 changed files with 89 additions and 11 deletions

48
test/e2e/test_update.py Normal file
View file

@ -0,0 +1,48 @@
"""A firmware update through /updateFirmware, as the web UI sends it.
Pass --firmware with a firmware.bin built for the device's chip, e.g.
.pio/build/esp32s3/firmware.bin. The device must have an OTA slot: the
release builds do, the C5, C6 and debug builds don't.
"""
import time
import pytest
from proto_client import run_sync
from test_routes import auth, upload
from test_settings import boot_time, wait_for_restart
# The new firmware is marked good once it has run this long with the network
# up; a reset before then boots the old one (docs/adr/0003-pioarduino-idf55.md).
PROVEN_AFTER_S = 60
@pytest.fixture
def firmware(request):
path = request.config.getoption("--firmware")
if not path:
pytest.skip("updates the device's firmware: pass --firmware <firmware.bin>")
with open(path, "rb") as f:
return f.read()
def test_an_update_restarts_on_the_new_firmware_and_stays(host, token, connect, schema, firmware):
c = connect(schema=schema.hash)
_, frames, _ = run_sync(c)
booted = boot_time(schema, frames)
c.close()
status, _, body = upload(host, "/updateFirmware", auth(token), file=firmware)
assert status == 200, body
wait_for_restart(host, token, schema, booted)
# Past the point where it is marked good it is still running, and hasn't
# restarted on the old firmware.
c = connect(schema=schema.hash)
_, frames, _ = run_sync(c)
restarted = boot_time(schema, frames)
c.close()
time.sleep(PROVEN_AFTER_S + 10)
c = connect(schema=schema.hash)
_, frames, _ = run_sync(c)
assert abs(boot_time(schema, frames) - restarted) < 5, "the device restarted again"