From 84a764636804438673d76922119768a678eb7f7a Mon Sep 17 00:00:00 2001 From: mikecarper Date: Thu, 15 Oct 2026 17:37:55 -0700 Subject: [PATCH] Cover merged WiFi boot and board power regressions in CI --- .github/workflows/run-unit-tests.yml | 5 ++ test/test_home_text_spacing.py | 10 ++- test/test_upstream_20261015_wifi_boot.py | 95 ++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 test/test_upstream_20261015_wifi_boot.py diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index a11780519..01e232d87 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -204,6 +204,8 @@ jobs: python3 -B test/test_upstream_routing_merge.py -v python3 -B test/test_iotthinks_merge.py -v python3 -B test/test_upstream_power_policy_merge.py -v + python3 -B test/test_upstream_20261015_wifi_boot.py -v + python3 -B test/test_wio_tracker_l2_gps_power.py -v python3 -B test/test_lr1121_power_merge.py -v python3 -B test/test_radio_interrupt_recovery.py -v python3 -B test/test_bridge_recovery.py -v @@ -271,6 +273,7 @@ jobs: python3 -B test/test_mqtt_handshake_scheduler.py -v python3 -B test/test_mqtt_transport_health.py -v python3 -B test/test_mqtt_diagnostics.py -v + python3 -B test/test_mqtt_advert_verify.py -v python3 -B test/test_mqtt_publish_ack.py -v python3 -B test/test_wifi_network_power_policy.py -v python3 -B test/test_mqtt_wifi_network_power.py -v @@ -643,6 +646,7 @@ jobs: python3 -B test/test_firmware_capabilities.py MESHCORE_TEST_REAL_PIO_PATHS=1 python3 -B test/test_esp32_partition_migration_build_dir.py -v python3 -B test/test_esp32_partition_migration_usb.py -v + python3 -B test/test_waveshare_partition_migration.py -v python3 -B test/test_usb_partition_bridge_set.py -v bash test/test_build_profiles.sh bash test/test_full_infrastructure_selection.sh @@ -679,6 +683,7 @@ jobs: python3 -B test/test_nrf52_ble_trace.py python3 -B test/test_nrf52_ble_debug_tools.py python3 -B test/test_nrf52_usb_power.py + python3 -B test/test_nrf52_shutdown_leds.py -v python3 -B test/test_shared_mota_queue.py - name: Verify opt-in RAK4631 combined Ethernet and OTA modes diff --git a/test/test_home_text_spacing.py b/test/test_home_text_spacing.py index 2bec71148..bfbef1cfa 100644 --- a/test/test_home_text_spacing.py +++ b/test/test_home_text_spacing.py @@ -11,6 +11,8 @@ PREAMBLE = r''' #include #include #include +#include +#include #include #include #include @@ -62,7 +64,13 @@ struct Task { const char* inboxTitle(){return "HISTORY";} const char* connectedClientLabel(){return connected ? "BLUETOOTH" : nullptr;} }; -struct Mesh { uint32_t pin=123456; uint32_t getBLEPin(){return pin;} } the_mesh; +struct Mesh { + uint32_t pin=123456; + uint32_t getBLEPin(){return pin;} + bool isDualRadioActive(){return false;} +} the_mesh; +const mesh::RadioProfiles* configuredRadioProfiles(){return nullptr;} +bool showingSecondaryRadioProfilePage(){return false;} struct HomePage { enum { FIRST=0 }; }; struct IPAddress { int operator[](int i){return 255-i;} }; struct Wifi { IPAddress localIP(){return {};} int status(){return 3;} } WiFi; diff --git a/test/test_upstream_20261015_wifi_boot.py b/test/test_upstream_20261015_wifi_boot.py new file mode 100644 index 000000000..aa11ba23f --- /dev/null +++ b/test/test_upstream_20261015_wifi_boot.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +"""Keep upstream dynamic-WiFi flags on the Companion's existing boot policy.""" +from pathlib import Path +import subprocess +import tempfile +import unittest + + +ROOT = Path(__file__).resolve().parents[1] +COMPANION = ROOT / "examples/companion_radio" + + +class DynamicWiFiBootContractTest(unittest.TestCase): + def test_real_include_order_normalizes_before_feature_selection(self): + mesh = (COMPANION / "MyMesh.h").read_text() + abstract = (COMPANION / "AbstractUITask.h").read_text() + self.assertLess(mesh.index('#include "AbstractUITask.h"'), + mesh.index('#include "CompanionFeatures.h"')) + self.assertIn('#include "NodePrefs.h"', abstract) + # Execute the actual normalization directives, omitting only the SDK + # types after the first include, then include the real feature header. + prefs = (COMPANION / "NodePrefs.h").read_text() + normalization = prefs[:prefs.index("#include ")].replace("#pragma once", "") + implementation = (COMPANION / "MyMesh.cpp").read_text() + end = implementation.index(" _prefs.wifi_enabled = 1;") + start = implementation.rfind("#if", 0, end) + end = implementation.index("#endif", end) + len("#endif") + wifi_defaults = implementation[start:end] + bluetooth_default = " _prefs.bluetooth_enabled = 1;" + self.assertIn(bluetooth_default, implementation) + cases = ( + # defines, terminal feature, WiFi macro, default WiFi, default SSID + (("ESP32_PLATFORM", "ENABLE_WIFI_INTERFACE", "BLE_PIN_CODE=123456"), 1, 1, 0, ""), + (("ESP32_PLATFORM", "ENABLE_WIFI_INTERFACE", "BLE_PIN_CODE=123456", "ENABLE_USB_INTERFACE"), 1, 1, 0, ""), + (("ESP32_PLATFORM", "ENABLE_WIFI_INTERFACE"), 1, 1, 1, ""), + (("ESP32_PLATFORM", "BLE_PIN_CODE=123456"), 0, 0, 0, ""), + (("ESP32_PLATFORM", "ENABLE_WIFI_INTERFACE", "BLE_PIN_CODE=123456", 'WIFI_SSID="saved-build-ssid"'), 1, 1, 0, "saved-build-ssid"), + (("ESP32_PLATFORM", "ENABLE_WIFI_INTERFACE", "BLE_PIN_CODE=123456", "WEBCONFIG_DISABLED"), 0, 1, 0, ""), + (("NRF52_PLATFORM", "BLE_PIN_CODE=123456"), 0, 0, 0, ""), + ) + with tempfile.TemporaryDirectory(prefix="dynamic-wifi-boot-") as directory: + for index, (defines, terminal, has_wifi, wifi_on, ssid) in enumerate(cases): + with self.subTest(defines=defines): + program = normalization + r''' +#include "examples/companion_radio/CompanionFeatures.h" +#include +#include +static_assert(COMPANION_FEATURE_TEXT_TERMINAL == @TERMINAL@, + "dynamic WiFi must expose WebConfig's text terminal"); +#if @HAS_WIFI@ +#ifndef WIFI_SSID +#error Dynamic WiFi must enter the existing WIFI_SSID boot routing +#endif +static_assert(sizeof(WIFI_SSID) == sizeof("@SSID@"), "keep credentials unchanged"); +#else +#ifdef WIFI_SSID +#error Do not invent WiFi support on an ordinary BLE-only target +#endif +#endif +#ifdef WIFI_PWD +#error Enabling the interface must not inject WiFi credentials +#endif +int main() { + struct { int wifi_enabled = -1, bluetooth_enabled = -1; } _prefs; +@WIFI_DEFAULTS@ +@BLUETOOTH_DEFAULT@ + assert(_prefs.wifi_enabled == @WIFI_ON@); + assert(_prefs.bluetooth_enabled == 1); +#if @HAS_WIFI@ + assert(strcmp(WIFI_SSID, "@SSID@") == 0); +#endif +} +''' + for key, value in { + "TERMINAL": terminal, "HAS_WIFI": has_wifi, + "SSID": ssid, "WIFI_ON": wifi_on, + "WIFI_DEFAULTS": wifi_defaults, + "BLUETOOTH_DEFAULT": bluetooth_default, + }.items(): + program = program.replace("@" + key + "@", str(value)) + binary = Path(directory) / str(index) + built = subprocess.run([ + "c++", "-std=c++17", "-Wall", "-Wextra", "-Werror", + "-I", str(ROOT), "-I", str(ROOT / "src"), + *["-D" + define for define in defines], + "-x", "c++", "-", "-o", str(binary), + ], input=program, text=True, capture_output=True, timeout=30) + self.assertEqual(built.returncode, 0, built.stderr) + ran = subprocess.run([str(binary)], capture_output=True, + text=True, timeout=10) + self.assertEqual(ran.returncode, 0, ran.stderr) + + +if __name__ == "__main__": + unittest.main()