Files
MeshCore/test/test_nrf52_extrafs_contract.py
T
mikecarper 54a5137032 Recover unreadable contacts without stranding Companions
Verify fallback namespace moves before accepting publication or rollback. Reload readable contact images at a safe host boundary, discard only unreadable contact data after bounded retries, and permit volatile operation when storage cannot recover. Preserve unrelated stores and fail explicit durability requests truthfully.

Keep existing flash, RAM, OTA and capability limits by streaming nRF52 preferences through the validated wire table and sharing identical contact deadline resets. Add fault-injection, sanitizer, legacy-image parity and CLI lifecycle regressions.
2026-10-16 08:28:45 -07:00

1198 lines
55 KiB
Python

#!/usr/bin/env python3
"""Contracts for the nRF52 Companion 100 KiB internal ExtraFS path."""
from pathlib import Path
import re
import subprocess
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[1]
PROFILE = ROOT / "variants" / "t1000-e" / "platformio.ini"
LINKER = ROOT / "boards" / "nrf52840_s140_v7_extrafs.ld"
MAIN = ROOT / "examples" / "companion_radio" / "main.cpp"
STORE = ROOT / "examples" / "companion_radio" / "DataStore.cpp"
STORE_HEADER = ROOT / "examples" / "companion_radio" / "DataStore.h"
MESH = ROOT / "examples" / "companion_radio" / "MyMesh.cpp"
def section(source: str, name: str) -> str:
start = source.index(f"[env:{name}]")
end = source.find("\n[", start + 1)
return source[start:] if end < 0 else source[start:end]
def function_body(source: str, signature: str) -> str:
start = source.index(signature)
opening = source.index("{", start)
depth = 0
for position in range(opening, len(source)):
if source[position] == "{":
depth += 1
elif source[position] == "}":
depth -= 1
if depth == 0:
return source[opening + 1 : position]
raise AssertionError(f"unterminated function: {signature}")
class Nrf52ExtraFsContractTest(unittest.TestCase):
def test_verified_factory_erase_clears_contact_recovery_state(self):
store = STORE.read_text(encoding="utf-8")
body = function_body(store, "bool DataStore::formatFileSystem()")
self.assertEqual(body.count("_contact_recovery = 0;"), 3)
self.assertIn("if (success) _contact_recovery = 0;", body)
rp = body[body.index("const bool success = LittleFS.format();"):]
self.assertIn("if (success) {\n _contact_recovery = 0;", rp)
esp = body[body.index("if (fs_success && nvs_err == ESP_OK)"):]
self.assertTrue(esp.startswith("if (fs_success && nvs_err == ESP_OK) {\n _contact_recovery = 0;"))
def test_uncertain_journal_publication_preserves_storage_authority(self):
store = STORE.read_text(encoding="utf-8")
# Compile the complete production methods, including their platform branches.
def method(signature):
start = store.index(signature)
body = function_body(store, signature)
return store[start:store.index("{", start) + 1] + body + "}"
implementation = "\n".join(method(signature) for signature in (
"static void makeContactPagePath(", "bool DataStore::migrateToSecondaryFS()",
"bool DataStore::saveChannels("))
with tempfile.TemporaryDirectory(prefix="mesh-secondary-publication-") as directory:
directory = Path(directory)
(directory / "migration_under_test.h").write_text(implementation, encoding="ascii")
binary = directory / "migration"
compiled = subprocess.run([
"c++", "-std=c++17", "-O1", "-g", "-Wall", "-Wextra",
"-fsanitize=address,undefined", "-fno-omit-frame-pointer", "-fno-pie", "-no-pie",
"-DNRF52_PLATFORM=1", "-I", str(ROOT / "test/fixtures/contact_cache/mocks"),
"-I", str(ROOT / "test/fixtures/contact_cache"), "-I", str(ROOT / "src"),
"-I", str(directory), str(ROOT / "test/fixtures/contact_cache/test_secondary_migration.cpp"),
"-o", str(binary),
], capture_output=True, text=True)
self.assertEqual(compiled.returncode, 0, compiled.stderr)
result = subprocess.run([str(binary)], capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn("PASS: 10 secondary migration publication scenarios", result.stdout)
def test_hil_stat_failure_is_consumed_before_source_discovery(self):
store = STORE.read_text(encoding="utf-8")
load_contacts = function_body(store, "void DataStore::loadContacts(")
load_pages = function_body(
store, "bool DataStore::loadContactPages(DataStoreHost* host,"
)
self.assertEqual(store.count('"/__hil.readfail"'), 1)
self.assertIn("HIL_CONTACT_STAT_FAILURE_FLAG = 0x80", store)
marker_at = load_contacts.index("hil_failure_marker_exists")
remove_at = load_contacts.index(
"contacts_fs->remove(", marker_at
)
verify_at = load_contacts.index(
"contactPathPresence(", remove_at
)
presence_at = load_contacts.index("uint32_t contact_page_presence")
source_at = load_contacts.index("chooseContactStoreSource(")
self.assertLess(marker_at, remove_at)
self.assertLess(remove_at, verify_at)
self.assertLess(verify_at, presence_at)
self.assertLess(presence_at, source_at)
injected_at = load_contacts.index(
"if (page == hil_stat_failure_page)"
)
incomplete_at = load_contacts.index(
"_contact_load_incomplete = true", injected_at
)
page_probe_at = load_contacts.index(
"contactPathPresence(contacts_fs, path, page_exists)", injected_at
)
self.assertLess(injected_at, incomplete_at)
self.assertLess(incomplete_at, page_probe_at)
self.assertLess(page_probe_at, source_at)
# Low-bit marker values remain handled by the existing post-discovery
# unread-page hook rather than being consumed by the stat hook.
self.assertIn("fail_page == page", load_pages)
self.assertIn("HIL_CONTACT_PAGE_FAILURE_MARKER", load_pages)
def test_contact_source_presence_uses_raw_tristate_lfs_stat(self):
store = STORE.read_text(encoding="utf-8")
load_contacts = function_body(store, "void DataStore::loadContacts(")
prepare = function_body(
store, "bool DataStore::prepareLegacyContactMigration()"
)
truncate = function_body(
store, "bool DataStore::truncateLegacyContacts("
)
service = function_body(
store, "bool DataStore::serviceContactWrites(DataStoreHost* host,"
)
stat = function_body(
store, "static mesh::storage::ContactPathState statContactPath("
)
self.assertIn("fs->_lockFS();", stat)
self.assertIn("lfs_stat(fs->_getFS(), path, &info)", stat)
self.assertIn("fs->_unlockFS();", stat)
self.assertIn("LFS_ERR_NOENT", stat)
source_at = load_contacts.index("chooseContactStoreSource(")
page_stat_at = load_contacts.index(
"contactPathPresence(contacts_fs, path, page_exists)"
)
legacy_stat_at = load_contacts.index(
'contactPathPresence(contacts_fs, "/contacts3", legacy_exists)'
)
marker_stat_at = load_contacts.index(
"contactPathPresence(contacts_fs, CONTACT_MIGRATION_MARKER,"
)
incomplete_at = load_contacts.index(
"_contact_load_incomplete = true", page_stat_at
)
self.assertLess(page_stat_at, incomplete_at)
self.assertLess(incomplete_at, source_at)
self.assertLess(legacy_stat_at, source_at)
self.assertLess(marker_stat_at, source_at)
# ESP32 has a separate SPIFFS recovery path. Check the actual nRF52
# branch so its tri-state discovery cannot regress to exists().
nrf_load = subprocess.run(
["c++", "-E", "-P", "-x", "c++", "-DNRF52_PLATFORM=1",
"-DMESH_CONTACT_CACHE=1", "-"],
input=load_contacts, text=True, capture_output=True, check=True,
).stdout
self.assertNotIn("->exists(", nrf_load)
for body in (prepare, truncate, service):
self.assertIn("contactPathPresence(", body)
self.assertNotIn("->exists(", body)
prepare_marker_at = prepare.index(
"contactPathPresence(fs, CONTACT_MIGRATION_MARKER, marker_exists)"
)
prepare_page_at = prepare.index(
"contactPathPresence(fs, path, path_exists)"
)
prepare_remove_at = prepare.index("fs->remove(path)")
self.assertLess(prepare_marker_at, prepare_page_at)
self.assertLess(prepare_page_at, prepare_remove_at)
truncate_legacy_at = truncate.index(
'contactPathPresence(fs, "/contacts3", legacy_exists)'
)
truncate_remove_at = truncate.index('fs->remove("/contacts3")')
truncate_marker_at = truncate.index(
"contactPathPresence(fs, CONTACT_MIGRATION_MARKER, marker_exists)"
)
truncate_complete_at = truncate.index(
"_legacy_contacts_pending_cleanup = false"
)
self.assertLess(truncate_legacy_at, truncate_remove_at)
self.assertLess(truncate_remove_at, truncate_marker_at)
self.assertLess(truncate_marker_at, truncate_complete_at)
service_legacy_at = service.index(
'contactPathPresence(fs, "/contacts3", legacy_exists)'
)
service_absent_at = service.index("if (!legacy_exists)")
service_marker_at = service.index(
"contactPathPresence(fs, CONTACT_MIGRATION_MARKER,"
)
service_complete_at = service.index(
"_legacy_contacts_pending_cleanup = false"
)
self.assertLess(service_legacy_at, service_absent_at)
self.assertLess(service_absent_at, service_marker_at)
self.assertLess(service_marker_at, service_complete_at)
def test_unread_contact_pages_fail_closed_without_rewrite(self):
store = STORE.read_text(encoding="utf-8")
header = STORE_HEADER.read_text(encoding="utf-8")
mesh = MESH.read_text(encoding="utf-8")
self.assertIn(
"mesh::storage::DirtyPageSet _unread_contact_pages;", header
)
self.assertIn("bool hasIncompleteContactLoad() const;", header)
load = function_body(
store, "bool DataStore::loadContactPages(DataStoreHost* host,"
)
quarantine = function_body(load, "auto quarantineUnreadPage =")
self.assertIn("_contact_slots.reserve(", quarantine)
self.assertIn("_unread_contact_pages.mark(page)", quarantine)
self.assertIn("_contact_load_incomplete = true", quarantine)
self.assertGreaterEqual(load.count("quarantineUnreadPage(page)"), 4)
self.assertIn("_dirty_contact_pages.clearAll()", load)
for signature in (
"bool DataStore::markContactDirty(const ContactInfo& contact)",
"bool DataStore::releaseContact(const ContactInfo& contact)",
"bool DataStore::restoreContactSlot(const ContactInfo& contact,",
):
body = function_body(store, signature)
self.assertIn(
"if (hasIncompleteContactLoad()) return false;", body
)
service = function_body(
store, "bool DataStore::serviceContactWrites(DataStoreHost* host,"
)
unread_at = service.index("_unread_contact_pages.bits()")
write_at = service.index("writeContactPage(host, page", unread_at)
self.assertLess(unread_at, write_at)
pending = function_body(
store, "bool DataStore::hasPendingContactWrites() const"
)
self.assertIn("!hasIncompleteContactLoad()", pending)
incomplete = function_body(
store, "bool DataStore::hasIncompleteContactLoad() const"
)
self.assertIn("_contact_load_incomplete", incomplete)
self.assertIn("_unread_contact_pages.empty()", incomplete)
handler = function_body(mesh, "void MyMesh::handleCmdFrame(")
get_at = handler.index("cmd_frame[0] == CMD_GET_CONTACTS")
next_at = handler.index("cmd_frame[0] == CMD_SET_ADVERT_NAME", get_at)
get_contacts = handler[get_at:next_at]
incomplete_at = get_contacts.index("hasIncompleteContactLoad()")
route_at = get_contacts.index("_iter_reply_route = _serial->captureReplyRoute()")
self.assertLess(incomplete_at, route_at)
self.assertIn("ERR_CODE_FILE_IO_ERROR", get_contacts)
base = (ROOT / "src" / "helpers" / "BaseChatMesh.cpp").read_text(
encoding="utf-8"
)
advert = function_body(base, "void BaseChatMesh::onAdvertRecv(")
peer_data = function_body(base, "void BaseChatMesh::onPeerDataRecv(")
path = function_body(base, "bool BaseChatMesh::onContactPathRecv(")
self.assertIn("if (!canMutateContacts())", advert)
self.assertGreaterEqual(peer_data.count("canMutateContacts()"), 2)
self.assertIn("if (canMutateContacts())", path)
self.assertIn(
"bool MyMesh::canMutateContacts() const", mesh
)
terminal_path = function_body(mesh, "void MyMesh::handleTerminalPath(")
self.assertIn("const ContactInfo previous = recipient", terminal_path)
self.assertIn("if (!recipient.setPath(", terminal_path)
self.assertIn("|| !scheduleContactWrite(recipient)", terminal_path)
self.assertIn("recipient = previous", terminal_path)
terminal = function_body(mesh, "void MyMesh::handleTerminalCommand(")
reset_at = terminal.index('strcmp(command, "reset path")')
next_at = terminal.index('strcmp(command, "card")', reset_at)
reset_path = terminal[reset_at:next_at]
self.assertIn("previous_out_path_len", reset_path)
self.assertIn("if (scheduleContactWrite(*recipient))", reset_path)
self.assertIn(
"recipient->out_path_len = previous_out_path_len", reset_path
)
def test_legacy_and_first_pass_page_load_fail_closed(self):
store = STORE.read_text(encoding="utf-8")
load_contacts = function_body(store, "void DataStore::loadContacts(")
load_pages = function_body(
store, "bool DataStore::loadContactPages(DataStoreHost* host,"
)
self.assertIn("uint32_t contact_page_presence = 0", load_contacts)
self.assertIn("contact_page_presence |= 1UL << page", load_contacts)
self.assertIn(
"loadContactPages(host, 0, contact_page_presence)", load_contacts
)
self.assertIn("expected_page_mask", load_pages)
self.assertIn(
"if ((expected_page_mask & (1UL << page)) == 0) continue;",
load_pages,
)
self.assertNotIn("if (!fs->exists(path)) continue;", load_pages)
open_failure = load_pages.index("if (!file)")
quarantine = load_pages.index("quarantineUnreadPage(page)", open_failure)
self.assertLess(open_failure, quarantine)
legacy_open = load_contacts.index('openRead(_getContactsChannelsFS(), "/contacts3")')
open_failure = load_contacts.index("if (!file)", legacy_open)
open_incomplete = load_contacts.index(
"_contact_load_incomplete = true", open_failure
)
size_check = load_contacts.index(
"isValidLegacyContactFileSize(legacy_size)", open_incomplete
)
read_failure = load_contacts.index("legacy_read_failed = true", size_check)
host_failure = load_contacts.index("legacy_host_refused = true", read_failure)
incomplete_return = load_contacts.index(
"_contact_load_incomplete = true", host_failure
)
migration_prepare = load_contacts.index(
"prepareLegacyContactMigration()", incomplete_return
)
self.assertLess(open_failure, open_incomplete)
self.assertLess(size_check, read_failure)
self.assertLess(read_failure, host_failure)
self.assertLess(incomplete_return, migration_prepare)
self.assertIn("record_index != _legacy_contact_count", load_contacts)
self.assertIn("contact_page_presence = 0", load_contacts)
reset = function_body(store, "void DataStore::resetContactPageState(")
self.assertIn("if (clear_incomplete)", reset)
reset_at = load_contacts.index("resetContactPageState()")
latched_at = load_contacts.index(
"if (_contact_load_incomplete)", reset_at
)
presence_at = load_contacts.index(
"uint32_t contact_page_presence", latched_at
)
self.assertLess(reset_at, latched_at)
self.assertLess(latched_at, presence_at)
service = function_body(
store, "bool DataStore::serviceContactWrites(DataStoreHost* host,"
)
self.assertLess(
service.index("if (hasIncompleteContactLoad())"),
service.index("_legacy_contacts_pending_cleanup"),
)
def test_contact_import_is_rejected_while_load_is_incomplete(self):
mesh = MESH.read_text(encoding="utf-8")
handler = function_body(mesh, "void MyMesh::handleCmdFrame(")
import_at = handler.index("cmd_frame[0] == CMD_IMPORT_CONTACT")
next_at = handler.index("cmd_frame[0] == CMD_SYNC_NEXT_MESSAGE", import_at)
binary_import = handler[import_at:next_at]
gate = binary_import.index("hasIncompleteContactLoad()")
error = binary_import.index("ERR_CODE_FILE_IO_ERROR", gate)
enqueue = binary_import.index("importContact(", error)
ok = binary_import.index("writeOKFrame()", enqueue)
self.assertLess(gate, error)
self.assertLess(error, enqueue)
self.assertLess(enqueue, ok)
terminal_import = function_body(
mesh, "void MyMesh::importTerminalCard("
)
gate = terminal_import.index("hasIncompleteContactLoad()")
retry = terminal_import.index("reboot and retry", gate)
enqueue = terminal_import.index("importContact(", retry)
self.assertLess(gate, retry)
self.assertLess(retry, enqueue)
def test_t1000_usb_and_ble_reserve_exact_100k_region(self):
profile = PROFILE.read_text(encoding="utf-8")
for environment in (
"t1000e_companion_radio_usb",
"t1000e_companion_radio_ble",
):
body = section(profile, environment)
self.assertIn(
"board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld",
body,
)
self.assertIn("board_upload.maximum_size = 708608", body)
linker = LINKER.read_text(encoding="utf-8")
self.assertRegex(
linker,
r"FLASH\s*\(rx\)\s*:\s*ORIGIN\s*=\s*0x27000,\s*"
r"LENGTH\s*=\s*0xD4000\s*-\s*0x27000",
)
main = MAIN.read_text(encoding="utf-8")
geometry = re.search(
r"ResilientInternalExtraFS\s+ExtraFS\s*\(\s*(0x[0-9A-Fa-f]+)\s*,\s*"
r"(0x[0-9A-Fa-f]+)\s*,\s*128\s*\)",
main,
)
self.assertIsNotNone(geometry)
start = int(geometry.group(1), 16)
size = int(geometry.group(2), 16)
self.assertEqual(start, 0xD4000)
self.assertEqual(size, 100 * 1024)
self.assertEqual(start + size, 0xED000)
def test_initial_secondary_mount_defers_repair_until_primary_validation(self):
main = MAIN.read_text(encoding="utf-8")
setup = function_body(main, "void setup()")
self.assertIn("isExpectedInternalExtraFsGeometry(", setup)
self.assertIn("isInternalExtraFsReservedByApplication(", setup)
self.assertIn("uint32_t __flash_arduino_end[]", main)
self.assertIn("(uintptr_t)__flash_arduino_end", setup)
geometry_at = setup.index("isExpectedInternalExtraFsGeometry(")
store_begin_at = setup.index("store.begin()", geometry_at)
secondary_mount = setup[geometry_at:store_begin_at]
self.assertIn("ExtraFS.Adafruit_LittleFS::begin()", secondary_mount)
self.assertNotIn("ExtraFS.begin()", setup)
self.assertNotIn("prepareInternalSecondaryFilesystem(", secondary_mount)
self.assertNotIn("ExtraFS.format()", secondary_mount)
self.assertNotIn("isErasedFlashRange(", secondary_mount)
self.assertIn("store.disableSecondaryFS(true);", secondary_mount)
def test_boot_recovery_runs_only_for_internal_secondary_after_primary_validation(self):
store = STORE.read_text(encoding="utf-8")
begin = function_body(store, "void DataStore::begin()")
primary_failure = function_body(begin, "if (!primary_ready)")
self.assertIn("return;", primary_failure)
self.assertNotIn("recoverInternalExtraFSOnBoot", primary_failure)
self.assertLess(
begin.index("if (!primary_ready)"),
begin.index("recoverInternalExtraFSOnBoot()"),
)
internal = re.search(
r"#if defined\(EXTRAFS\) && !defined\(QSPIFLASH\)(.*?)#else",
begin,
re.DOTALL,
)
self.assertIsNotNone(internal)
self.assertIn("recoverInternalExtraFSOnBoot()", internal.group(1))
self.assertLess(
begin.index("recoverInternalExtraFSOnBoot()"),
begin.index("cleanupAtomicTempFiles(_fs)"),
)
migration_at = begin.index("migrateToSecondaryFS()")
self.assertLess(begin.index("recoverInternalExtraFSOnBoot()"), migration_at)
self.assertNotIn("reinitializeInternalExtraFS", begin[migration_at:])
self.assertNotIn("recoverInternalExtraFSOnBoot", begin[migration_at:])
# QSPI/removable secondary storage retains the non-destructive path.
invalid = re.search(
r"if\s*\(_fsExtra\s*!=\s*nullptr\s*&&\s*"
r"!validateLfsFilesystem\(_fsExtra\)\)\s*\{(.*?)\}",
begin,
re.DOTALL,
)
self.assertIsNotNone(invalid)
self.assertIn("disableSecondaryFS(true);", invalid.group(1))
self.assertNotIn("format", invalid.group(1))
self.assertNotIn("reinitializeInternalExtraFS", invalid.group(1))
def test_boot_recovery_retains_configured_secondary_and_checks_exact_region(self):
store = STORE.read_text(encoding="utf-8")
recover = function_body(store, "bool DataStore::recoverInternalExtraFSOnBoot()")
self.assertIn("_configuredFsExtra == nullptr", recover)
policy_at = recover.index("recoverInternalSecondaryFilesystem(")
geometry_at = recover.index("isExpectedInternalExtraFsGeometry(")
reservation_at = recover.index("isInternalExtraFsReservedByApplication(")
self.assertLess(geometry_at, policy_at)
self.assertLess(reservation_at, policy_at)
self.assertIn("(uintptr_t)__flash_arduino_end", recover)
self.assertIn("extra->getFlashAddr()", recover)
self.assertIn("extra->getFlashSize()", recover)
self.assertIn("extra->getBlockSize()", recover)
self.assertIn("return false;", recover[geometry_at:policy_at])
self.assertNotIn("->format(", recover)
self.assertNotIn("_fs->", recover)
header = STORE_HEADER.read_text(encoding="utf-8")
self.assertIn("FILESYSTEM* _configuredFsExtra;", header)
extra_constructor = re.search(
r"DataStore::DataStore\(FILESYSTEM& fs, FILESYSTEM& fsExtra,.*?\{",
store,
re.DOTALL,
)
self.assertIsNotNone(extra_constructor)
self.assertIn("_configuredFsExtra(&fsExtra)", extra_constructor.group(0))
def test_boot_recovery_retries_nonformatting_mount_before_guarded_reinitialization(self):
store = STORE.read_text(encoding="utf-8")
recover = function_body(store, "bool DataStore::recoverInternalExtraFSOnBoot()")
policy_at = recover.index("recoverInternalSecondaryFilesystem(")
ready_at = recover.index("validateLfsFilesystem(_fsExtra)", policy_at)
unmount_at = recover.index("extra->end()", ready_at)
remount_at = recover.index("extra->Adafruit_LittleFS::begin()", unmount_at)
repair_at = recover.index("reinitializeInternalExtraFS(true)", remount_at)
self.assertLess(ready_at, unmount_at)
self.assertLess(unmount_at, remount_at)
self.assertLess(remount_at, repair_at)
self.assertIn("_fsExtra = _configuredFsExtra", recover[remount_at:repair_at])
self.assertNotIn("extra->begin()", recover)
self.assertNotIn("migrateToSecondaryFS()", recover)
def test_boot_recovery_only_clears_authority_latches_after_verified_success(self):
store = STORE.read_text(encoding="utf-8")
recover = function_body(store, "bool DataStore::recoverInternalExtraFSOnBoot()")
failure_at = recover.index("InternalSecondaryFsRecoveryResult::Failed")
clear_at = recover.rindex("_secondary_authority_unknown = false;")
failure = recover[failure_at:clear_at]
self.assertIn("disableSecondaryFS(true);", failure)
self.assertIn("return false;", failure)
success = recover[clear_at:]
for flag in (
"_secondary_authority_unknown",
"_identity_creation_blocked",
"_contact_load_incomplete",
"_prefs_load_incomplete",
):
self.assertIn(f"{flag} = false;", success)
self.assertNotIn("_primary_storage_unavailable = false", recover)
self.assertIn("return true;", success)
def test_repair_is_internal_only_and_revalidates_before_activation(self):
store = STORE.read_text(encoding="utf-8")
repair = function_body(store, "bool DataStore::repairInternalExtraFS()")
self.assertRegex(
repair,
r"#if defined\(NRF52_PLATFORM\) && defined\(EXTRAFS\) "
r"&& !defined\(QSPIFLASH\)",
)
self.assertIn("reinitializeInternalExtraFS()", repair)
self.assertIn("return migrateToSecondaryFS();", repair)
active_retry = repair[
repair.index("if (_fsExtra != nullptr)") :
repair.index("if (!reinitializeInternalExtraFS())")
]
self.assertIn("return migrateToSecondaryFS();", active_retry)
self.assertNotIn("format()", active_retry)
reinitialize = function_body(
store, "bool DataStore::reinitializeInternalExtraFS(bool scan_physical_pages)"
)
geometry_at = reinitialize.index("isExpectedInternalExtraFsGeometry(")
self.assertIn("isInternalExtraFsReservedByApplication(", reinitialize)
self.assertIn("uint32_t __flash_arduino_end[]", store)
self.assertIn("(uintptr_t)__flash_arduino_end", reinitialize)
unmount_at = reinitialize.index("extra->end()")
format_at = reinitialize.index("extra->format()")
mount_at = reinitialize.index(
"extra->Adafruit_LittleFS::begin()"
)
validate_at = reinitialize.index(
"validateLfsFilesystem(_configuredFsExtra)"
)
activate_at = reinitialize.index("_fsExtra = _configuredFsExtra;")
self.assertLess(geometry_at, unmount_at)
self.assertLess(unmount_at, format_at)
self.assertLess(format_at, mount_at)
self.assertLess(mount_at, validate_at)
self.assertLess(validate_at, activate_at)
def test_migration_failures_block_rebuild_and_can_be_retried(self):
store = STORE.read_text(encoding="utf-8")
header = STORE_HEADER.read_text(encoding="utf-8")
migration = function_body(store, "bool DataStore::migrateToSecondaryFS()")
self.assertIn("bool migrateToSecondaryFS();", header)
self.assertIn('SECONDARY_MIGRATION_JOURNAL = "/.extrafs.mig"', store)
pending_at = migration.index(
"writeJournal(SECONDARY_MIGRATION_PENDING)"
)
copy_at = migration.index("copySecondarySources()", pending_at)
committed_at = migration.index(
"writeJournal(SECONDARY_MIGRATION_COMMITTED)", copy_at
)
retire_at = migration.index("retireSecondarySources()", committed_at)
journal_remove_at = migration.index(
"_fsExtra->remove(SECONDARY_MIGRATION_JOURNAL)", retire_at
)
self.assertLess(pending_at, copy_at)
self.assertLess(copy_at, committed_at)
self.assertLess(committed_at, retire_at)
self.assertLess(retire_at, journal_remove_at)
copy_failure_end = migration.index(
"if (journal_state == MigrationJournalState::Committed)", committed_at
)
self.assertIn(
"_fsExtra = nullptr;", migration[copy_at:copy_failure_end]
)
copy_helper = function_body(migration, "auto copy =")
self.assertNotIn("source_fs->remove(path)", copy_helper)
self.assertIn("return success;", migration)
self.assertIn("compared == expected_size", migration)
mesh = MESH.read_text(encoding="utf-8")
repair = function_body(mesh, "void MyMesh::repairInternalExtraFS(")
quarantine_at = repair.index("_store->hasIncompleteContactLoad()")
migrate_at = repair.index("_store->repairInternalExtraFS()")
reload_gate = repair.index("if (authoritative_reload_required)", migrate_at)
reboot_at = repair.index("_scheduled_reboot_at = futureMillis(1000)", reload_gate)
return_at = repair.index("return;", reboot_at)
contacts_at = repair.index("_store->saveContacts(this, save_filter)")
self.assertLess(quarantine_at, migrate_at)
self.assertLess(migrate_at, return_at)
self.assertLess(reload_gate, reboot_at)
self.assertLess(reboot_at, return_at)
self.assertLess(return_at, contacts_at)
self.assertNotIn("already_ready", repair)
def test_secondary_migration_presence_errors_fail_before_authority_changes(self):
store = STORE.read_text(encoding="utf-8")
migration = function_body(store, "bool DataStore::migrateToSecondaryFS()")
copy_helper = function_body(migration, "auto copy =")
self.assertIn("bool exact_snapshot", migration)
source_stat_at = copy_helper.index(
"contactPathPresence(source_fs, path, source_exists)"
)
destination_stat_at = copy_helper.index(
"contactPathPresence(dest_fs, path, destination_exists)"
)
destination_open_at = copy_helper.index(
"mesh::AtomicFileWriter destination(dest_fs, path)"
)
self.assertLess(source_stat_at, destination_stat_at)
self.assertLess(destination_stat_at, destination_open_at)
self.assertNotIn("->exists(", copy_helper[: copy_helper.index("#else")])
source_absent_at = copy_helper.index("if (!source_exists)")
orphan_remove_at = copy_helper.index("dest_fs->remove(path)", source_absent_at)
verified_absent_at = copy_helper.index(
"contactPathPresence(dest_fs, path, destination_remains)",
orphan_remove_at,
)
self.assertLess(source_absent_at, orphan_remove_at)
self.assertLess(orphan_remove_at, verified_absent_at)
conflict_at = copy_helper.index("if (!exact_snapshot)", verified_absent_at)
self.assertLess(conflict_at, destination_open_at)
primary_helper = function_body(
migration, "auto migratePrimarySources ="
)
self.assertIn(
"contactPathPresence(_fsExtra, path, secondary_exists)",
primary_helper,
)
self.assertIn(
"contactPathPresence(_fs, path, primary_exists)", primary_helper
)
self.assertNotIn(
"->exists(", primary_helper[: primary_helper.index("#else")]
)
read_journal = function_body(migration, "auto readJournal =")
journal_stat_at = read_journal.index(
"contactPathPresence(_fsExtra, SECONDARY_MIGRATION_JOURNAL,"
)
journal_open_at = read_journal.index(
"openRead(_fsExtra, SECONDARY_MIGRATION_JOURNAL)"
)
self.assertLess(journal_stat_at, journal_open_at)
self.assertIn("return MigrationJournalState::IoError;", read_journal)
self.assertNotIn("->exists(", read_journal)
source_scan = function_body(migration, "auto hasSecondarySources =")
self.assertGreaterEqual(source_scan.count("contactPathPresence("), 3)
self.assertIn("present = false;", source_scan)
self.assertIn("return false;", source_scan)
self.assertNotIn("->exists(", source_scan)
copy_sources = function_body(migration, "auto copySecondarySources =")
self.assertEqual(copy_sources.count(", true)"), 3)
pending_at = migration.index(
"writeJournal(SECONDARY_MIGRATION_PENDING)"
)
scan_at = migration.index(
"!hasSecondarySources(secondary_sources_present)"
)
self.assertLess(scan_at, pending_at)
scan_failure_end = migration.index(
"if (journal_state != MigrationJournalState::Committed", scan_at
)
scan_failure_branch = migration[scan_at:scan_failure_end]
self.assertIn("_contact_load_incomplete = true;", scan_failure_branch)
self.assertIn("return false;", scan_failure_branch)
self.assertNotIn("_fsExtra = nullptr;", scan_failure_branch)
journal_io_at = migration.index(
"journal_state == MigrationJournalState::IoError"
)
journal_io_end = migration.index(
"if (journal_state == MigrationJournalState::Invalid)",
journal_io_at,
)
journal_io_branch = migration[journal_io_at:journal_io_end]
self.assertIn("_contact_load_incomplete = true;", journal_io_branch)
self.assertIn("return false;", journal_io_branch)
self.assertNotIn("_fsExtra = nullptr;", journal_io_branch)
primary_failure_at = migration.index("if (!migratePrimarySources())")
self.assertLess(
migration.index("MigrationJournalState journal_state = readJournal()"),
primary_failure_at,
)
self.assertLess(primary_failure_at, journal_io_at)
primary_failure_branch = migration[primary_failure_at:journal_io_at]
self.assertIn("_contact_load_incomplete = true;", primary_failure_branch)
self.assertIn("return false;", primary_failure_branch)
self.assertNotIn("_fsExtra = nullptr;", primary_failure_branch)
invalid_at = migration.index(
"journal_state == MigrationJournalState::Invalid"
)
invalid_end = migration.index(
"bool secondary_sources_present", invalid_at
)
invalid_branch = migration[invalid_at:invalid_end]
self.assertIn("_contact_load_incomplete = true;", invalid_branch)
self.assertIn("return false;", invalid_branch)
self.assertNotIn("_fsExtra = nullptr;", invalid_branch)
retire_helper = function_body(
migration, "auto retireSecondarySources ="
)
last_stat_at = retire_helper.rindex("contactPathPresence(")
first_remove_at = retire_helper.index("_fs->remove(path)")
self.assertLess(last_stat_at, first_remove_at)
self.assertIn("uint32_t page_presence = 0;", retire_helper)
self.assertIn("uint16_t bucket_presence = 0;", retire_helper)
self.assertNotIn("->exists(", retire_helper)
committed_at = migration.index(
"writeJournal(SECONDARY_MIGRATION_COMMITTED)"
)
retire_at = migration.index("retireSecondarySources()", committed_at)
journal_remove_at = migration.index(
"_fsExtra->remove(SECONDARY_MIGRATION_JOURNAL)", retire_at
)
self.assertLess(committed_at, retire_at)
self.assertLess(retire_at, journal_remove_at)
self.assertNotIn(
"_fsExtra->exists(SECONDARY_MIGRATION_JOURNAL)", migration
)
load_channels = function_body(
store, "void DataStore::loadChannels(DataStoreHost* host)"
)
save_channels = function_body(
store, "bool DataStore::saveChannels(DataStoreHost* host)"
)
self.assertIn("bool& incomplete = _contact_load_incomplete;", load_channels)
self.assertLess(
load_channels.index("if (incomplete) return;"),
load_channels.index('openRead(contacts_fs, "/channels2")'),
)
self.assertLess(
save_channels.index("if (_contact_load_incomplete) return false;"),
save_channels.index("mesh::AtomicFileWriter file("),
)
def test_legacy_primary_files_move_before_contact_journal_can_fall_back(self):
store = STORE.read_text(encoding="utf-8")
migration = function_body(store, "bool DataStore::migrateToSecondaryFS()")
# Older ExtraFS test builds could leave identity and preferences only
# on the secondary filesystem. Read the journal without changing
# authority, then recover these files before acting on that result.
self.assertIn(
'static const char* to_primary[] = {"/_main.id", "/new_prefs"};',
migration,
)
primary_move_at = migration.index("if (!migratePrimarySources())")
journal_flow_at = migration.index(
"MigrationJournalState journal_state = readJournal()"
)
journal_result_at = migration.index(
"journal_state == MigrationJournalState::IoError",
journal_flow_at,
)
self.assertLess(journal_flow_at, primary_move_at)
self.assertLess(primary_move_at, journal_result_at)
# A primary-present conflict is nonfatal: primary is canonical and the
# differing secondary copy is preserved for recovery. Only failure to
# create a missing primary copy may block the handoff.
copy_helper = function_body(migration, "auto copy =")
primary_helper = function_body(
migration, "auto migratePrimarySources ="
)
self.assertNotIn("source_fs->remove(path)", copy_helper)
self.assertLess(
copy_helper.rindex("filesEqual(source_fs, dest_fs, path)"),
copy_helper.rindex("return true;"),
)
conflict_at = primary_helper.index(
"if (!filesEqual(_fsExtra, _fs, path))"
)
missing_copy_at = primary_helper.index(
"bool recovered_primary = copy(_fsExtra, _fs, path, false)"
)
remove_at = primary_helper.index("_fsExtra->remove(path)")
self.assertLess(conflict_at, missing_copy_at)
self.assertLess(missing_copy_at, remove_at)
conflict_branch = primary_helper[
conflict_at:missing_copy_at
]
self.assertIn("continue;", conflict_branch)
self.assertNotIn("success = false", conflict_branch)
missing_copy_branch = primary_helper[
missing_copy_at:remove_at
]
self.assertIn("success = false;", missing_copy_branch)
retire_branch = primary_helper[remove_at:]
self.assertNotIn("success = false;", retire_branch)
self.assertIn(
"preserving conflicting legacy secondary", primary_helper
)
def test_identity_creation_requires_proven_absence(self):
store = STORE.read_text(encoding="utf-8")
header = STORE_HEADER.read_text(encoding="utf-8")
mesh = MESH.read_text(encoding="utf-8")
self.assertIn("bool _identity_creation_blocked = false;", header)
self.assertIn("bool _primary_storage_unavailable = false;", header)
self.assertIn("bool _secondary_authority_unknown = false;", header)
self.assertIn("bool canCreateMainIdentity() const;", header)
self.assertIn(
"void disableSecondaryFS(bool authority_unknown = true);", header
)
self.assertIn("void markPrimaryFSUnavailable();", header)
load = function_body(
store,
"bool DataStore::loadMainIdentity(mesh::LocalIdentity &identity)",
)
raw_stat = load.index(
'contactPathPresence(_fs, "/_main.id", identity_exists)'
)
proven_missing = load.index("if (!identity_exists) return false;", raw_stat)
decode = load.index('identity_store.load("_main", identity)', proven_missing)
self.assertLess(raw_stat, proven_missing)
self.assertLess(proven_missing, decode)
self.assertIn(
"_identity_creation_blocked = true;",
load[raw_stat:proven_missing],
)
self.assertIn(
"_identity_creation_blocked = true;",
load[decode:],
)
self.assertIn("_identity_creation_blocked = false;", load[decode:])
save = function_body(
store,
"bool DataStore::saveMainIdentity(const mesh::LocalIdentity &identity)",
)
self.assertLess(
save.index("if (_identity_creation_blocked) return false;"),
save.index('identity_store.save("_main", identity)'),
)
migration = function_body(
store, "bool DataStore::migrateToSecondaryFS()"
)
primary_helper = function_body(
migration, "auto migratePrimarySources ="
)
self.assertIn(
'const bool is_identity = strcmp(path, "/_main.id") == 0;',
primary_helper,
)
self.assertGreaterEqual(
primary_helper.count(
"if (is_identity) _identity_creation_blocked = true;"
),
3,
)
recovered = primary_helper.index(
"bool recovered_primary = copy(_fsExtra, _fs, path, false)"
)
verify_destination = primary_helper.index(
"contactPathPresence(_fs, path, recovered_primary_exists)",
recovered,
)
reject_missing = primary_helper.index(
"if (!recovered_primary)", verify_destination
)
self.assertLess(recovered, verify_destination)
self.assertLess(verify_destination, reject_missing)
store_begin = function_body(store, "void DataStore::begin()")
reset_at = store_begin.index("resetContactPageState(true)")
unmounted_at = store_begin.index("if (_primary_storage_unavailable)")
primary_validate = store_begin.index("validateLfsFilesystem(_fs)")
primary_failure = store_begin.index("if (!primary_ready)", primary_validate)
secondary_validate = store_begin.index(
"validateLfsFilesystem(_fsExtra)", primary_failure
)
primary_failure_branch = store_begin[primary_failure:secondary_validate]
self.assertLess(reset_at, primary_validate)
self.assertLess(unmounted_at, primary_validate)
unmounted_branch = store_begin[unmounted_at:primary_validate]
self.assertIn("_identity_creation_blocked = true;", unmounted_branch)
self.assertIn("_contact_load_incomplete = true;", unmounted_branch)
self.assertIn("return;", unmounted_branch)
self.assertGreaterEqual(primary_failure_branch.count("delay(3000);"), 2)
self.assertIn("_fs->format()", primary_failure_branch)
self.assertIn("createRamFallbackFileSystem()", primary_failure_branch)
self.assertIn("useVolatilePrimaryFS", primary_failure_branch)
self.assertNotIn("recoverPrimaryFilesystem", store_begin)
self.assertIn("IdentityLoadResult::Unreadable", store_begin)
secondary_failure = store_begin.index(
"if (_fsExtra != nullptr && !validateLfsFilesystem(_fsExtra))"
)
self.assertIn(
"disableSecondaryFS(true);",
store_begin[secondary_failure:],
)
disable = function_body(
store, "void DataStore::disableSecondaryFS(bool authority_unknown)"
)
self.assertIn("_secondary_authority_unknown = true;", disable)
self.assertIn("_contact_load_incomplete = true;", disable)
self.assertIn("_identity_creation_blocked = true;", disable)
unavailable = function_body(
store, "void DataStore::markPrimaryFSUnavailable()"
)
self.assertIn("_primary_storage_unavailable = true;", unavailable)
self.assertIn("_contact_load_incomplete = true;", unavailable)
self.assertIn("_identity_creation_blocked = true;", unavailable)
setup = function_body(MAIN.read_text(encoding="utf-8"), "void setup()")
main = MAIN.read_text(encoding="utf-8")
primary_helper = (
ROOT / "src/helpers/nrf52/InternalPrimaryFsBoot.h"
).read_text(encoding="utf-8")
self.assertIn(
"static const uint32_t INTERNAL_PRIMARY_FS_START = 0xED000UL;",
primary_helper,
)
self.assertIn(
"static const uint32_t INTERNAL_PRIMARY_FS_START = 0x6D000UL;",
primary_helper,
)
self.assertIn("7UL * FLASH_NRF52_PAGE_SIZE", primary_helper)
primary_policy = setup.index(
"InternalSecondaryFsBootResult primary_fs_boot"
)
fallback = setup.index("createRamFallbackFileSystem()", primary_policy)
volatile_store = setup.index("store.useVolatilePrimaryFS", fallback)
store_begin = setup.index("store.begin()", volatile_store)
self.assertLess(primary_policy, fallback)
self.assertLess(fallback, volatile_store)
self.assertLess(volatile_store, store_begin)
self.assertIn("beginInternalPrimaryFilesystemSafely", setup)
self.assertGreaterEqual(primary_helper.count("delay(3000);"), 2)
self.assertIn("fs.format()", primary_helper)
self.assertIn("ReinitializedUnreadable", primary_helper)
# Never call the core override in the nRF path: it auto-erases a
# nonblank filesystem after any mount failure. The sole occurrence is
# the separately scoped STM32 fallback.
self.assertEqual(setup.count("InternalFS.begin();"), 1)
self.assertGreater(setup.index("#else\n InternalFS.begin();", primary_policy), fallback)
self.assertIn("store.disableSecondaryFS(true);", setup)
secondary_geometry = setup.index(
"const bool extra_fs_geometry_valid ="
)
secondary_disable = setup.index(
"store.disableSecondaryFS(true);", secondary_geometry
)
secondary_guard = setup[secondary_geometry:secondary_disable]
self.assertIn("if (!extra_fs_geometry_valid", secondary_guard)
self.assertIn("!ExtraFS.Adafruit_LittleFS::begin()", secondary_guard)
self.assertNotIn("ExtraFS.format()", secondary_guard)
self.assertNotIn("isErasedFlashRange(", secondary_guard)
begin = function_body(mesh, "void MyMesh::begin(")
load_at = begin.index("const bool identity_loaded =")
creation_gate = begin.index(
"is_new_install && !_store->canCreateMainIdentity()", load_at
)
generate_at = begin.index("mesh::generateUsableLocalIdentity", creation_gate)
save_at = begin.index(
"identity_ready = _store->saveMainIdentity(self_id)", generate_at
)
self.assertLess(load_at, creation_gate)
self.assertLess(creation_gate, generate_at)
self.assertLess(generate_at, save_at)
blocked_branch = begin[creation_gate:generate_at]
self.assertIn("board.reboot();", blocked_branch)
self.assertIn("return;", blocked_branch)
def test_local_rescue_repair_rebuilds_live_contacts_and_channels(self):
mesh = MESH.read_text(encoding="utf-8")
repair = function_body(mesh, "void MyMesh::repairInternalExtraFS(")
self.assertIn("_store->repairInternalExtraFS()", repair)
self.assertIn("_store->saveContacts(this, save_filter)", repair)
self.assertIn("const bool channels_saved = saveChannels();", repair)
self.assertIn("capacity_kib >= 84 && capacity_kib <= 100", repair)
self.assertIn("getStorageTotalKb()", repair)
rescue = function_body(mesh, "void MyMesh::checkCLIRescueCmd()")
command_at = rescue.index('strcmp(cli_command, "repair extrafs")')
rebuild_at = rescue.index('strcmp(cli_command, "rebuild")')
repair_path = rescue[command_at:rebuild_at]
self.assertIn("repairInternalExtraFS(output);", repair_path)
terminal = function_body(mesh, "void MyMesh::handleTerminalCommand(")
self.assertIn('strcmp(command, "repair extrafs")', terminal)
self.assertIn("repairInternalExtraFS(terminalOutput());", terminal)
self.assertIn(
"repair extrafs (erases/rebuilds internal ExtraFS)", terminal
)
def test_rescue_rebuild_reports_success_only_after_every_save_succeeds(self):
mesh = MESH.read_text(encoding="utf-8")
rescue = function_body(mesh, "void MyMesh::checkCLIRescueCmd()")
rebuild_at = rescue.index('strcmp(cli_command, "rebuild")')
erase_at = rescue.index('strcmp(cli_command, "erase")', rebuild_at)
rebuild = rescue[rebuild_at:erase_at]
expected_saves = (
("identity_saved", "_store->saveMainIdentity(self_id)"),
("prefs_saved", "savePrefs()"),
("contacts_saved", "_store->saveContacts(this, save_filter)"),
("channels_saved", "saveChannels()"),
)
for result, call in expected_saves:
self.assertRegex(
rebuild,
rf"(?:const\s+)?bool\s+{result}\s*=\s*"
rf"{re.escape(call)}\s*;",
)
# A failed or still-pending contact rebuild must retain the lazy-write
# retry path instead of claiming that the destructive rebuild is done.
self.assertRegex(
rebuild,
r"(?:const\s+)?bool\s+contacts_complete\s*=\s*"
r"contacts_saved\s*&&\s*"
r"!_store->hasPendingContactWrites\(\)\s*;",
)
self.assertIsNotNone(
re.search(
r"if\s*\(\s*contacts_complete\s*\)\s*\{.*?\}\s*"
r"else\s*\{\s*scheduleContactWriteRetry\(\)\s*;",
rebuild,
re.DOTALL,
)
)
success_text = 'output.println(" > erase and rebuild done")'
success_at = rebuild.index(success_text)
success_prefix = rebuild[:success_at]
guard_at = success_prefix.rfind("if (")
brace_at = success_prefix.rfind("{")
self.assertGreaterEqual(guard_at, 0)
self.assertGreater(brace_at, guard_at)
success_guard = success_prefix[guard_at:brace_at]
for result in ("identity_saved", "prefs_saved", "channels_saved"):
self.assertIn(result, success_guard)
self.assertIn("contacts_complete", success_guard)
self.assertNotIn("||", success_guard)
# The positive message must follow all save attempts; the branch must
# also retain an explicit error response for a partial rebuild.
for _, call in expected_saves:
self.assertLess(rebuild.index(call), success_at)
self.assertIn("Error:", rebuild[success_at:])
def test_channel_update_rolls_back_when_atomic_save_fails(self):
mesh = MESH.read_text(encoding="utf-8")
# Both wire encodings must snapshot the raw persisted representation.
# getChannel() resolves aliases into effective wire keys/TX routing;
# using it for rollback would corrupt a prefixed channel after failure.
# Exact-byte rollback is also executed by the native channel-alias and
# Meshtext command fixtures, not merely checked as source ordering.
for key_bytes in (16, 32):
with self.subTest(key_bytes=key_bytes):
channel_update = function_body(mesh,
f"if (cmd_frame[0] == CMD_SET_CHANNEL && len >= 2 + 32 + {key_bytes})")
snapshot_at = channel_update.index(
"getChannelForSave(channel_idx, previous)"
)
self.assertNotIn("getChannel(channel_idx, previous)", channel_update)
mutate_at = channel_update.index(
"setChannel(channel_idx, channel)"
)
save_at = channel_update.index("saveChannels()")
rollback_at = channel_update.index(
"setChannel(channel_idx, previous)", save_at
)
error_at = channel_update.index(
"writeErrFrame(ERR_CODE_FILE_IO_ERROR)", rollback_at
)
ok_at = channel_update.index("writeOKFrame()")
self.assertLess(snapshot_at, mutate_at)
self.assertLess(mutate_at, save_at)
self.assertLess(save_at, ok_at)
self.assertLess(save_at, rollback_at)
self.assertLess(rollback_at, error_at)
self.assertEqual(channel_update.count("writeOKFrame()"), 1)
def test_absent_advert_delete_avoids_bucket_rewrite_without_legacy_cache(self):
store = STORE.read_text(encoding="utf-8")
body = function_body(store, "bool DataStore::deleteBlobByKey(")
fast_path = body.index(
"if (!legacy_exists && bucket_valid"
)
save = body.index("saveBlobBucket(")
self.assertLess(fast_path, save)
self.assertIn(
"&& (!found || records[selected].len == 0)",
body[fast_path:save],
)
self.assertIn("free(records);", body[fast_path:save])
self.assertIn("return true;", body[fast_path:save])
def test_nrf_advert_tombstones_never_fall_back_to_legacy_cache(self):
store = STORE.read_text(encoding="utf-8")
read = function_body(store, "uint8_t DataStore::getBlobByKey(")
bucket_at = read.index("findBlobInBucket(")
nrf_return_at = read.index("return 0;", bucket_at)
legacy_at = read.index(
'File file = openRead(_getContactsChannelsFS(), "/adv_blobs")'
)
self.assertLess(nrf_return_at, legacy_at)
between = read[nrf_return_at:legacy_at]
self.assertIn("#endif", between)
self.assertIn("#if !defined(NRF52_PLATFORM)", between)
put = function_body(store, "bool DataStore::putBlobByKey(")
cleanup_at = put.index("clearLegacyBlobRecord(")
self.assertNotIn(
"return false;", put[cleanup_at:put.index("#else", cleanup_at)]
)
delete = function_body(store, "bool DataStore::deleteBlobByKey(")
cleanup_at = delete.index("clearLegacyBlobRecord(")
self.assertNotIn(
"return false;",
delete[cleanup_at:delete.index("#else", cleanup_at)],
)
def test_quarantined_extrafs_rescue_file_commands_are_null_safe(self):
mesh = MESH.read_text(encoding="utf-8")
rescue = function_body(mesh, "void MyMesh::checkCLIRescueCmd()")
unavailable = (
'ExtraFS is unavailable; run repair extrafs first'
)
cat_start = rescue.index('memcmp(cli_command, "cat", 3)')
rm_start = rescue.index('memcmp(cli_command, "rm ", 3)', cat_start)
reboot_start = rescue.index(
'strcmp(cli_command, "reboot")', rm_start
)
cat_path = rescue[cat_start:rm_start]
rm_path = rescue[rm_start:reboot_start]
for command_path in (cat_path, rm_path):
secondary_at = command_path.index(
"_store->getSecondaryFS()"
)
null_guard_at = command_path.index(
"if (selected_fs == nullptr)", secondary_at
)
access_at = min(
position
for operation in (
"_store->openRead(selected_fs, path)",
"_store->removeFile(selected_fs, path)",
)
if (position := command_path.find(operation)) >= 0
)
self.assertLess(null_guard_at, access_at)
self.assertIn(unavailable, command_path)
self.assertIn("uint8_t buffer[64];", cat_path)
self.assertNotRegex(cat_path, r"uint8_t\s+buffer\s*\[\s*file_size\s*\]")
if __name__ == "__main__":
unittest.main()