Files
MeshCore/test/test_nrf52_flash_trim.py
T

318 lines
18 KiB
Python

import importlib.util
import shutil
import subprocess
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[1]
SPEC = importlib.util.spec_from_file_location("nrf52_flash_trim", ROOT / "scripts/nrf52_flash_trim.py")
TRIM = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(TRIM)
class Nrf52FlashTrimTest(unittest.TestCase):
def policy(self, name="RAK_4631_repeater", role="simple_repeater", **kwargs):
options = dict(platform="nordicnrf52", mcu="nrf52840", env_name=name,
src_filter="+<*.cpp> +<../examples/%s>" % role)
options.update(kwargs)
return TRIM.trim_policy(**options)
def test_every_infrastructure_role_keeps_compact_crypto_and_names(self):
for role in ("simple_repeater", "simple_room_server", "simple_sensor", "kiss_modem"):
with self.subTest(role=role):
defines = self.policy(role=role)
for name in ("MESH_NRF52_FLASH_TRIM", "ED25519_COMPACT_BASE",
"ED25519_COMPACT_SHA512", "OTA_TARGET_NAME_FRONT_CODED",
"SSD1306_NO_SPLASH"):
self.assertEqual(defines[name], 1)
self.assertEqual(defines["CFG_TUD_HID"], 0)
self.assertNotIn("CFG_TUD_CDC", defines)
self.assertFalse(any(name.startswith("CFG_TUH") for name in defines))
def test_every_companion_alias_is_excluded(self):
for suffix in ("companion_radio_full", "companion_radio_usb", "companion_radio_ble",
"companion_radio_wifi", "companion_radio_serial", "companion_radio_ethernet",
"companion_usb", "companion_ble", "comp_radio_usb", "terminal_chat", "term_chat"):
with self.subTest(suffix=suffix):
self.assertEqual(self.policy(name="RAK_4631_" + suffix), {})
def test_source_filter_and_synthetic_alias_also_exclude_companion(self):
for role in ("companion_radio/*.cpp", "simple_secure_chat/main.cpp"):
self.assertEqual(self.policy(name="custom", role=role), {})
for flags in ('-DCOMPANION_RADIO_FULL=1', '-D COMPANION_FEATURE_USB_MOTA_SOURCE=1',
'-DOTA_VARIANT=\'"RAK_4631_terminal_chat"\'',
'-DOTA_VARIANT=\'"RAK_4631_companion_radio_full"\''):
with self.subTest(flags=flags):
self.assertEqual(self.policy(flags=flags), {})
self.assertEqual(self.policy(cppdefines=[("COMPANION_RADIO_FULL", 1)]), {})
def test_unknown_roles_other_platforms_and_explicit_optout_are_unchanged(self):
for options in (dict(role="custom"), dict(platform="espressif32", mcu="esp32"),
dict(platform="ststm32", mcu="stm32f411"),
dict(flags="-DMESH_NRF52_FLASH_TRIM=0")):
self.assertEqual(self.policy(**options), {})
def test_effective_filter_is_ordered_and_does_not_include_outside_source_by_wildcard(self):
self.assertEqual(self.policy(src_filter="+<*> +<../examples/simple_repeater> -<../examples/simple_repeater>"), {})
self.assertEqual(self.policy(src_filter="+<*>"), {})
self.assertTrue(self.policy(src_filter="+<../examples/companion_radio> -<../examples/companion_radio> +<../examples/simple_repeater>"))
def test_board_and_user_defines_are_not_overridden(self):
defines = self.policy(flags="-D CFG_TUD_HID=1 -UED25519_COMPACT_BASE -USSD1306_NO_SPLASH")
self.assertNotIn("CFG_TUD_HID", defines)
self.assertNotIn("ED25519_COMPACT_BASE", defines)
self.assertNotIn("SSD1306_NO_SPLASH", defines)
self.assertEqual(TRIM.flag_defines("-DMESH_NRF52_FLASH_TRIM=1 -UMESH_NRF52_FLASH_TRIM")["MESH_NRF52_FLASH_TRIM"], None)
def test_rak_repeater_trims_follow_hardware_and_effective_role(self):
for board in ("RAK_3401", "RAK_4631"):
for suffix in ("repeater", "repeater_lora_ota_no_external_sensors",
"repeater_unified_lora_ota", "repeater_bridge_rs232_serial1"):
with self.subTest(board=board, suffix=suffix):
defines = self.policy(name=board + "_" + suffix, flags="-D" + board)
for name in ("ED25519_COMPACT_SCALAR", "MESH_CLI_SHARE_REPLIES"):
self.assertEqual(defines[name], 1)
self.assertNotIn("ED25519_COMPACT_FIELD", defines)
for role in ("simple_room_server", "simple_sensor", "kiss_modem", "companion_radio"):
defines = self.policy(role=role, flags="-D" + board)
for name in TRIM.RAK_REPEATER_TRIM_DEFINES:
self.assertNotIn(name, defines)
for options in (dict(flags="-DRAK_3401=0"), dict(mcu="nrf52832", flags="-DRAK_3401"),
dict(src_filter="+<../examples/simple_repeater> +<../examples/simple_sensor>",
flags="-DRAK_4631")):
for name in TRIM.RAK_REPEATER_TRIM_DEFINES:
self.assertNotIn(name, self.policy(**options))
defines = self.policy(flags="-DRAK_3401 -DED25519_COMPACT_SCALAR=0 "
"-UED25519_COMPACT_FIELD -DMESH_CLI_SHARE_REPLIES=0")
for name in TRIM.RAK_REPEATER_TRIM_DEFINES:
self.assertNotIn(name, defines)
def test_local_names_honor_overlay_identity_and_explicit_defines(self):
flags = ('-DMESH_OTA_LOCAL_TARGET_NAME_ONLY=1 '
'-DMOTA_TARGET_ID=0x29a0da19 '
'-DOTA_VARIANT=\'"RAK_4631_repeater_lora_ota_no_external_sensors"\'')
defines = self.policy(flags=flags)
self.assertEqual(defines["OTA_TARGET_NAME_TABLE"], 0)
self.assertEqual(defines["OTA_LOCAL_TARGET_ID"], "0x29a0da19")
self.assertEqual(defines["OTA_LOCAL_TARGET_NAME"], "OTA_VARIANT")
explicit = self.policy(flags=flags + ' -DOTA_TARGET_NAME_TABLE=1 '
'-DOTA_LOCAL_TARGET_ID=7 -DOTA_LOCAL_TARGET_NAME=custom_name')
for name in ("OTA_TARGET_NAME_TABLE", "OTA_LOCAL_TARGET_ID", "OTA_LOCAL_TARGET_NAME"):
self.assertNotIn(name, explicit)
for flags in ("", "-DMESH_OTA_LOCAL_TARGET_NAME_ONLY=0"):
self.assertNotIn("OTA_TARGET_NAME_TABLE", self.policy(flags=flags))
def test_cli_reply_copy_flag_is_attached_only_to_exact_cli_object(self):
class Node:
def __init__(self, path): self.path = path
def srcnode(self): return self
def get_abspath(self): return str(self.path)
class Env(dict):
def subst(self, value): return value.replace("$PROJECT_DIR", str(ROOT))
def Object(self, node, **flags): return node, flags
env = Env(CCFLAGS=["-Os", "-flto"])
cli = Node(ROOT / "src/helpers/CommonCLI.cpp")
result, flags = TRIM.compile_shared_cli_replies(env, cli)
self.assertIs(result, cli)
self.assertEqual(flags["CCFLAGS"], ["-Os", "-flto", "-fno-builtin-strcpy"])
self.assertEqual(env["CCFLAGS"], ["-Os", "-flto"])
other = Node(ROOT / "test/fixtures/CommonCLI.cpp")
self.assertIs(TRIM.compile_shared_cli_replies(env, other), other)
companion = Node(ROOT / "examples/companion_radio/MyMesh.cpp")
result, flags = TRIM.compile_shared_companion_replies(env, companion)
self.assertIs(result, companion)
self.assertEqual(flags["CCFLAGS"], ["-Os", "-flto", "-fno-builtin-strcpy"])
self.assertEqual(env["CCFLAGS"], ["-Os", "-flto"])
for path in ("examples/simple_repeater/MyMesh.cpp", "test/fixtures/MyMesh.cpp",
"src/helpers/CommonCLI.cpp", "src/helpers/RadioProfileCLI.cpp"):
other = Node(ROOT / path)
self.assertIs(TRIM.compile_shared_companion_replies(env, other), other)
def test_companion_shared_replies_are_limited_to_the_qualified_rak_full_image(self):
defaults = dict(platform="nordicnrf52", mcu="nrf52840",
env_name="RAK_4631_companion_radio_full",
src_filter="+<../examples/companion_radio/*.cpp>")
self.assertTrue(TRIM.companion_shared_replies_policy(**defaults))
for changes in (dict(env_name="RAK_3401_companion_radio_full"),
dict(env_name="RAK_4631_companion_radio_usb"),
dict(env_name="custom_companion_radio_full"),
dict(mcu="nrf52832"), dict(platform="ststm32"),
dict(src_filter="+<../examples/simple_repeater>"),
dict(src_filter="+<../examples/companion_radio> +<../examples/simple_sensor>"),
dict(flags="-DMESH_CLI_SHARE_REPLIES=0"),
dict(flags="-UMESH_CLI_SHARE_REPLIES"),
dict(flags="-DMESH_NRF52_COMPANION_TRIM=0")):
with self.subTest(changes=changes):
self.assertFalse(TRIM.companion_shared_replies_policy(**(defaults | changes)))
def test_local_names_do_not_change_companion_or_unqualified_roles(self):
for options in (dict(name="RAK_4631_companion_radio_full"),
dict(role="custom"), dict(platform="espressif32", mcu="esp32")):
self.assertEqual(self.policy(flags="-DMESH_OTA_LOCAL_TARGET_NAME_ONLY=1", **options), {})
def test_cc310_archive_is_exact_and_requires_supported_mcu_and_enabled_crypto(self):
with tempfile.TemporaryDirectory() as temp_dir:
archive = Path(temp_dir) / "libraries/Adafruit_nRFCrypto" / TRIM.CC310_ARCHIVE
archive.parent.mkdir(parents=True)
archive.write_bytes(b"future-sdk")
enabled = {"USE_CC310_HW_CRYPTO": "1"}
self.assertFalse(TRIM.supported_cc310(temp_dir, "nrf52840", enabled))
with patch.object(TRIM, "CC310_ARCHIVE_SHA256", TRIM.hashlib.sha256(b"future-sdk").hexdigest()):
self.assertTrue(TRIM.supported_cc310(temp_dir, "nrf52840", enabled))
self.assertFalse(TRIM.supported_cc310(temp_dir, "nrf52832", enabled))
self.assertFalse(TRIM.supported_cc310(temp_dir, "nrf52840", {"USE_CC310_HW_CRYPTO": "0"}))
self.assertFalse(TRIM.supported_cc310(temp_dir, "nrf52840", {}))
def test_profile_wiring_is_nrf52_only_and_companion_common_flags_unchanged(self):
config = (ROOT / "platformio.ini").read_text()
nrf = config.split("[nrf52_base]", 1)[1].split("\n[", 1)[0]
self.assertIn("pre:scripts/nrf52_flash_trim.py", nrf)
self.assertEqual(config.count("pre:scripts/nrf52_flash_trim.py"), 1)
self.assertNotIn("ED25519_COMPACT", nrf)
self.assertNotIn("CFG_TUD_HID", nrf)
self.assertNotIn("SSD1306_NO_SPLASH", nrf)
self.assertIn("-D MESH_NRF52_LOOP_STACK_WORDS=2048", nrf)
def test_tft_force_include_is_per_library_and_preserves_existing_include_pair(self):
self.assertTrue(TRIM.source_selected("+<helpers/ui/ST7735Display.cpp>",
"helpers/ui/ST7735Display.cpp"))
self.assertFalse(TRIM.source_selected("+<helpers/ui/*.cpp> -<helpers/ui/ST7735Display.cpp>",
"helpers/ui/ST7735Display.cpp"))
class LibraryEnvironment(dict):
def Append(self, **kwargs):
for key, value in kwargs.items():
self.setdefault(key, []).extend(value)
def subst(self, text):
return text.replace("$PROJECT_DIR", str(ROOT))
library = LibraryEnvironment(CCFLAGS=["-include", "SoftDeviceSvcCompat.h"])
source = object()
self.assertIs(TRIM.trim_tft_library(library, source), source)
self.assertEqual(library["CCFLAGS"].count("-include"), 2)
before = list(library["CCFLAGS"])
TRIM.trim_tft_library(library, source)
self.assertEqual(library["CCFLAGS"], before)
def test_install_does_not_mutate_excluded_roles_and_scopes_wrapper_build(self):
class Platform:
name = "nordicnrf52"
def get_package_dir(self, _name):
return "sdk"
class BuildEnvironment(dict):
def PioPlatform(self):
return Platform()
def BoardConfig(self):
return {"build.mcu": "nrf52840"}
def AppendUnique(self, **kwargs):
for name, values in kwargs.items():
for value in values:
if value not in self.setdefault(name, []):
self[name].append(value)
def BuildSources(self, *args):
self.setdefault("BUILT_SOURCES", []).append(args)
def subst(self, value):
return value.replace("$PROJECT_DIR", str(ROOT))
def AddBuildMiddleware(self, callback, pattern):
self.setdefault("MIDDLEWARE", []).append((callback, pattern))
base = dict(BUILD_FLAGS=["-DUSE_CC310_HW_CRYPTO=1"],
SRC_FILTER=["+<../examples/simple_repeater>"], CPPDEFINES=[])
excluded = BuildEnvironment(**base, PIOENV="RAK_4631_companion_radio_full")
before = dict(excluded)
TRIM.install(excluded)
self.assertEqual(excluded, before)
active = BuildEnvironment(BUILD_FLAGS=list(base["BUILD_FLAGS"]),
SRC_FILTER=list(base["SRC_FILTER"]), CPPDEFINES=[],
PIOENV="RAK_4631_repeater")
with patch.object(TRIM, "supported_cc310", return_value=True):
TRIM.install(active)
self.assertTrue(active["MESH_NRF52_FLASH_TRIM_ACTIVE"])
self.assertIn("-Wl,--wrap=CRYS_ECPKI_GetEcDomain", active["LINKFLAGS"])
self.assertEqual(active["BUILT_SOURCES"][0][-1], "+<Cc310DomainTrim.c>")
self.assertEqual(active["MIDDLEWARE"][0][1], "*Adafruit_VL53L0X*/*.cpp")
@unittest.skipUnless(shutil.which("cc"), "a native C compiler is required")
def test_wrapped_domain_preserves_p256_and_rejects_unused_curves(self):
fixture = ROOT / "test/fixtures/nrf52_flash_trim"
with tempfile.TemporaryDirectory() as temp_dir:
executable = Path(temp_dir) / "cc310-trim-test"
subprocess.run(["cc", "-std=c99", "-Wall", "-Wextra", "-Werror",
"-DMESH_NRF52_FLASH_TRIM=1", "-I" + str(fixture),
str(ROOT / "src/helpers/nrf52/Cc310DomainTrim.c"),
str(fixture / "test_domain_trim.c"), "-o", str(executable)], check=True)
subprocess.run([str(executable)], check=True)
def test_companion_keeps_small_crypto_stack_fonts_reader_and_usb_transports(self):
for platform, mcu, expected in (("nordicnrf52", "nrf52840", True),
("nordicnrf52", "nrf52832", True),
("espressif32", "esp32s3", False)):
with self.subTest(platform=platform, mcu=mcu):
defines = TRIM.companion_trim_policy(platform, mcu,
"+<*> +<../examples/companion_radio/*.cpp>")
self.assertEqual(bool(defines), expected)
if not expected:
continue
self.assertEqual(defines["MESH_NRF52_COMPANION_TRIM"], 1)
self.assertEqual(defines["ED25519_SMALL_BASE"], 1)
self.assertEqual(defines["ED25519_COMPACT_SHA512"], 1)
self.assertEqual(defines["ED25519_COMPACT_SCALAR"], 1)
self.assertEqual(defines["CFG_TUD_MSC"], 0)
self.assertEqual(defines["CFG_TUD_HID"], 0)
for name in ("MESH_NRF52_FLASH_TRIM", "ED25519_COMPACT_BASE",
"CFG_TUD_CDC", "CFG_TUD_DFU",
"CFG_TUD_DFU_RUNTIME", "COMPANION_FEATURE_READER"):
self.assertNotIn(name, defines)
self.assertFalse(any(name.startswith("CFG_TUH") for name in defines))
def test_companion_role_requires_selected_source_and_honors_explicit_options(self):
for sources in ("+<*>", "+<../examples/simple_secure_chat>",
"+<../examples/companion_radio> -<../examples/companion_radio>",
"+<../examples/companion_radio> +<../examples/simple_repeater>"):
self.assertEqual(TRIM.companion_trim_policy("nordicnrf52", "nrf52840", sources), {})
for flags in ("-DMESH_NRF52_COMPANION_TRIM=0", "-UMESH_NRF52_COMPANION_TRIM",
"-DMESH_NRF52_FLASH_TRIM=0"):
self.assertEqual(TRIM.companion_trim_policy("nordicnrf52", "nrf52840",
"+<../examples/companion_radio>", flags), {})
result = TRIM.companion_trim_policy("nordicnrf52", "nrf52840",
"+<../examples/companion_radio>", "-DCFG_TUD_MSC=1 -USSD1306_NO_SPLASH")
self.assertNotIn("CFG_TUD_MSC", result)
self.assertNotIn("SSD1306_NO_SPLASH", result)
result = TRIM.companion_trim_policy("nordicnrf52", "nrf52840",
"+<../examples/companion_radio>",
"-DED25519_SMALL_BASE=0 -UED25519_COMPACT_SHA512 -UED25519_COMPACT_SCALAR")
for name in ("ED25519_SMALL_BASE", "ED25519_COMPACT_SHA512", "ED25519_COMPACT_SCALAR"):
self.assertNotIn(name, result)
for value in (0, 1):
result = TRIM.companion_trim_policy("nordicnrf52", "nrf52840",
"+<../examples/companion_radio>", "-DED25519_COMPACT_BASE=%d" % value)
self.assertNotIn("ED25519_SMALL_BASE", result)
@unittest.skipUnless(shutil.which("cc"), "a native C compiler is required")
def test_companion_uses_same_p256_domain_wrapper(self):
fixture = ROOT / "test/fixtures/nrf52_flash_trim"
with tempfile.TemporaryDirectory() as temp_dir:
executable = Path(temp_dir) / "cc310-companion-test"
subprocess.run(["cc", "-std=c99", "-Wall", "-Wextra", "-Werror",
"-DMESH_NRF52_COMPANION_TRIM=1", "-I" + str(fixture),
str(ROOT / "src/helpers/nrf52/Cc310DomainTrim.c"),
str(fixture / "test_domain_trim.c"), "-o", str(executable)], check=True)
subprocess.run([str(executable)], check=True)
if __name__ == "__main__":
unittest.main()