mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-09-06 21:23:52 +00:00
A 31-character pack ID makes mounted tile paths (/sd/pyxis-map/packs/<id>/tiles/<z>/<x>/<y>.png) exceed the 68-byte mount buffer once either x or y is two digits, so beginRead returns INVALID_ARGUMENT and the UI shows 'Tile I/O error' at z4+ even though the file exists and is intact. Compiles the unmodified MapTilePack/MapTileStoreSD/SDAccess/codec/manifest sources against small Arduino/FreeRTOS host shims. The core section (no card needed) drives the real store with a model of the mount-prefix arithmetic and asserts the 31-char-ID boundary loads at z4/z5; an optional end-to-end section runs the real MapTilePack over a bound card root when /sd is writable. Fails on the current 64-byte PATH_CAPACITY: red by design.
110 lines
4.6 KiB
Python
110 lines
4.6 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
|
|
import pytest
|
|
|
|
from native_test import find_cxx
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
TEST_SOURCE = ROOT / "tests/native/test_map_tile_store_path_capacity.cpp"
|
|
PACK_SOURCE = ROOT / "lib/tdeck_ui/Hardware/TDeck/MapTilePack.cpp"
|
|
STORE_SOURCE = ROOT / "lib/tdeck_ui/Hardware/TDeck/MapTileStoreSD.cpp"
|
|
SDACCESS_SOURCE = ROOT / "lib/tdeck_ui/Hardware/TDeck/SDAccess.cpp"
|
|
CODEC_SOURCE = ROOT / "lib/tdeck_ui/Hardware/TDeck/ActiveMapSetCodec.cpp"
|
|
MANIFEST_SOURCE = ROOT / "lib/tdeck_ui/UI/LXMF/MapPackManifest.cpp"
|
|
SHIM_DIR = ROOT / "tests/native/sdhostshim"
|
|
|
|
|
|
def sudo_ok() -> bool:
|
|
return shutil.which("sudo") is not None and subprocess.run(
|
|
["sudo", "-n", "true"], capture_output=True, timeout=10
|
|
).returncode == 0
|
|
|
|
|
|
def sd_writable() -> bool:
|
|
return os.access("/sd", os.W_OK)
|
|
|
|
|
|
def run_test(binary: Path, card_root: Path, env: dict, timeout: int) -> subprocess.CompletedProcess[str]:
|
|
"""Run the test binary, arranging a /sd bind mount for the end-to-end
|
|
section when possible.
|
|
|
|
The production store reads the literal "/sd" mount point, so the
|
|
end-to-end card-content section only runs when "/sd" is a writable
|
|
directory. Without a usable password-less sudo we still run the binary
|
|
normally: the core regression section (mount-independent) executes, and
|
|
the binary prints a skip note for the card section.
|
|
"""
|
|
command = [str(binary), str(card_root)]
|
|
mounted = False
|
|
run_as_root = False
|
|
if not sd_writable() and sudo_ok():
|
|
probe = subprocess.run(["mountpoint", "-q", "/sd"], capture_output=True, timeout=10)
|
|
if probe.returncode == 0:
|
|
# A live mount we did not create: leave it alone, run normally.
|
|
pass
|
|
else:
|
|
subprocess.run(
|
|
["sudo", "-n", "mkdir", "-p", "/sd"], capture_output=True, timeout=10)
|
|
mnt = subprocess.run(
|
|
["sudo", "-n", "mount", "--bind", str(card_root), "/sd"],
|
|
capture_output=True, text=True, timeout=30,
|
|
)
|
|
if mnt.returncode == 0:
|
|
mounted = True
|
|
run_as_root = True # bind mount content is root-owned
|
|
else:
|
|
raise pytest.skip(f"could not bind-mount card root at /sd: {mnt.stderr.strip()}")
|
|
if run_as_root:
|
|
command = ["sudo", "-n"] + command
|
|
try:
|
|
return subprocess.run(command, capture_output=True, text=True,
|
|
timeout=timeout, env=env)
|
|
finally:
|
|
if mounted:
|
|
subprocess.run(["sudo", "-n", "umount", "/sd"], capture_output=True, timeout=30)
|
|
|
|
|
|
@pytest.mark.parametrize("sanitize", [False, True], ids=["strict-cxx11", "asan-ubsan"])
|
|
def test_map_tile_store_path_capacity(tmp_path: Path, sanitize: bool) -> None:
|
|
# Note: -Wconversion/-Wsign-conversion are deliberately omitted here (and
|
|
# only here): SDAccess.cpp is ARDUINO-gated production code that only ever
|
|
# compiles under PlatformIO's ESP32 flag set, where the uint8_t/int
|
|
# Arduino API conversions are routine. The other flags above are kept so
|
|
# the unmodified store/pack sources stay strictly checked.
|
|
binary = tmp_path / "test_map_tile_store_path_capacity"
|
|
card_root = tmp_path / "card"
|
|
card_root.mkdir()
|
|
command = [
|
|
find_cxx(), "-std=c++11", "-Wall", "-Wextra", "-Werror", "-pedantic",
|
|
"-DARDUINO=100",
|
|
f"-I{SHIM_DIR}", f"-I{ROOT / 'lib/tdeck_ui'}",
|
|
str(TEST_SOURCE), str(PACK_SOURCE), str(STORE_SOURCE),
|
|
str(SDACCESS_SOURCE), str(CODEC_SOURCE), str(MANIFEST_SOURCE),
|
|
"-o", str(binary),
|
|
]
|
|
if sanitize:
|
|
command[1:1] = ["-fsanitize=address,undefined", "-fno-omit-frame-pointer"]
|
|
compiled = subprocess.run(command, capture_output=True, text=True, timeout=120,
|
|
cwd=ROOT)
|
|
assert compiled.returncode == 0, compiled.stdout + compiled.stderr
|
|
env = os.environ.copy()
|
|
if sanitize:
|
|
env["ASAN_OPTIONS"] = "detect_leaks=1:halt_on_error=1"
|
|
env["UBSAN_OPTIONS"] = "halt_on_error=1:print_stacktrace=1"
|
|
ran = run_test(binary, card_root, env, timeout=120)
|
|
assert ran.returncode == 0, ran.stdout + ran.stderr
|
|
# The end-to-end section (4 tests) is conditional on a writable /sd mount;
|
|
# the core regression section (6 tests) always runs.
|
|
for expected in (
|
|
"map tile store path capacity: 6 tests passed\n",
|
|
"map tile store path capacity: 10 tests passed\n",
|
|
):
|
|
if ran.stdout == expected:
|
|
return
|
|
raise AssertionError(f"unexpected output: {ran.stdout!r}")
|