fix: harden portal OTA release

This commit is contained in:
2026-09-20 19:44:05 +10:00
parent 7c5b50499e
commit 360b7cff86
20 changed files with 621 additions and 198 deletions
+71
View File
@@ -73,6 +73,26 @@ class FakeSerial:
self.close()
class FakeClock:
def __init__(self):
self.value = 0.0
def __call__(self):
return self.value
def advance(self, seconds):
self.value += seconds
class BlockingEmptySerial(FakeSerial):
clock = None
def read(self, _size):
type(self).read_calls += 1
type(self).clock.advance(0.25)
return b""
def load_capture_module():
fake_serial_module = types.ModuleType("serial")
fake_serial_module.Serial = FakeSerial
@@ -126,6 +146,57 @@ def main():
assert ("open", False, False, "/dev/fake") in FakeSerial.events
assert ("close",) in FakeSerial.events
assert FakeSerial.read_calls == 2
# An immediate empty-return backend must back off progressively instead of
# spinning. This takes no real sleep because the sleeper is injected.
FakeSerial.read_calls = 0
with tempfile.TemporaryDirectory() as temporary_directory:
output = Path(temporary_directory) / "serial-ota.log"
ready = Path(temporary_directory) / "serial-ota.ready"
empty_sleeps = []
assert module.capture(
"/dev/fake",
output,
ready,
should_stop=lambda: FakeSerial.read_calls >= 5,
sleep=empty_sleeps.append,
) == 0
assert empty_sleeps == [0.01, 0.02, 0.04, 0.08]
# A normal 250 ms serial timeout is already rate-limited by the device
# driver, so it must not receive an additional backoff sleep.
module.serial.Serial = BlockingEmptySerial
BlockingEmptySerial.events = []
BlockingEmptySerial.read_calls = 0
clock = FakeClock()
BlockingEmptySerial.clock = clock
with tempfile.TemporaryDirectory() as temporary_directory:
output = Path(temporary_directory) / "serial-ota.log"
ready = Path(temporary_directory) / "serial-ota.ready"
empty_sleeps = []
assert module.capture(
"/dev/fake",
output,
ready,
should_stop=lambda: BlockingEmptySerial.read_calls >= 2,
clock=clock,
sleep=empty_sleeps.append,
) == 0
assert empty_sleeps == []
module.serial.Serial = FakeSerial
FakeSerial.read_calls = 0
with tempfile.TemporaryDirectory() as temporary_directory:
output = Path(temporary_directory) / "serial-ota.log"
ready = Path(temporary_directory) / "serial-ota.ready"
assert module.capture(
"/dev/fake",
output,
ready,
should_stop=lambda: False,
deadline_seconds=0.0,
) == 2
print("WiFiManager passive OTA serial-capture test-harness check passed")
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env bash
# Check OTA fixture identity and environment locking without PlatformIO or a board.
set -euo pipefail
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
temporary_root="$(mktemp -d "${TMPDIR:-/tmp}/wifimanager-ota-identity-test.XXXXXX")"
chmod 700 "$temporary_root"
cleanup() {
rm -rf -- "$temporary_root"
}
trap cleanup EXIT HUP INT TERM
export ARDUINO_TEST_HARNESS_LOCK_DIR="$temporary_root/locks"
export WIFIMANAGER_OTA_IDENTITY_DIR="$temporary_root/identity"
# shellcheck source=tools/lib/ota-fixture-identity.sh
source "$root/tools/lib/ota-fixture-identity.sh"
# shellcheck source=tools/lib/harness-locks.sh
source "$root/tools/lib/harness-locks.sh"
wm_write_ota_fixture_identity A
identity_file="$WIFIMANAGER_OTA_IDENTITY_DIR/ota_fixture_identity.h"
[[ "$(stat -c '%a' "$WIFIMANAGER_OTA_IDENTITY_DIR")" == 700 ]]
[[ "$(stat -c '%a' "$identity_file")" == 600 ]]
rg -Fqx '#define WM_OTA_FIXTURE_IMAGE "A"' "$identity_file"
wm_lock_ota_fixture_environment esp8266_ota
collision_output="$temporary_root/lock-collision.log"
if (
root="$root"
export WIFIMANAGER_OTA_IDENTITY_DIR="$temporary_root/other-identity"
# shellcheck source=tools/lib/ota-fixture-identity.sh
source "$root/tools/lib/ota-fixture-identity.sh"
wm_lock_ota_fixture_environment esp8266_ota
) 2>"$collision_output"; then
echo "A second test-harness process unexpectedly acquired the same OTA environment lock." >&2
exit 1
fi
rg -Fqx "OTA fixture environment 'esp8266_ota' is already in use by another local test-harness run." "$collision_output"
wm_harness_lock_resource "test resource" "identity-test:shared-resource"
if (
# shellcheck source=tools/lib/harness-locks.sh
source "$root/tools/lib/harness-locks.sh"
wm_harness_lock_resource "test resource" "identity-test:shared-resource"
) 2>"$collision_output"; then
echo "A second test-harness process unexpectedly acquired the same resource lock." >&2
exit 1
fi
rg -Fqx 'Cannot start: test resource is already in use by another local test-harness process.' "$collision_output"
wm_remove_ota_fixture_identity
[[ ! -e "$identity_file" ]]
echo "WiFiManager OTA fixture identity test-harness check passed"