Files
MeshCore/test/test_webconfig_ap_start.py
T
mikecarper bd4cbf281f Keep ESP32 access points compatible with LR transports
Protect standard access points from the shared ESP-IDF LR flag, defer incompatible raw ESP-NOW starts, and select RAM storage before LR writes. Track successful LR owners so setup and OTA access points reject a conflicting running transport before changing WiFi.

Run actual production policies, transport lifecycle methods, and WebConfig teardown branches against a shared-LR SDK model with runtime negative controls; include the regression in unit CI.
2026-10-07 02:07:06 -07:00

71 lines
4.0 KiB
Python

#!/usr/bin/env python3
"""Exercise production WebConfig AP startup against independent SDK state."""
import os
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import unittest
from test_replay_reset_integration import extract_braced
ROOT = Path(__file__).resolve().parents[1]
SOURCE = ROOT / "src/helpers/esp32/WebConfigServer.cpp"
FIXTURE = ROOT / "test/fixtures/webconfig_ap_start.cpp"
class WebConfigApStartTest(unittest.TestCase):
def test_live_ap_state_and_reusable_handoff(self):
compiler = os.environ.get("CXX") or shutil.which("g++") or shutil.which("clang++")
self.assertIsNotNone(compiler, "A host C++17 compiler is required")
source = SOURCE.read_text(encoding="utf-8")
# Keeping helper extraction optional lets the old implementation compile:
# its false-success behavior must fail executable checks, not compilation.
helper = ""
if "static bool setupAccessPointStarted(" in source:
helper += extract_braced(source, "static bool setupAccessPointStarted(") + "\n"
if "static bool setupAccessPointReady(" in source:
helper += extract_braced(source, "static bool setupAccessPointReady(") + "\n"
if "static bool scanSetupNetworks(" in source:
helper += extract_braced(source, "static bool scanSetupNetworks(")
method = extract_braced(source, "bool WebConfigServer::startSetupMode(")
scan_handler = extract_braced(source, "void WebConfigServer::handleScan(")
policy = (ROOT / "src/helpers/esp32/WiFiRadioPolicy.h").read_text(encoding="utf-8")
mask = policy[policy.index("static constexpr uint8_t kAccessPointProtocolMask ="):]
mask = mask[:mask.index(";") + 1]
owner_policy = policy[policy.index("static constexpr uint8_t kLongRangeRadioOwner ="):]
owner_policy = owner_policy[:owner_policy.index("// A bridge can start/stop")]
mask += "\n" + owner_policy
fixture = FIXTURE.read_text(encoding="utf-8").replace("@AP_MASK@", mask)
ap_policy = (ROOT / "src/helpers/esp32/WiFiAccessPointPolicy.h").read_text(encoding="utf-8")
scanner = "namespace mesh { namespace wifi {\n" + "\n".join(
extract_braced(ap_policy, signature) for signature in (
"inline bool finishAccessPointScan(", "inline bool scanBeforeAccessPoint(")) + "\n} }\n"
helper = scanner + helper
fixture = fixture.replace("@METHODS@", helper + "\n" + method + "\n" + scan_handler)
with tempfile.TemporaryDirectory(prefix="webconfig-ap-start-") as directory:
work = Path(directory)
generated = work / "fixture.cpp"
generated.write_text(fixture, encoding="utf-8")
binary = work / "webconfig-ap-start.exe"
sanitizer = (["-fsanitize=address,undefined", "-fno-sanitize-recover=all",
"-fno-pie", "-no-pie"] if sys.platform.startswith("linux") else [])
for arduino_major, protected in ((2, False), (2, True), (3, False), (3, True)):
with self.subTest(arduino_major=arduino_major, protected=protected):
security_flags = ['-DWEBCONFIG_AP_PASSWORD="testpass"'] if protected else []
compiled = subprocess.run([compiler, "-std=c++17", "-Wall", "-Wextra",
*sanitizer, f"-DESP_ARDUINO_VERSION_MAJOR={arduino_major}",
*security_flags,
str(generated), "-o", str(binary)],
capture_output=True, text=True, timeout=60)
self.assertEqual(compiled.returncode, 0, compiled.stdout + compiled.stderr)
checked = subprocess.run([str(binary)], capture_output=True,
text=True, timeout=15)
self.assertEqual(checked.returncode, 0, checked.stdout + checked.stderr)
self.assertIn("WebConfig AP startup regression checks passed", checked.stdout)
if __name__ == "__main__":
unittest.main()