Files
MeshCore/test/test_cardputer_board_policy.py
T
mikecarper 64a068506b Merge PR #26: add M5Stack Cardputer-Adv support
Integrate mjgodat's keyboard/display Companion, USB Companion, Repeater and
Room Server support for Cardputer-Adv with Cap LoRa-1262.

Fix UI retry dispatch and ACK ownership, packet/storage bounds, numeric
input validation, sparse channel selection, optional-display shutdown,
keyboard layout limits, reserved GPIOs and Windows cross-drive includes.
Retain all 350 contacts and queue capacity using the existing contact cache;
account for the custom UI's runtime allocations in the RAM admission gate.
Carry the MultiMote and DejaVu notices with firmware packages and add CI
coverage for all four board roles plus the shared T-Deck Companion.

Validation: all four Cardputer roles and the T-Deck BLE Companion build;
Cardputer sanitizer, UI, layout, ownership, licensing and storage tests;
65 RAM-policy tests and 14 contact-cache tests. PlatformIO ran serially.

PR: https://github.com/mikecarper/MeshCore/pull/26
2026-10-27 22:25:35 -07:00

127 lines
5.3 KiB
Python

"""Protect built-in Cardputer buses and Windows cross-drive include paths."""
from pathlib import Path
import ntpath
import os
import runpy
import shutil
import subprocess
import sys
import tempfile
import unittest
from unittest import mock
from test_replay_reset_integration import extract_braced
ROOT = Path(__file__).resolve().parents[1]
VARIANT = ROOT / "variants/m5stack_cardputer_adv"
class CardputerBoardPolicyTests(unittest.TestCase):
def check_gpio(self, remove_board_guard=False):
method = extract_braced((VARIANT / "CardputerAdvBoard.h").read_text(),
"bool isUserGpioAvailable(uint8_t pin) const override")
if remove_board_guard:
method = method.replace("if (pin == reserved) return false;",
"if (pin == reserved) break;")
source = r'''
#include <cassert>
#include <cstdint>
#define PIN_SD_CS 12
#include "globals.h"
class ESP32Board {
public:
mutable unsigned calls = 0;
virtual bool isUserGpioAvailable(uint8_t pin) const {
++calls;
// Stand in for shared radio/USB/ADC/strapping exclusions. The variant
// must retain a base veto, rather than replacing the common policy.
return pin != 7 && pin < 49;
}
};
class CardputerAdvBoard : public ESP32Board {
public:
@METHOD@
};
int main() {
const uint8_t claimed[] = {
0, 8, 9, 11, 12, 33, 34, 35, 36, 37, 38, 41, 42, 43, 46
};
CardputerAdvBoard board;
for (unsigned pin = 0; pin < 256; ++pin) {
bool local = false;
for (uint8_t reserved : claimed) local |= pin == reserved;
const unsigned before = board.calls;
const bool available = board.isUserGpioAvailable(static_cast<uint8_t>(pin));
assert(available == (!local && pin != 7 && pin < 49));
assert(board.calls == before + unsigned(!local));
}
}
'''.replace("@METHOD@", method)
compiler = os.environ.get("CXX") or shutil.which("g++") or shutil.which("clang++")
self.assertIsNotNone(compiler, "a native C++ compiler is required")
with tempfile.TemporaryDirectory(prefix="cardputer-gpio-") as directory:
source_path = Path(directory) / "policy.cpp"
binary = Path(directory) / ("policy.exe" if os.name == "nt" else "policy")
source_path.write_text(source, encoding="ascii")
flags = [] if os.name == "nt" else ["-fsanitize=address,undefined",
"-fno-sanitize-recover=all", "-fno-pie", "-no-pie"]
built = subprocess.run([compiler, "-std=c++11", "-Wall", "-Wextra", "-Werror",
*flags, "-I", str(VARIANT), str(source_path), "-o", str(binary)],
capture_output=True, text=True, timeout=30)
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
checked = subprocess.run([str(binary)], capture_output=True, text=True, timeout=10)
if remove_board_guard:
self.assertNotEqual(checked.returncode, 0, "unprotected board pins passed")
self.assertIn("Assertion", checked.stderr)
else:
self.assertEqual(checked.returncode, 0, checked.stdout + checked.stderr)
def test_all_builtin_bus_pins_are_reserved_and_base_policy_is_preserved(self):
self.check_gpio()
def test_missing_board_guard_is_rejected(self):
self.check_gpio(remove_board_guard=True)
def test_windows_other_drive_keeps_include_and_shared_root_order(self):
class Environment(dict):
def subst(self, value):
return self["PROJECT_PACKAGES_DIR"] if value == "$PROJECT_PACKAGES_DIR" else value
def Flatten(self, values):
return values
packages = r"C:\Users\Mesh User\.platformio\packages"
sdk = packages + r"\framework-arduinoespressif32-libs\esp32s3\include"
project = r"D:\Radio Builds\MeshCore\src"
paths = [sdk + r"\driver\include", project, sdk + r"\freertos\include",
r"C:\Users\Mesh User\.platformio\lib"]
env = Environment(PROJECT_PACKAGES_DIR=packages, CPPPATH=paths)
hook_path = VARIANT / "win_long_cmdline.py"
code = compile(hook_path.read_text(), str(hook_path), "exec")
hook = {"env": env, "Import": lambda name: None}
with mock.patch.object(sys, "platform", "win32"), \
mock.patch.object(os, "path", ntpath), mock.patch.object(os, "sep", "\\"):
exec(code, hook)
self.assertIsNone(hook["_prefix_root"](packages, project))
flags = hook["_short_incflags"](env)
prefix = ntpath.normcase(sdk).replace("\\", "/") + "/"
self.assertEqual(flags, ["-iprefix", prefix, "-iwithprefixbefore",
"driver/include", "-I" + project, "-iwithprefixbefore",
"freertos/include", "-I" + paths[-1]])
self.assertIn("_cardputer_short_incflags", env)
# No root shared with the SDK: preserve every include unchanged.
env["CPPPATH"] = [project]
self.assertEqual(hook["_short_incflags"](env), ["-I" + project])
def test_hook_leaves_other_operating_systems_unchanged(self):
env = {}
with mock.patch.object(sys, "platform", "linux"):
runpy.run_path(str(VARIANT / "win_long_cmdline.py"),
init_globals={"env": env, "Import": lambda name: None})
self.assertEqual(env, {})
if __name__ == "__main__":
unittest.main()