148 lines
6.8 KiB
Python
148 lines
6.8 KiB
Python
#!/usr/bin/env python3
|
|
"""MediaMesh scope policy through the real CommonCLI and RegionMap methods."""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
from cpp_source import body
|
|
from test_region_map_transactions import region_implementation
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
FIXTURE = ROOT / "test/fixtures/mediamesh_scope_integration.cpp"
|
|
REGION_FIXTURE = ROOT / "test/fixtures/region_map_transactions"
|
|
|
|
|
|
def implementation():
|
|
region = (ROOT / "src/helpers/RegionMap.cpp").read_text(encoding="ascii")
|
|
generated = region_implementation()
|
|
for signature in ("int RegionMap::getTransportKeysFor(",
|
|
"RegionEntry* RegionMap::findMatch("):
|
|
generated += "\n" + body(region, signature)
|
|
common = (ROOT / "src/helpers/CommonCLI.cpp").read_text(encoding="ascii")
|
|
generated += "\n" + body(common, "bool CommonCLI::mediaMeshRegionStoreMissing(")
|
|
generated += "\n" + body(common, "bool CommonCLI::updateMediaMeshScope(")
|
|
generated += "\n" + body(common, "void CommonCLI::onRegionsLoaded(")
|
|
generated += "\n" + body(common, "void CommonCLI::recoverMediaMeshRegions(")
|
|
repeater = (ROOT / "examples/simple_repeater/MyMesh.cpp").read_text(encoding="ascii")
|
|
generated += "\n" + body(repeater, "void MyMesh::onDefaultRegionChanged(")
|
|
packet = (ROOT / "src/Packet.cpp").read_text(encoding="ascii")
|
|
generated += "\nnamespace mesh {\n" + body(packet, "Packet::Packet(") + "\n}\n"
|
|
return generated
|
|
|
|
|
|
class MediaMeshScopeIntegrationTest(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
compiler = shutil.which("g++") or shutil.which("clang++")
|
|
if compiler is None:
|
|
raise AssertionError("a native C++ compiler is required")
|
|
cls.temporary = tempfile.TemporaryDirectory(prefix="meshcore-mediamesh-scope-")
|
|
cls.work = Path(cls.temporary.name)
|
|
(cls.work / "scope_under_test.inc").write_text(implementation(), encoding="ascii")
|
|
(cls.work / "contact_transaction_under_test.h").write_text(
|
|
(ROOT / "src/helpers/ContactFileTransaction.h").read_text(encoding="ascii"),
|
|
encoding="ascii")
|
|
(cls.work / "IdentityStore.h").write_text(
|
|
'#include <helpers/IdentityStore.h>\n', encoding="ascii")
|
|
(cls.work / "PersistentStoreFormat.h").write_text(
|
|
'#include <helpers/PersistentStoreFormat.h>\n', encoding="ascii")
|
|
# The host SHA-256 implementation computes real digests. Only adapt its
|
|
# typed output pointers to the firmware library's void* signature.
|
|
(cls.work / "SHA256.h").write_text(f'''
|
|
#pragma once
|
|
#define SHA256 NativeSHA256
|
|
#include "{ROOT / 'test/mocks/SHA256.h'}"
|
|
#undef SHA256
|
|
class SHA256 : public NativeSHA256 {{ public:
|
|
void finalize(void* out, size_t length) {{
|
|
NativeSHA256::finalize(static_cast<uint8_t*>(out), length);
|
|
}}
|
|
void finalizeHMAC(const uint8_t* key, size_t size, void* out, size_t length) {{
|
|
NativeSHA256::finalizeHMAC(key, size, static_cast<uint8_t*>(out), length);
|
|
}}
|
|
}};
|
|
''', encoding="ascii")
|
|
cls.binaries = {}
|
|
for platform in ("ESP32_PLATFORM", "NRF52_PLATFORM"):
|
|
binary = cls.work / platform
|
|
command = [
|
|
compiler, "-std=c++17", "-O1", "-g", "-Wall", "-Wextra", "-Werror",
|
|
"-Wno-unused-parameter", "-Wno-sign-compare", "-Wno-unused-function",
|
|
"-fsanitize=address,undefined", "-fno-sanitize-recover=all", "-fno-pie", "-no-pie",
|
|
"-D" + platform + "=1", "-I", str(cls.work),
|
|
"-I", str(REGION_FIXTURE / "mocks"),
|
|
"-I", str(ROOT / "test/mocks"), "-I", str(ROOT / "src"),
|
|
str(FIXTURE), str(ROOT / "src/helpers/TransportKeyStore.cpp"),
|
|
"-o", str(binary),
|
|
]
|
|
result = subprocess.run(command, capture_output=True, text=True, timeout=60)
|
|
if result.returncode:
|
|
cls.temporary.cleanup()
|
|
raise AssertionError(result.stdout + result.stderr)
|
|
cls.binaries[platform] = binary
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
cls.temporary.cleanup()
|
|
|
|
def run_case(self, case):
|
|
environment = os.environ.copy()
|
|
environment["ASAN_OPTIONS"] = "detect_leaks=1:abort_on_error=1"
|
|
environment["UBSAN_OPTIONS"] = "halt_on_error=1:print_stacktrace=1"
|
|
for platform, binary in self.binaries.items():
|
|
with self.subTest(platform=platform):
|
|
result = subprocess.run([str(binary), case], capture_output=True,
|
|
text=True, timeout=60, env=environment)
|
|
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
|
self.assertIn("PASS: " + case, result.stdout)
|
|
|
|
def test_real_scope_key_filter_and_reboot_persistence(self):
|
|
self.run_case("scope-filter")
|
|
|
|
def test_existing_hierarchy_and_nonflood_policy_are_preserved(self):
|
|
self.run_case("existing-policy")
|
|
|
|
def test_full_region_table_preflight_and_missing_scope_disable(self):
|
|
self.run_case("full-table")
|
|
|
|
def test_save_failures_retry_even_when_ram_flags_already_match(self):
|
|
self.run_case("save-retry")
|
|
|
|
def test_invalid_callback_context_is_refused(self):
|
|
self.run_case("invalid-context")
|
|
|
|
def test_unreadable_existing_regions_are_held_then_reloaded_automatically(self):
|
|
self.run_case("unreadable-boot")
|
|
|
|
def test_malformed_regions_and_transaction_siblings_are_not_overwritten(self):
|
|
self.run_case("malformed-boot")
|
|
|
|
def test_metadata_errors_cannot_look_like_a_fresh_region_store(self):
|
|
self.run_case("metadata-boot")
|
|
|
|
def test_all_infrastructure_roles_bind_after_loading_regions(self):
|
|
for directory, filename, classname in (
|
|
("simple_repeater", "MyMesh.cpp", "MyMesh"),
|
|
("simple_room_server", "MyMesh.cpp", "MyMesh"),
|
|
("simple_sensor", "SensorMesh.cpp", "SensorMesh")):
|
|
with self.subTest(role=directory):
|
|
source = (ROOT / "examples" / directory / filename).read_text(encoding="ascii")
|
|
begin = body(source, "void " + classname + "::begin(")
|
|
self.assertLess(begin.index("_cli.loadPrefs(_fs)"),
|
|
begin.index("region_map.load(_fs)"))
|
|
self.assertLess(begin.index("region_map.load(_fs)"),
|
|
begin.index("_cli.onRegionsLoaded(_fs, regions_loaded)"))
|
|
self.assertIn("const bool regions_loaded = region_map.load(_fs);", begin)
|
|
common = (ROOT / "src/helpers/CommonCLI.cpp").read_text(encoding="ascii")
|
|
loop = body(common, "void CommonCLI::loop()")
|
|
guarded = body(loop, "if (!mesh::morseTimingActive(")
|
|
self.assertIn("recoverMediaMeshRegions();", guarded)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|