Files
MeshCore/test/test_region_name_export.py
T

102 lines
4.1 KiB
Python

#!/usr/bin/env python3
"""Bounded RegionMap name export through the real role-facing wrappers."""
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest
from cpp_source import body
ROOT = Path(__file__).resolve().parents[1]
FIXTURE = ROOT / "test/fixtures/region_name_export.cpp"
def implementation():
source = (ROOT / "src/helpers/RegionMap.cpp").read_text(encoding="ascii")
signatures = (
"static const char* skip_hash(", "RegionMap::RegionMap(",
"bool RegionMap::is_name_char(", "RegionEntry* RegionMap::putRegion(",
"RegionEntry* RegionMap::findByName(", "RegionEntry* RegionMap::findById(",
"bool RegionMap::wouldCreateCycle(", "int RegionMap::exportNamesTo(",
)
generated = "\n".join(body(source, signature) for signature in signatures)
text_helpers = (ROOT / "src/helpers/TxtDataHelpers.cpp").read_text(encoding="ascii")
generated += "\n" + body(text_helpers, "void StrHelper::strncpy(")
for role, directory in (("repeater", "simple_repeater"),
("room", "simple_room_server")):
role_source = (ROOT / "examples" / directory / "MyMesh.cpp").read_text(encoding="ascii")
# Keep the original production wrapper body and signature unchanged.
# A namespace gives each minimal role adapter its original MyMesh name.
generated += "\nnamespace " + role + " {\n"
generated += "class MyMesh {\npublic:\nRegionMap& region_map;\n"
generated += "explicit MyMesh(RegionMap& map) : region_map(map) {}\n"
generated += "void getLocalScopes(char* buf, size_t len);\n};\n"
generated += body(role_source, "void MyMesh::getLocalScopes(") + "\n}\n"
return generated
class RegionNameExportTest(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-region-name-export-")
work = Path(cls.temporary.name)
(work / "region_under_test.inc").write_text(implementation(), encoding="ascii")
cls.executable = work / "test"
command = [
compiler, "-std=c++17", "-O1", "-g", "-Wall", "-Wextra", "-Werror",
"-Wno-unused-parameter", "-Wno-sign-compare",
"-fsanitize=address,undefined", "-fno-sanitize-recover=all",
"-fno-pie", "-no-pie", "-I", str(work),
"-I", str(ROOT / "test/fixtures/region_map_transactions/mocks"),
"-I", str(ROOT / "test/mocks"), "-I", str(ROOT / "src"),
str(FIXTURE), "-o", str(cls.executable),
]
result = subprocess.run(command, capture_output=True, text=True, timeout=60)
if result.returncode:
cls.temporary.cleanup()
raise AssertionError(result.stdout + result.stderr)
@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"
result = subprocess.run([str(self.executable), 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_healthy_production_capacities(self):
self.run_case("healthy")
def test_repeater_one_byte_destination(self):
self.run_case("repeater-one-byte")
def test_direct_one_byte_destination(self):
self.run_case("direct-one-byte")
def test_room_one_byte_destination(self):
self.run_case("room-one-byte")
def test_null_and_nonpositive_capacities(self):
self.run_case("invalid")
def test_capacity_and_filter_matrix(self):
self.run_case("matrix")
def test_role_wrapper_capacity_matrix(self):
self.run_case("wrappers")
if __name__ == "__main__":
unittest.main()