Files
MeshCore/test/test_contact_cache.py
T
mikecarper 4f114e914b Keep ESP contact saves responsive on PSRAM boards
Extend the cooperative contact transaction to uncached ESP32 storage,
including the PSRAM policy used by the XIAO and V4. Preserve owned contact
copies, the 152-byte disk format, revision cancellation and synchronous
durability operations. Keep contact capacities unchanged.

Avoid SDK error logs when optional blob or transaction cleanup files are
absent, while preserving metadata and deletion failure handling.

Validate cached ESP/nRF and uncached PSRAM paths under ASan/UBSan,
including bounded writes, mutation cancellation, faults, recovery and
reboot readback. Update the existing uncached production fixture.
2026-10-04 04:01:40 -07:00

127 lines
6.5 KiB
Python

#!/usr/bin/env python3
"""Exercise real contact handles, cache policy and persistence under sanitizers."""
from pathlib import Path
import subprocess
import tempfile
import unittest
from test_t096_full_memory import method
ROOT = Path(__file__).resolve().parents[1]
def crypto_objects(temp):
objects = []
for name in ("fe", "ge", "sc", "sha512", "keypair", "key_exchange"):
obj = temp / (name + ".o")
subprocess.run([
"cc", "-O1", "-g", "-fsanitize=address", "-fwrapv", "-c",
str(ROOT / "lib/ed25519" / (name + ".c")), "-o", str(obj),
], capture_output=True, text=True, check=True)
objects.append(str(obj))
return objects
class ContactCacheTest(unittest.TestCase):
def test_esp32_misses_recalculate_without_flash_io(self):
with tempfile.TemporaryDirectory(prefix="mesh-secret-power-") as temp:
temp = Path(temp)
binary = temp / "test"
result = subprocess.run([
"c++", "-std=c++17", "-O1", "-g", "-fsanitize=address,undefined",
"-DESP32_PLATFORM=1", "-DCOMPANION_RADIO_FULL=1",
"-I", str(ROOT / "test/fixtures/contact_cache/mocks"),
"-I", str(ROOT / "lib/ed25519"), "-I", str(ROOT / "src"),
str(ROOT / "test/fixtures/contact_cache/test_secret_power.cpp"),
*crypto_objects(temp), "-lcrypto", "-o", str(binary),
], capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stderr)
result = subprocess.run([str(binary)], capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
def test_eviction_snapshots_transactions_and_identity_changes(self):
self.run_cache_test("ESP32_PLATFORM")
def test_nrf52_paged_store_migration_and_snapshots(self):
self.run_cache_test("NRF52_PLATFORM")
def run_cache_test(self, platform):
with tempfile.TemporaryDirectory(prefix="mesh-contact-cache-") as temp:
temp = Path(temp)
store = (ROOT / "examples/companion_radio/DataStore.cpp").read_text()
functions = [
"static bool serializeContactRecord(",
"static bool deserializeContactRecord(",
"void DataStore::loadContacts(",
"bool DataStore::saveContacts(",
"bool DataStore::flushContactWrites(",
"bool DataStore::readStoredPath(",
"bool DataStore::flushCachedPaths(",
"uint16_t DataStore::secretSlot(",
"bool DataStore::readSavedSecret(",
"bool DataStore::saveSecret(",
]
helper_start = store.index("namespace {\nbool cachedContactFilter")
helper_end = store.index("bool DataStore::readStoredPath(", helper_start)
implementation = store[helper_start:helper_end]
header = (ROOT / "examples/companion_radio/DataStore.h").read_text()
state_start = header.index(" mesh::ContactFileTransaction* _contact_write")
state_end = header.index("\n#endif", state_start)
(temp / "contact_write_state_under_test.h").write_text(
header[state_start:state_end])
functions.append("DataStore::~DataStore()")
if platform == "ESP32_PLATFORM":
functions.extend((
"void DataStore::cancelContactWrite(",
"bool DataStore::cancelCooperativeWrite(",
"bool DataStore::serviceContactWrite(",
"bool DataStore::markContactDirty(",
"bool DataStore::releaseContact(",
"bool DataStore::serviceContactWrites(",
"bool DataStore::hasPendingContactWrites() const",
"void DataStore::begin()",
"void DataStore::disableSecondaryFS(",
))
if platform == "NRF52_PLATFORM":
implementation += method(store, "static void makeContactPagePath(") + "\n"
implementation += method(store, "static void discardInvalidContactPage(") + "\n"
functions.append("bool DataStore::writeContactPage(")
functions.append("bool DataStore::loadContactPages(")
implementation += "\n".join(method(store, signature) for signature in functions)
(temp / "store_under_test.h").write_text(implementation)
packet = (ROOT / "src/Packet.cpp").read_text()
(temp / "packet_under_test.h").write_text("namespace mesh {\n" + "\n".join(
method(packet, signature) for signature in (
"Packet::Packet()", "int Packet::getRawLength() const",
"bool Packet::isValidPathLen(", "size_t Packet::writePath(",
"uint8_t Packet::copyPath(")) + "\n}\n")
chat = (ROOT / "src/helpers/BaseChatMesh.cpp").read_text()
mesh = (ROOT / "src/Mesh.cpp").read_text()
(temp / "send_under_test.h").write_text(
"namespace mesh {\n" + method(mesh, "bool Mesh::sendDirect(") + "\n}\n"
+ "\n".join(method(chat, signature) for signature in (
"int BaseChatMesh::sendLogin(", "int BaseChatMesh::sendAnonReq(",
"int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t*",
"int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t"))
)
binary = temp / "test"
result = subprocess.run([
"c++", "-std=c++17", "-O1", "-g", "-Wall", "-Wextra",
"-fsanitize=address,undefined", "-fno-omit-frame-pointer",
"-DMESH_CONTACT_CACHE=1", "-DCOMPANION_RADIO_FULL=1",
"-DMESH_CONTACT_SECRET_FLASH_CACHE=1",
"-D" + platform + "=1", "-DMAX_CONTACTS=350",
"-I", str(ROOT / "test/fixtures/contact_cache/mocks"),
"-I", str(ROOT / "lib/ed25519"),
"-I", str(ROOT / "src"), "-I", str(temp),
str(ROOT / "test/fixtures/contact_cache/test_contact_cache.cpp"),
str(ROOT / "src/helpers/ContactInfo.cpp"), *crypto_objects(temp),
"-lcrypto", "-o", str(binary),
], capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stderr)
result = subprocess.run([str(binary)], capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
if __name__ == "__main__":
unittest.main()