diff --git a/test/fixtures/companion_help_output.sha256.json b/test/fixtures/companion_help_output.sha256.json index eab3260df..2f1519081 100644 --- a/test/fixtures/companion_help_output.sha256.json +++ b/test/fixtures/companion_help_output.sha256.json @@ -30,12 +30,12 @@ "MESH_ENABLE_LOST_REPLY=0" ], "output_bytes": [ - 4644, - 4636 + 4907, + 4899 ], "output_sha256": [ - "c830a4086f88e06ec7fe21bb6f1287e78fe4e72cefd7c3999740913f9ce3831d", - "b71c0ba723d2ff693c1ed8ce23998d977607a213dba4af688fe4db16a59e38f6" + "f35a89cf2f1f730069729b5d977edb48b89e5062e14ead8009b5675e1d3146f8", + "8d68da4d7f2b66a8a2ad889b0f7e01eb1a9a5432388ac6466ed3c53eebd558c1" ] }, "all-options-native0-lost1": { @@ -68,12 +68,12 @@ "MESH_ENABLE_LOST_REPLY=1" ], "output_bytes": [ - 4679, - 4671 + 4942, + 4934 ], "output_sha256": [ - "7375ec52046c4479f32f33a1845689344eb6d9c3b29d1d0a1404d57c148eb703", - "03401a37d0b371186885ddda541f8c011c76125f98c46b27be4f3d559fe2cd3e" + "1931d924971295f58e9c87606a101d52f4c584a3e2a76d8aed208874c17942b1", + "81707eb39133311da39145a722ac526997e21efe597ded614ef1818af11414dc" ] }, "all-options-native1-lost0": { @@ -106,12 +106,12 @@ "MESH_ENABLE_LOST_REPLY=0" ], "output_bytes": [ - 4729, - 4721 + 4992, + 4984 ], "output_sha256": [ - "7c2f2ca10b445bd5a4ea9f747198dc41bf4275997b38a5af1a108edb1fada34c", - "8841b225cf40dcad34d7de15b945384e933c668dd0d63f2abbf9449cf542af95" + "916dfbc8915ca39d0f298d2c23dee8674605f73ede5d0f39134e791523864114", + "8ed4ff32d2e8a6e9f3dabae7bf0d1e5497ee09d77294b8e4b5399aa43e57cee9" ] }, "all-options-native1-lost1": { @@ -144,12 +144,12 @@ "MESH_ENABLE_LOST_REPLY=1" ], "output_bytes": [ - 4764, - 4756 + 5027, + 5019 ], "output_sha256": [ - "c141a0237099f1f63ae8768001614673d6d54914ae78d41fc4a483f031326f25", - "d562a7dcac544c502908205b14eaa20e921c76830ba5921018c4aa19798efea8" + "7b7caa2c93f326246bc6a31b26a4efc11cae71548c812742924259b51c8d43ae", + "bd9c03ac867cf6ad62d3febbfa4875951902878226a67081d72f188e073a0c20" ] }, "full-native0-lost0": { @@ -173,12 +173,12 @@ "MESH_ENABLE_LOST_REPLY=0" ], "output_bytes": [ - 3214, - 3206 + 3564, + 3556 ], "output_sha256": [ - "ef1d9548ee100678eac89f0d60a42dd356bf99127eb819beccc4a15895145b94", - "8a3fe836f56831c9c8de86254fd8186f0ece25d0d63acf3b1c916990341a0c0a" + "2df296daf6c3050c2390dc55afffd0c2c43b35620cd8ce1a73162202b8abf02a", + "b4b69f1a12aebab76945cdef4c7e94078a8e9a8d3f718b68facb215570e9d0cc" ] }, "full-native0-lost1": { @@ -202,12 +202,12 @@ "MESH_ENABLE_LOST_REPLY=1" ], "output_bytes": [ - 3249, - 3241 + 3599, + 3591 ], "output_sha256": [ - "ef5d1cd1f549568acf0b1e769bdcd68bd96855301ec8e7e053df9e9c87a03b6d", - "17a4afab06e7e61cccde52da08ea6d939ead8c34f4ca321cabdaed3efdf9da62" + "3105232dfa903a6c15f3ed6d6034b59b0a32ca24d6151ad48fc33de621b3277a", + "8bd526c52eedc16c7cfdc013311634470a40fdc80035760a6accacd876cca794" ] }, "full-native1-lost0": { @@ -231,12 +231,12 @@ "MESH_ENABLE_LOST_REPLY=0" ], "output_bytes": [ - 3299, - 3291 + 3649, + 3641 ], "output_sha256": [ - "3402723097417e800d48275bb7e4b7a8911ac57a8370a544900137938fcbde66", - "2313fef0b2d9f3f74ab816dd6d8d27b44acb126469b3e08446544c3f0c291e77" + "88ca44fdc7bce777e3120131c0631e981c497aa0a80849b5badca9df29c0b624", + "64f96b9832d0f83ed5fea70ae9514cf60ae1232d1589c4e73a31ddbf246bb40d" ] }, "full-native1-lost1": { @@ -260,12 +260,12 @@ "MESH_ENABLE_LOST_REPLY=1" ], "output_bytes": [ - 3334, - 3326 + 3684, + 3676 ], "output_sha256": [ - "71c96f2d042f38c896fe14dc6f867898bb413498dfe669d874130e1595a8e560", - "feb8bb64cca5032c07d967f7428dafb4787fc7eb342fc398ea78bb3746b4b7b8" + "c1db29ea27a6db989227eef74c55b2ca4e07b0909df8232842d52886e02697ce", + "f9428924f7fc57fee248100282deeb03ba66ee1a83d11ee37c3dd3bfc9edd61d" ] }, "minimal-native0-lost0": { @@ -275,12 +275,12 @@ "MESH_ENABLE_LOST_REPLY=0" ], "output_bytes": [ - 1696, - 1688 + 2046, + 2038 ], "output_sha256": [ - "82ed92c5c52cbd699c5b57f11006367b338cab5347f06aeccb0edf80f8b16ec2", - "c0047e0c3eae1e9d828dabe6157ed4296af1d0c93c249aa1dda0ea5920f5ef25" + "5571a06d789dff50aebd53777095cc8f0f74e5fd4226ed399bd25050e9e5a02e", + "9328e546b561bc1d965f8036d73c862cfed9d5077111c0b5c8744822235740bb" ] }, "minimal-native0-lost1": { @@ -290,12 +290,12 @@ "MESH_ENABLE_LOST_REPLY=1" ], "output_bytes": [ - 1731, - 1723 + 2081, + 2073 ], "output_sha256": [ - "de5cbd88c77ed4f30817bcd89904725c97fb49142cf048e7b90e897d646e7b6b", - "20daa08b614642afffe00bc1c89621a020e688d79a32113da16037e3a3f4c460" + "9ee93b7a588ce48c647b9871dc7bec5c3f307c06c29a65cb6ff66a55060247b6", + "449353daa515ede8c6528782406b3f4a101d60bee46412aa934ea94e922ed5a5" ] }, "minimal-native1-lost0": { @@ -305,12 +305,12 @@ "MESH_ENABLE_LOST_REPLY=0" ], "output_bytes": [ - 1696, - 1688 + 2046, + 2038 ], "output_sha256": [ - "82ed92c5c52cbd699c5b57f11006367b338cab5347f06aeccb0edf80f8b16ec2", - "c0047e0c3eae1e9d828dabe6157ed4296af1d0c93c249aa1dda0ea5920f5ef25" + "5571a06d789dff50aebd53777095cc8f0f74e5fd4226ed399bd25050e9e5a02e", + "9328e546b561bc1d965f8036d73c862cfed9d5077111c0b5c8744822235740bb" ] }, "minimal-native1-lost1": { @@ -320,12 +320,12 @@ "MESH_ENABLE_LOST_REPLY=1" ], "output_bytes": [ - 1731, - 1723 + 2081, + 2073 ], "output_sha256": [ - "de5cbd88c77ed4f30817bcd89904725c97fb49142cf048e7b90e897d646e7b6b", - "20daa08b614642afffe00bc1c89621a020e688d79a32113da16037e3a3f4c460" + "9ee93b7a588ce48c647b9871dc7bec5c3f307c06c29a65cb6ff66a55060247b6", + "449353daa515ede8c6528782406b3f4a101d60bee46412aa934ea94e922ed5a5" ] }, "qspi-native0-lost0": { @@ -359,12 +359,12 @@ "MESH_ENABLE_LOST_REPLY=0" ], "output_bytes": [ - 4504, - 4496 + 4767, + 4759 ], "output_sha256": [ - "32c607f77a4c48829a6bbd91538d0dc717f813fc32251455702913a8ffea0cdb", - "cfb475576309abb89fcad75d7d019049ed2a5489dd325fea5bcaf0ed186c9d82" + "35197aca1bdc0f7bf52f8b468a23f019e05f5779b6eaf0cb1e498a042685ea8d", + "69b0ad692bb4adf99dedbc192269b5f43c0270dee6c863ec93fa333952d730ac" ] }, "qspi-native0-lost1": { @@ -398,12 +398,12 @@ "MESH_ENABLE_LOST_REPLY=1" ], "output_bytes": [ - 4539, - 4531 + 4802, + 4794 ], "output_sha256": [ - "de162715b7175070aa2fce814b9b20dcef2a1a33ca1a14a5d0063f7dbccb489a", - "3ea7895b287cbe415d3febfc4d1f97084828f37ae4fa78e98ab8da0b0c2bf4fe" + "6c8d2efbc41010b73ccc76cc6da9e37e1ad7f85cc38e271e982314d94f9ccbf4", + "8f664370def3695157f7d2b56a36826182d19f8866b69643e6c654f15fc8aff6" ] }, "qspi-native1-lost0": { @@ -437,12 +437,12 @@ "MESH_ENABLE_LOST_REPLY=0" ], "output_bytes": [ - 4589, - 4581 + 4852, + 4844 ], "output_sha256": [ - "a173923cd67bf96c4c1f6f5d4fa933214ecaa68c8c43d7d881f53f3e49a807c5", - "c367f8675987d86270c0b763e1249201872d0e725a20cee4b93e155d360cf12d" + "d5f33dd01c217329a8c8833b52fef66d7f8cb36d8318099b2365e541d0c2d20d", + "7e74ea47a40bb08a968edd6b9164901e8611ed87581df2826e10c62dc8f1c418" ] }, "qspi-native1-lost1": { @@ -476,17 +476,19 @@ "MESH_ENABLE_LOST_REPLY=1" ], "output_bytes": [ - 4624, - 4616 + 4887, + 4879 ], "output_sha256": [ - "9ba51362d80560bafd41f170e4791542dea7f128cca2c07c8e13413a42e74b09", - "d3a7f18a0f799b95b3a44cee602fc3d6e60a314bdd15e5bcf4f29ad510b45cda" + "8c718f17d64ee474727fe90ab49a3c829bb040e24b9d1f3164034d1ed85b4a4b", + "f46636f6b6aa8a8f0104f84d1ddffb689ea56fd42d7294b209222ba76f75dd2d" ] } }, "schema": 1, - "scope": "Exact existing help bytes before bounded print-call splitting; network then USB terminal mode", - "source_commit": "c46defaefd642973cbbdb473815d56b5b261cb2e", - "source_file_sha256": "4665bf3d9c60abe3961aa3548d51c0d3a242d49a2ff061a4732fa07d06ae5a6a" + "scope": "Exact current shared help bytes for all 16 logging/feature profiles; network then USB terminal mode", + "source_commit": "837847f8910221d190578f68b2e09599be2202a0", + "source_file_sha256": "91760ca4450d42c236f4591aba4f273b4deff2d584126d6207c1561e8fd85dcd", + "source_fragment_file": "examples/companion_radio/CompanionHelp.inc", + "source_fragment_sha256": "f481cbb032578e425ac820f43cf91926ae1c97e609a4d0e3092377c48cbe2676" } diff --git a/test/test_cli_settings_contract.py b/test/test_cli_settings_contract.py index 153e1e9bd..fda1e74c9 100644 --- a/test/test_cli_settings_contract.py +++ b/test/test_cli_settings_contract.py @@ -203,6 +203,7 @@ class CLISettingsContractTest(unittest.TestCase): shared = extract_braced(companion, 'static bool isCompanionRadioPrefsCommand(') sets |= {k for verb, k in re.findall(r'"(get|set) ([\w.]+) ?"', shared) if verb == 'set'} self.assertEqual(gets - sets, { + 'bootloader.ver', # Installed bootloader identity 'contact.cache', 'contact.cache.timing', # Contact-storage diagnostics 'display.wifi', # Connection/display status 'wifi.tcp', # Read-only app listener/traffic diagnostic diff --git a/test/test_companion_identity_cli.py b/test/test_companion_identity_cli.py index 322ac2d36..928193cfc 100644 --- a/test/test_companion_identity_cli.py +++ b/test/test_companion_identity_cli.py @@ -19,6 +19,22 @@ HARNESS = r''' #include #include #include +#include + +// Keep the production diagnostics delegate in the command path. Identity +// requests must not consult board power state or trigger a hardware query. +struct IdentityBoard { + bool hasUsbPowerDetect(){assert(false);return false;} + bool isUsbPowered(){assert(false);return false;} + bool hasSolarChargerDetect(){assert(false);return false;} + bool isSolarChargerActive(){assert(false);return false;} + uint16_t getBattMilliVolts(){assert(false);return 0;} + bool isExternalPowered(){assert(false);return false;} + bool isChargerActive(){assert(false);return false;} + bool supportsPowerManagement(){assert(false);return false;} + uint16_t getBootVoltage(){assert(false);return 0;} + bool getSystemWatchdogRunning(bool&){assert(false);return false;} +} board; namespace mesh { @HEX_CHARS@ @@ -184,7 +200,13 @@ class CompanionIdentityCliTests(unittest.TestCase): terminal = method(source, 'void MyMesh::handleTerminalCommand(') self.assertIn('handleLocalControlCommand(command, local_reply, sizeof(local_reply))', terminal) self.assertIn('terminalOutput().printf(" -> %s\\r\\n", local_reply);', terminal) - self.assertIn('" get public.key\\r\\n"', terminal) + help_branch = method(terminal, 'else if (strcmp(command, "help") == 0)') + self.assertIn('#include "CompanionHelp.inc"', help_branch) + self.assertIn('mesh::companion::writePackedCompanionHelp(output);', help_branch) + help_fragments = (ROOT / 'examples/companion_radio/CompanionHelp.inc').read_text() + self.assertIn('" get public.key\\r\\n"', help_fragments) + packed_help = (ROOT / 'examples/companion_radio/CompanionHelpPacked.cpp').read_text() + self.assertIn('#include "CompanionHelp.inc"', packed_help) shared = method(source, 'bool MyMesh::handleCommand(') self.assertIn('if (handleLocalControlCommand(command, reply, reply_capacity)) return true;', shared) self.assertIn('return handleCommand(command, 0, reply);', diff --git a/test/test_companion_primary_radio_persistence.py b/test/test_companion_primary_radio_persistence.py index b7e509a18..6729b8555 100644 --- a/test/test_companion_primary_radio_persistence.py +++ b/test/test_companion_primary_radio_persistence.py @@ -29,9 +29,19 @@ HARNESS = r''' static MemoryFS* profile_metadata_fs = nullptr; static bool profile_metadata_error = false; static unsigned profile_metadata_probes = 0; +static unsigned fast_metadata_probes = 0; +static bool isProfileMetadataPath(const char* path) { + for (const char* expected : {"/spiffs/radio_profiles", "/spiffs/radio_profiles.bak", + "/spiffs/radio_profiles.tmp", "/spiffs/radio_profiles.reply", + "/spiffs/radio_fast", "/spiffs/radio_fast.bak", "/spiffs/radio_fast.tmp"}) { + if (!strcmp(path, expected)) return true; + } + return false; +} int stat(const char* path, struct stat* info) { - assert(profile_metadata_fs && !strncmp(path, "/spiffs/radio_profiles", 22)); + assert(profile_metadata_fs && isProfileMetadataPath(path)); ++profile_metadata_probes; + if (!strncmp(path, "/spiffs/radio_fast", 18)) ++fast_metadata_probes; if (profile_metadata_error) { errno = EIO; return -1; } if (!profile_metadata_fs->exists(path + 7)) { errno = ENOENT; return -1; } info->st_mode = 0100000; @@ -131,6 +141,15 @@ struct Infrastructure { static bool error(const char* reply) { return strstr(reply,"Error") || strstr(reply,"ERROR"); } int main() { unsigned checks=0; + // The shared CLI probes both stores at startup. Match only their actual + // committed, rollback and staging names, rather than accepting any prefix. + for (const char* path : {"/spiffs/radio_profiles", "/spiffs/radio_profiles.bak", + "/spiffs/radio_profiles.tmp", "/spiffs/radio_profiles.reply", + "/spiffs/radio_fast", "/spiffs/radio_fast.bak", "/spiffs/radio_fast.tmp"}) + assert(isProfileMetadataPath(path)); + for (const char* path : {"/spiffs/radio_profiles_extra", "/spiffs/radio_fast_extra", + "/spiffs/radio_fast.reply", "/radio_profiles", "/spiffs/new_prefs"}) + assert(!isProfileMetadataPath(path)); // App radio settings require exactly the upstream four-field CSV, even // with an explicit preamble or an active temporary/secondary profile. for(uint32_t timestamp:{0U,1700000000U})for(uint16_t preamble:{0U,48U}) { @@ -224,6 +243,7 @@ int main() { m.expectOriginal();++checks; } assert(profile_metadata_probes>0); + assert(fast_metadata_probes>0); printf("%u production primary-radio persistence scenarios passed\n",checks); } ''' diff --git a/test/test_companion_terminal_profile.py b/test/test_companion_terminal_profile.py index 8de1da386..e04cbeddf 100644 --- a/test/test_companion_terminal_profile.py +++ b/test/test_companion_terminal_profile.py @@ -83,9 +83,15 @@ assert re.search( assert terminal.index("handleLocalControlCommand(") < terminal.index( 'strncmp(command, "set ", 4) == 0' ) -# Help is emitted in bounded chunks with concatenated C++ literals. Check the -# actual help branch rather than requiring one print() call per command. +# Both direct and packed help use the same feature-gated fragments. Follow +# the actual terminal routes before checking their shared command listings. help_branch = body(terminal, 'if (strcmp(command, "help") == 0)') +assert '#include "CompanionHelp.inc"' in help_branch +assert '#define MESH_COMPANION_HELP_BEGIN output.print(' in help_branch +assert 'mesh::companion::writePackedCompanionHelp(output);' in help_branch +packed_help = (root / "examples/companion_radio/CompanionHelpPacked.cpp").read_text() +assert '#include "CompanionHelp.inc"' in packed_help +help_fragments = (root / "examples/companion_radio/CompanionHelp.inc").read_text() for help_line in (' board\\r\\n', ' version\\r\\n', ' get pwrmgt.bootreason\\r\\n', ' set pin <0-999999>\\r\\n', ' set mt.hops <0..7> (saved; default 7)\\r\\n', @@ -93,14 +99,14 @@ for help_line in (' board\\r\\n', ' version\\r\\n', ' set mt.node (saved; use a unique ID)\\r\\n', ' set mt.shortname <1..4 UTF-8 characters|default> (saved; NodeInfo uses 4 bytes)\\r\\n', ' send mt.nodeinfo (GPS-free; also automatic after first chat)\\r\\n'): - assert help_line in help_branch + assert help_line in help_fragments # Explicit NodeInfo remains available, but first successful chat also arms # separate GPS-free metadata. Do not restore the obsolete manual-only policy. -assert "no automatic adverts" not in help_branch +assert "no automatic adverts" not in help_fragments assert re.search( r"#if COMPANION_FEATURE_MEMORY_DIAGNOSTICS\s+" r'" memory\\r\\n"\s+#endif', - help_branch, + help_fragments, ) # Terminal-specific handlers keep precedence, then both the generic `set` diff --git a/test/test_companion_usb_logging.py b/test/test_companion_usb_logging.py index 488644848..909b2f098 100644 --- a/test/test_companion_usb_logging.py +++ b/test/test_companion_usb_logging.py @@ -319,7 +319,8 @@ class CompanionUsbLoggingTests(unittest.TestCase): '-include', str(work / 'platform_shim.h'), '-I', str(work), '-I', str(ROOT / 'test/fixtures/radio_profiles/mocks'), '-I', str(ROOT / 'test/mocks'), '-I', str(ROOT / 'src'), - '-I', str(ROOT / 'src/helpers'), '-I', str(ROOT), str(source), + '-I', str(ROOT / 'src/helpers'), + '-I', str(ROOT / 'examples/companion_radio'), '-I', str(ROOT), str(source), *[str(ROOT / 'src/helpers' / name) for name in extra], '-o', str(binary)], capture_output=True, text=True) self.assertEqual(built.returncode, 0, built.stdout + built.stderr) @@ -454,11 +455,12 @@ int main(){ } } '''.replace('@HELP@', source[start:end]) - # These hashes were captured from the unmodified c46 help branch. + # These reviewed hashes cover the production shared help fragments. # Compare the complete output bytes, including every conditional line, # rather than duplicating a second help implementation in this test. fixture = json.loads((ROOT / 'test/fixtures/companion_help_output.sha256.json').read_text()) self.assertEqual(fixture['schema'], 1) + self.assertEqual(len(fixture['profiles']), 16) with tempfile.TemporaryDirectory() as directory: work = Path(directory) self.prepare(work) diff --git a/test/test_indicator_exclusive_transport.py b/test/test_indicator_exclusive_transport.py index 569bf9eb3..952bf20a3 100644 --- a/test/test_indicator_exclusive_transport.py +++ b/test/test_indicator_exclusive_transport.py @@ -123,7 +123,8 @@ assert "#elif defined(BLE_PIN_CODE)" in power_helper assert re.search( r"static bool bluetoothWiFiCoexistenceRequired\(\) \{\s+" r"#if defined\(COMPANION_EXCLUSIVE_WIFI_BLE\)\s+.*?return false;\s+" - r"#elif defined\(BLE_PIN_CODE\) && defined\(WIFI_SSID\)", + r"#elif defined\(BLE_PIN_CODE\) && \(defined\(WIFI_SSID\)" + r" \|\| defined\(ENABLE_WIFI_INTERFACE\)\)", webconfig, re.DOTALL, ) @@ -151,14 +152,20 @@ help_body = mesh[help_start:help_end] # bounded. Keep both policy-specific commands on that same selected stream. help_output = re.search(r"auto& (\w+) = terminalOutput\(\);", help_body) assert help_output +assert f"#define MESH_COMPANION_HELP_BEGIN {help_output[1]}.print(" in help_body +assert '#include "CompanionHelp.inc"' in help_body +assert f"mesh::companion::writePackedCompanionHelp({help_output[1]});" in help_body +help_fragments = (root / "examples/companion_radio/CompanionHelp.inc").read_text() +packed_help = (root / "examples/companion_radio/CompanionHelpPacked.cpp").read_text() +assert '#include "CompanionHelp.inc"' in packed_help help_transport = re.search( r"#if defined\(COMPANION_EXCLUSIVE_WIFI_BLE\)(.*?)#endif", - help_body, + help_fragments, re.DOTALL, ) assert help_transport for text in (" get companion.transport\\r\\n", " set companion.transport \\r\\n"): - assert f'{help_output[1]}.print("{text}")' in help_transport[1] + assert f'"{text}"' in help_transport[1] assert "WebUI unavailable while Bluetooth transport is active" in mesh diff --git a/test/test_nrf52_extrafs_contract.py b/test/test_nrf52_extrafs_contract.py index 08b804f2b..bd6a87198 100644 --- a/test/test_nrf52_extrafs_contract.py +++ b/test/test_nrf52_extrafs_contract.py @@ -1033,9 +1033,18 @@ class Nrf52ExtraFsContractTest(unittest.TestCase): terminal = function_body(mesh, "void MyMesh::handleTerminalCommand(") self.assertIn('strcmp(command, "repair extrafs")', terminal) self.assertIn("repairInternalExtraFS(terminalOutput());", terminal) + self.assertIn('#include "CompanionHelp.inc"', terminal) + self.assertIn("mesh::companion::writePackedCompanionHelp(output);", terminal) + help_fragments = (MESH.parent / "CompanionHelp.inc").read_text(encoding="ascii") self.assertIn( - "repair extrafs (erases/rebuilds internal ExtraFS)", terminal + "repair extrafs (erases/rebuilds internal ExtraFS)", help_fragments ) + self.assertRegex(help_fragments, + r"#if defined\(NRF52_PLATFORM\) && defined\(EXTRAFS\) && !defined\(QSPIFLASH\)" + r"\s+MESH_COMPANION_HELP_BEGIN\s+" + r'" repair extrafs \(erases/rebuilds internal ExtraFS\)\\r\\n"') + packed_help = (MESH.parent / "CompanionHelpPacked.cpp").read_text(encoding="ascii") + self.assertIn('#include "CompanionHelp.inc"', packed_help) def test_rescue_rebuild_reports_success_only_after_every_save_succeeds(self): mesh = MESH.read_text(encoding="utf-8") diff --git a/test/test_nrf52_font_license.py b/test/test_nrf52_font_license.py index ccae37222..7e41e9c15 100644 --- a/test/test_nrf52_font_license.py +++ b/test/test_nrf52_font_license.py @@ -27,6 +27,7 @@ class BuildEnvironment(dict): self.update(changes) self.actions = [] self.dependencies = [] + self.middleware = [] def Depends(self, target, sources): self.dependencies.append((target, sources)) @@ -34,6 +35,9 @@ class BuildEnvironment(dict): def AddPostAction(self, target, action): self.actions.append((target, action)) + def AddBuildMiddleware(self, callback, pattern): + self.middleware.append((callback, pattern)) + def subst(self, value): return (value.replace("$PROJECT_DIR", str(ROOT)) .replace("$BUILD_DIR", str(self.get("BUILD_DIR", "build"))) diff --git a/test/test_nrf52_uf2reset_cli.py b/test/test_nrf52_uf2reset_cli.py index 20f22d76f..68eba2209 100644 --- a/test/test_nrf52_uf2reset_cli.py +++ b/test/test_nrf52_uf2reset_cli.py @@ -42,11 +42,18 @@ help_body = companion[help_start:help_end] help_output = re.search(r"auto& (\w+) = terminalOutput\(\);", help_body) assert help_output # Bounded help writes use the cached selected stream; UF2 help stays nRF-only. +assert '#include "CompanionHelp.inc"' in help_body +assert (f'#define MESH_COMPANION_HELP_LINE_BEGIN {help_output[1]}.println(' + in help_body) +assert 'mesh::companion::writePackedCompanionHelp(output);' in help_body +help_fragments = (ROOT / "examples/companion_radio/CompanionHelp.inc").read_text() +packed_help = (ROOT / "examples/companion_radio/CompanionHelpPacked.cpp").read_text() +assert '#include "CompanionHelp.inc"' in packed_help assert re.search( r"#if defined\(NRF52_PLATFORM\)\s+" - + re.escape(f'{help_output[1]}.println(" uf2reset");') - + r"\s+#endif", - help_body, + r'MESH_COMPANION_HELP_LINE_BEGIN\s+" uf2reset"\s+' + r"MESH_COMPANION_HELP_LINE_END\s+#endif", + help_fragments, ) assert "mesh::cli::isUf2ResetCommand(command)" in chat diff --git a/test/test_rak15002_sd_seeder.py b/test/test_rak15002_sd_seeder.py index e60a53a56..0ad468222 100644 --- a/test/test_rak15002_sd_seeder.py +++ b/test/test_rak15002_sd_seeder.py @@ -204,6 +204,7 @@ int main() { EnvironmentSensorManager sensors; assert(sensors.detectWisBlockStor methods = "namespace mesh { namespace cli {\n" + "\n".join( extract_braced(source, signature) for signature in ( "static bool sdCardBusAvailable(", "static void formatSdCardElapsed(", + "static __attribute__((noinline)) bool finishSdCardFailure(", "bool handleSdCardSet(", "bool handleSdCardGet(")) + "\n} }\n" fixture = (FIXTURES / "cli.cpp").read_text() build(self, fixture.replace("@METHODS@", methods), ["NRF52_PLATFORM", "OTA_SD_STORE"]) diff --git a/test/test_rak3401_onboard_radio.py b/test/test_rak3401_onboard_radio.py index 9a8469feb..abaf26fe2 100644 --- a/test/test_rak3401_onboard_radio.py +++ b/test/test_rak3401_onboard_radio.py @@ -468,8 +468,11 @@ class Rak3401OnboardRadioTests(unittest.TestCase): self.assertIn("digitalWrite(SX126X_POWER_EN, HIGH);", begin) init = extract_braced(target, "bool radio_init()") self.assertIn("radio.std_init(&SPI)", init) - self.assertIn("SX126xBufferedRadioHal radioHal(SPI)", target) - self.assertIn("new Module(&radioHal, P_LORA_NSS, P_LORA_DIO_1, " + # The normal nRF52 path keeps byte-wise transfers while allowing its + # saved SPI clock selection; onboard-radio probing must not replace it. + self.assertNotIn("BufferedRadioHal", target) + self.assertIn("static TunableRadioHal radio_hal(SPI);", target) + self.assertIn("new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, " "P_LORA_RESET, P_LORA_BUSY)", target) # External-radio construction and retry must retain their SPI1 owner. external_target = (VARIANT / "target.cpp").read_text() diff --git a/test/test_rak_sd_context_handoff.py b/test/test_rak_sd_context_handoff.py index acb8f7446..a9d253c5b 100644 --- a/test/test_rak_sd_context_handoff.py +++ b/test/test_rak_sd_context_handoff.py @@ -109,6 +109,7 @@ class RakSdContextHandoffTests(unittest.TestCase): methods = 'namespace mesh { namespace cli {\n' + '\n'.join( extract_braced(source, signature) for signature in ( 'static bool sdCardBusAvailable(', 'static void formatSdCardElapsed(', + 'static __attribute__((noinline)) bool finishSdCardFailure(', 'bool handleSdCardSet(')) + '\n} }\n' fixture = (ROOT / 'test/fixtures/rak15002_sd_seeder/maintenance.cpp').read_text() for profile in ( diff --git a/test/test_touch_debug_cli.py b/test/test_touch_debug_cli.py index 9ac8c4e15..e04b6d3f3 100644 --- a/test/test_touch_debug_cli.py +++ b/test/test_touch_debug_cli.py @@ -161,7 +161,12 @@ int main() { terminal = extract_braced(mesh, "void MyMesh::handleTerminalCommand(") self.assertIn("handleLocalControlCommand(command, local_reply, sizeof(local_reply))", terminal) help_body = extract_braced(terminal, 'else if (strcmp(command, "help") == 0)') - self.assertIn('get/set display.touch (this boot)\\r\\n', help_body) + self.assertIn('#include "CompanionHelp.inc"', help_body) + self.assertIn('mesh::companion::writePackedCompanionHelp(output);', help_body) + help_fragments = (ROOT / 'examples/companion_radio/CompanionHelp.inc').read_text() + self.assertIn('get/set display.touch (this boot)\\r\\n', help_fragments) + packed_help = (ROOT / 'examples/companion_radio/CompanionHelpPacked.cpp').read_text() + self.assertIn('#include "CompanionHelp.inc"', packed_help) # No persistent preference schema or write is involved. setter = extract_braced(local, 'if (strncmp(command, "set display.touch", 17) == 0') self.assertNotIn("savePrefs", setter) diff --git a/test/test_xiao_identity_recovery.py b/test/test_xiao_identity_recovery.py index ac7d70e6f..c0566f352 100644 --- a/test/test_xiao_identity_recovery.py +++ b/test/test_xiao_identity_recovery.py @@ -6,6 +6,8 @@ import subprocess import tempfile import unittest +from test_sx126x_buffered_radio_hal import MOCK as ARDUINO_HAL_MOCK + ROOT = Path(__file__).resolve().parents[1] IDENTITY = r"""#pragma once @@ -27,7 +29,9 @@ TARGET = r"""#pragma once #include #include #include -int SPI, Wire; +#include +SPIClass SPI; +int Wire; unsigned radio_bytes, prng_bytes, crypto_calls, progress_calls, init_calls, rtc_calls; long random(long minimum, long maximum) { assert(minimum == 0 && maximum == 256); ++prng_bytes; return 0x3c; @@ -41,12 +45,19 @@ public: } }; @RADIO_NOISE_LISTENER@ -struct Module { Module(int, int, int, int, int&) {} }; +struct Module { + ArduinoHal* hal; + Module(ArduinoHal* adapter, int cs, int irq, int reset, int busy) : hal(adapter) { + assert(cs == 4 && irq == 1 && reset == 2 && busy == 3); + assert(hal->spi == &SPI && hal->spiSettings.hz == 8000000); + assert(!hal->initInterface); + } +}; struct FakeRadio : PhysicalLayer { std::unique_ptr module; bool next_result = false; FakeRadio(Module* m) : module(m) {} - bool std_init(int* spi) { + bool std_init(SPIClass* spi) { assert(spi == &SPI); ++init_calls; available = next_result; return next_result; } }; @@ -94,11 +105,15 @@ static void freshIdentity(bool radio_ready) { for (uint8_t value : identity.pub_key) assert(value == expected); } int main() { + auto* hal = TunableRadioHal::find(radio.module->hal); + assert(hal && hal->spi == &SPI && hal->defaultHz() == 8000000); + assert(hal->capabilities().flags == 0); // The default XIAO byte path is retained. freshIdentity(false); // No successful initialization has occurred yet. radio.next_result = true; assert(radio_init()); freshIdentity(true); radio.next_result = false; assert(!radio_init()); freshIdentity(false); radio.next_result = true; assert(radio_init()); freshIdentity(true); assert(init_calls == 3); + assert(radio.module->hal == hal && hal->spi == &SPI); #ifdef DISABLE_I2C_RTC_SCAN assert(rtc_calls == 0); #else @@ -120,13 +135,17 @@ class XiaoIdentityRecoveryTests(unittest.TestCase): with self.subTest(crypto=crypto, rtc_scan=rtc_scan), tempfile.TemporaryDirectory(prefix="xiao-identity-") as temp: work = Path(temp) (work / "helpers").mkdir() + (work / "hal/Arduino").mkdir(parents=True) + (work / "hal/Arduino/ArduinoHal.h").write_text(ARDUINO_HAL_MOCK, encoding="ascii") (work / "Arduino.h").write_text("#pragma once\n", encoding="ascii") (work / "Identity.h").write_text(IDENTITY, encoding="ascii") (work / "helpers/ArduinoHelpers.h").write_text("#pragma once\n", encoding="ascii") (work / "target.h").write_text(TARGET.replace("@RADIO_NOISE_LISTENER@", listener), encoding="ascii") (work / "target.cpp").write_bytes((ROOT / "variants/xiao_nrf52/target.cpp").read_bytes()) + (work / "XiaoRadioSpiConfig.h").write_bytes((ROOT / "variants/xiao_nrf52/XiaoRadioSpiConfig.h").read_bytes()) (work / "check.cpp").write_text(HARNESS, encoding="ascii") - flags = ["-std=c++17", "-Wall", "-Wextra", "-Werror", "-I", str(work), "-I", str(ROOT / "src")] + flags = ["-std=c++17", "-Wall", "-Wextra", "-Werror", "-DNRF52_PLATFORM=1", + "-I", str(work), "-I", str(ROOT / "src")] if os.name != "nt": flags += ["-fsanitize=address,undefined", "-fno-pie", "-no-pie"] if crypto: