Repair stale CI fixtures for shared help and radio controls

Allow saved radio-fast metadata in the persistence harness, follow shared raw and packed CLI help, refresh reviewed help snapshots, and update HAL, build-hook and SD helper mocks without changing firmware behavior.
This commit is contained in:
mikecarper committed 2026-10-23 13:56:39 -07:00
1 parent 837847f891
commit a032d53004
15 files changed
+199 -90

No files matched your search

+69 -67
View File
@@ -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"
}
+1
View File
@@ -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
+23 -1
View File
@@ -19,6 +19,22 @@ HARNESS = r'''
#include <string>
#include <vector>
#include <Identity.h>
#include <helpers/CompanionPowerDiagnostics.h>
// 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);',
@@ -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);
}
'''
+11 -5
View File
@@ -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 <!8hex|default> (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 <channel slot> (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`
+4 -2
View File
@@ -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)
+10 -3
View File
@@ -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 <wifi|ble>\\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
+10 -1
View File
@@ -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")
+4
View File
@@ -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")))
+10 -3
View File
@@ -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
+1
View File
@@ -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"])
+5 -2
View File
@@ -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()
+1
View File
@@ -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 (
+6 -1
View File
@@ -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 <on|off> (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 <on|off> (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)
+23 -4
View File
@@ -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 <cassert>
#include <memory>
#include <stdexcept>
int SPI, Wire;
#include <hal/Arduino/ArduinoHal.h>
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> 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: