Files
MeshCore/test/test_pocket_world_map.py
T

120 lines
6.1 KiB
Python

#!/usr/bin/env python3
"""Qualify the real bounded world-map decoder, projection and generated pack."""
import importlib.util
from pathlib import Path
import re
import shutil
import subprocess
import tempfile
import unittest
import zlib
ROOT = Path(__file__).resolve().parents[1]
SPEC = importlib.util.spec_from_file_location("pack_world_map", ROOT / "tools/meshpocket_map/pack_world_map.py")
packer = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(packer)
def run(command):
result = subprocess.run(command, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode:
raise AssertionError("command failed: " + " ".join(map(str, command)) + "\n" + result.stdout)
return result.stdout
class PocketWorldMapTests(unittest.TestCase):
def test_generated_pack_bounds_and_roundtrip(self):
header = packer.OUTPUT.read_text(encoding="utf-8")
chunks = [tuple(map(int, row)) for row in re.findall(
r"\{(\d+), (\d+), (\d+), (-?\d+), (-?\d+), (-?\d+), (-?\d+)\}", header)]
data_section = header.split("static const uint8_t kDeflate[] = {", 1)[1].split("};", 1)[0]
packed = bytes(int(byte, 16) for byte in re.findall(r"0x([0-9a-f]{2})", data_section))
self.assertEqual(len(chunks), 32)
self.assertEqual(len(packed) + 24 * len(chunks), 80658)
self.assertIn("kRingCount = 1422;", header)
offset = 0
for start, size, raw_size, west, south, east, north in chunks:
self.assertEqual(start, offset)
self.assertLessEqual(raw_size, 3072)
self.assertLessEqual(west, east)
self.assertLessEqual(south, north)
stream = zlib.decompressobj(wbits=-15)
raw = stream.decompress(packed[start:start + size])
self.assertTrue(stream.eof)
self.assertFalse(stream.unused_data)
self.assertEqual(len(raw), raw_size)
offset += size
self.assertEqual(offset, len(packed))
def test_simplification_retains_tiny_islands_and_dateline(self):
island = [(170.0, -20.0), (170.001, -20.0), (170.001, -20.001),
(170.0, -20.001), (170.0, -20.0)]
simplified = packer.simplify(island)
self.assertGreaterEqual(len(set(simplified)), 3)
self.assertEqual(simplified[0], simplified[-1])
crossing = [(179.8, -20.0), (-179.8, -20.0), (-179.8, -20.4),
(179.8, -20.4), (179.8, -20.0)]
simplified = packer.simplify(crossing)
self.assertLess(max(p[0] for p in simplified) - min(p[0] for p in simplified), 1000)
self.assertEqual(simplified[0], simplified[-1])
with self.assertRaisesRegex(ValueError, "SHA-256"):
list(packer.source_rings(b"incorrect source archive"))
def test_real_renderer_and_parser_under_sanitizers(self):
cc, cxx = shutil.which("gcc"), shutil.which("g++")
if not cc or not cxx:
self.skipTest("GCC/G++ unavailable")
with tempfile.TemporaryDirectory(prefix="mesh-pocket-map-") as temporary:
directory = Path(temporary)
# Windows MinGW commonly has no sanitizer runtime. Linux CI runs
# the same real renderer with both bounds/undefined checks active.
sanitizer = [] if __import__("os").name == "nt" else ["-fsanitize=address,undefined"]
run([cc, "-std=c11", "-DMESHCORE_TINF_IMPLEMENTATION", "-g", "-O1", *sanitizer,
"-c", str(ROOT / "src/helpers/ota/tinf/tinflate.c"), "-o", str(directory / "tinf.o")])
executable = directory / "world_map"
run([cxx, "-std=c++17", "-DPOCKET_WORLD_MAP_TEST", "-I" + str(ROOT / "src"),
"-g", "-O1", *sanitizer, str(ROOT / "test/fixtures/pocket_world_map/test_world_map.cpp"),
str(ROOT / "src/helpers/ui/PocketWorldMap.cpp"), str(directory / "tinf.o"), "-o", str(executable)])
self.assertIn("checks passed", run([str(executable)]))
def test_arm_decode_stack_fits_companion_loop(self):
tool = shutil.which("arm-none-eabi-g++")
if not tool:
candidate = Path.home() / ".platformio/packages/toolchain-gccarmnoneeabi/bin/arm-none-eabi-g++"
if candidate.is_file():
tool = str(candidate)
if not tool:
self.skipTest("ARM compiler unavailable")
cc = str(Path(tool).with_name("arm-none-eabi-gcc"))
with tempfile.TemporaryDirectory(prefix="mesh-pocket-map-stack-") as temporary:
directory = Path(temporary)
flags = ["-mcpu=cortex-m4", "-mthumb", "-Oz", "-fno-inline-small-functions", "-fstack-usage"]
run([tool, "-std=gnu++17", *flags, "-fno-exceptions", "-fno-rtti", "-DPOCKET_WORLD_MAP_TEST",
"-I" + str(ROOT / "src"), "-c", str(ROOT / "src/helpers/ui/PocketWorldMap.cpp"),
"-o", str(directory / "map.o")])
run([cc, "-std=gnu11", *flags, "-DMESHCORE_TINF_IMPLEMENTATION", "-c",
str(ROOT / "src/helpers/ota/tinf/tinflate.c"), "-o", str(directory / "tinf.o")])
def frames(name):
rows = {}
for line in (directory / (name + ".su")).read_text().splitlines():
function, size, kind = line.rsplit("\t", 2)
self.assertEqual(kind, "static", line)
rows[function] = int(size)
return rows
renderer, decoder = frames("map"), frames("tinf")
entry = next(size for name, size in renderer.items() if "::renderWorldMap(" in name)
# Decoder helper sum overbounds mutually exclusive branches.
decode = sum(size for name, size in decoder.items()
if not name.endswith(":tinf_init") and not name.endswith(":tinf_uncompress"))
draw = sum(size for name, size in renderer.items()
if any(signature in name for signature in ("::drawChunk(", "::project(", "::readDelta(")))
peak = entry + max(decode, draw)
self.assertLessEqual(peak + 1536, 8192)
print(f"ARM Pocket map local stack: {peak} bytes + 1536 caller reserve <= 8192")
if __name__ == "__main__":
unittest.main()