Align host fixtures with ESP32 metadata and OTA ownership

This commit is contained in:
mikecarper
2026-10-05 15:01:40 -07:00
parent b4a77c6996
commit 853bf2e1d9
3 changed files with 46 additions and 2 deletions
@@ -9,6 +9,7 @@ import unittest
from test_companion_preferences_transaction import HARNESS as PREFERENCES_HARNESS
from test_companion_preferences_transaction import esp_recovery_helpers
from test_radio_profile_spiffs_missing import STAT_HEADER
from test_replay_reset_integration import extract_braced
ROOT = Path(__file__).resolve().parents[1]
@@ -17,8 +18,22 @@ HARNESS = r'''
#include <helpers/RadioProfileCLI.h>
#include <helpers/TxtDataHelpers.h>
#include <helpers/radiolib/RXPowerSaving.h>
#include <sys/stat.h>
#include <limits>
#define constrain(value,low,high) ((value)<(low)?(low):((value)>(high)?(high):(value)))
// The production SPIFFS metadata syscall sees the same mounted filesystem as
// the File peripheral. The standalone header keeps this fake away from libc.
static MemoryFS* profile_metadata_fs = nullptr;
static bool profile_metadata_error = false;
static unsigned profile_metadata_probes = 0;
int stat(const char* path, struct stat* info) {
assert(profile_metadata_fs && !strncmp(path, "/spiffs/radio_profiles", 22));
++profile_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;
return 0;
}
struct Clock : mesh::RTCClock {
uint32_t getCurrentTime() override { return 1700000000; }
void setCurrentTime(uint32_t) override {}
@@ -55,6 +70,7 @@ struct MyMesh {
_prefs.freq=909.5f; _prefs.bw=62.5f; _prefs.sf=7; _prefs.cr=5;
_prefs.airtime_factor=1; _prefs.rx_ps_level=1; _prefs.rx_ps_preamble=16;
_prefs.rx_ps_rx_us=3000; _prefs.rx_ps_sleep_us=4000;
profile_metadata_fs=&profile_fs;
_radio_profiles.begin(&profile_fs,&radio,&clock);
assert(_radio_profiles.savePrimaryPreamble(48));
assert(savePrefs());
@@ -63,6 +79,7 @@ struct MyMesh {
void bootSanitize() { @COMPANION_SANITIZE@ }
bool handleCommand(const char*,uint32_t,char*);
const char* command(const char* value,uint32_t timestamp=0) {
profile_metadata_fs=&profile_fs;
memset(reply,0,sizeof(reply));
assert(handleCommand(value,timestamp,reply));
assert(memchr(reply,0,sizeof(reply)));
@@ -73,6 +90,7 @@ struct MyMesh {
assert(store.loadPrefs(loaded,lat,lon));
}
uint16_t rebootPreamble() {
profile_metadata_fs=&profile_fs;
Radio reboot_radio;mesh::RadioProfileCLI reboot_cli;
reboot_cli.begin(&profile_fs,&reboot_radio,&clock);
return reboot_cli.primaryPreamble();
@@ -181,6 +199,18 @@ int main() {
assert(error(m.command(command)));assert(m.store.fs.files["/new_prefs"]==prefs);
assert(m._prefs.airtime_factor==1);++checks;
}
{ // Unreadable metadata must protect the existing committed profile image.
MyMesh m;
const auto files=m.profile_fs.files;
Radio reboot_radio;mesh::RadioProfileCLI reboot_cli;
profile_metadata_error=true;
reboot_cli.begin(&m.profile_fs,&reboot_radio,&m.clock);
assert(!reboot_cli.savePrimaryPreamble(32));
assert(m.profile_fs.files==files);
profile_metadata_error=false;
m.expectOriginal();++checks;
}
assert(profile_metadata_probes>0);
printf("%u production primary-radio persistence scenarios passed\n",checks);
}
'''
@@ -228,6 +258,13 @@ def production_primary_radio_harness():
return harness
def prepare_profile_metadata_boundary(work):
"""Supply only the SPIFFS syscall declaration, shared with the app test."""
work = Path(work)
(work / 'sys').mkdir(exist_ok=True)
(work / 'sys/stat.h').write_text(STAT_HEADER, encoding='ascii')
class CompanionPrimaryRadioPersistenceTests(unittest.TestCase):
def test_production_validation_rollback_and_dutycycle_boot(self):
harness = production_primary_radio_harness()
@@ -235,6 +272,7 @@ class CompanionPrimaryRadioPersistenceTests(unittest.TestCase):
self.assertIsNotNone(compiler)
with tempfile.TemporaryDirectory(prefix='companion-primary-radio-') as directory:
work = Path(directory)
prepare_profile_metadata_boundary(work)
# Reuse the profile fixture's identity/crypto mocks and add only
# unused JSON conversion links required by real NodePrefs adapters.
utils = (ROOT/'test/mocks/Utils.h').read_text()
+5 -1
View File
@@ -343,7 +343,11 @@ class CompanionUsbDefaultTest(unittest.TestCase):
self.assertLess(loop.index("serviceUsbLoggingReplyBarrier();"), loop.index("the_mesh.loop();"))
self.assertLess(loop.index("serviceUsbLoggingOwnership(mesh::isUsbLoggingEnabled());"), loop.index("the_mesh.loop();"))
self.assertLess(loop.index("expireUsbBinaryStartupProbeBeforeDispatch();"), loop.index("the_mesh.loop();"))
self.assertNotIn("COMPANION_RADIO_FULL", MAIN)
# USB defaults apply to ordinary and Full builds. Unrelated WiFi OTA
# ownership may legitimately use the Full profile elsewhere in main.
for name in ("beginUsbDefaultSession", "resetUsbTerminalHostSession",
"serviceUsbTerminalHostSessionReset", "serviceUsbAsciiSessionDefault"):
self.assertNotIn("COMPANION_RADIO_FULL", function("static void " + name + "() {"))
source = (ROOT / "examples/companion_radio/MyMesh.cpp").read_text()
self.assertNotIn("mesh::setUsbLoggingEnabled(", source)
logging_reply = source[source.index('if (strncmp(command, "set logging.output ", 19)'):
+3 -1
View File
@@ -19,7 +19,8 @@ import unittest
import urllib.request
from test_client_acl_response import production_acl_methods
from test_companion_primary_radio_persistence import production_primary_radio_harness
from test_companion_primary_radio_persistence import (
prepare_profile_metadata_boundary, production_primary_radio_harness)
from test_companion_delayed_reply_delivery import production_delayed_reply_inputs
from test_replay_reset_integration import extract_braced
@@ -244,6 +245,7 @@ public:
transaction = (ROOT / "src/helpers/ContactFileTransaction.h").read_text()
(work / "ContactFileTransaction.h").write_text(transaction.replace(
'#include "IdentityStore.h"', '#include <helpers/IdentityStore.h>'), encoding="ascii")
prepare_profile_metadata_boundary(work)
(work / "radio.cpp").write_text(harness, encoding="ascii")
binary = work / "radio.exe"
checked([compiler, "-std=c++17", "-Wall", "-Wextra", "-Wno-unused-function",