"""No-build contracts for evidence-bound firmware choice consolidation.""" from copy import deepcopy import json import os from pathlib import Path import shlex import subprocess import sys import tempfile import unittest ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / "scripts")) import consolidated_profiles as consolidated SOURCE = "a" * 40 CASES = ( dict(target="PicoW_companion_radio", legacy="PicoW_companion_radio_wifi", board="rpipicow", platform="RP2040_PLATFORM", key="combinedCompanion", capability="companion.picow.combined", marker="meshcore.capability.companion.picow.combined.v1", flags=["-DPICOW_COMBINED_COMPANION=1", "-DENABLE_USB_INTERFACE=1", "-DENABLE_WIFI_INTERFACE=1", "-DPIO_FRAMEWORK_ARDUINO_ENABLE_BLUETOOTH=1"]), dict(target="heltec_v4_repeater", legacy="heltec_v4_expansionkit_repeater", board="heltec_v4", platform="ESP32_PLATFORM", key="oledExpansionCompatible", capability="hardware.heltec_v4.oled_kit", marker="meshcore.capability.hardware.heltec_v4.oled_kit.v1", flags=["-DHELTEC_V4_OLED_KIT_COMPAT=1", "-DHELTEC_LORA_V4_OLED=1", "-DDISPLAY_CLASS=SSD1306Display", "-DENV_PIN_SDA=4", "-DENV_PIN_SCL=3"]), dict(target="heltec_v4_r8_tft_room_server", legacy="heltec_v4_r8_tft_portrait_room_server_observer_mqtt", board="heltec_v4_r8", platform="ESP32_PLATFORM", key="displayRotation", capability="display.rotation.runtime", marker="meshcore.capability.display.rotation.heltec_r8.v1", flags=["-DHELTEC_TFT_RUNTIME_ROTATION=1", "-DHELTEC_V4_R8_TFT=1", "-DDISPLAY_CLASS=ST7789LCDDisplay"]), dict(target="heltec_v4_tft_companion_radio_full_femon", legacy="heltec_v4_expansionkit_tft_companion_radio_full_femon", board="heltec_v4", platform="ESP32_PLATFORM", key="tftExpansionCompatible", capability="hardware.heltec_v4.tft_kit", marker="meshcore.capability.hardware.heltec_v4.tft_kit.v1", flags=["-DHELTEC_V4_TFT_KIT_COMPAT=1", "-DHELTEC_LORA_V4_TFT=1", "-DDISPLAY_CLASS=ST7789LCDDisplay"]), ) ACCESSORY_CASES = ( dict(base="muzi_base_uno", legacy="muzi_base_uno_superIO", board="muzi_base", marker="muzi_superio", roles=("ble", "full"), flag="MUZI_BASE_ACCESSORY_AUTODETECT", flags=["-DMUZI_BASE=1", "-DMUZI_BASE_ACCESSORY_AUTODETECT=1", "-DDISPLAY_CLASS=SH1107Display", "-DRADIO_CLASS=CustomSX1262", "-DUSE_SX1262=1"]), dict(base="muzi_base_duo", legacy="muzi_base_duo_superIO", board="muzi_base", marker="muzi_superio", roles=("ble", "full"), flag="MUZI_BASE_ACCESSORY_AUTODETECT", flags=["-DMUZI_BASE=1", "-DMUZI_BASE_ACCESSORY_AUTODETECT=1", "-DDISPLAY_CLASS=SH1107Display", "-DRADIO_CLASS=CustomLR1121", "-DUSE_LR1121=1"]), dict(base="LilyGo_T-Echo-Lite", legacy="LilyGo_T-Echo-Lite_non_shell", board="t-echo", marker="techo_lite", roles=("usb", "ble", "full"), flag="TECHO_LITE_DISPLAY_AUTODETECT", flags=["-DLILYGO_TECHO_LITE=1", "-DTECHO_LITE_DISPLAY_AUTODETECT=1", "-DDISPLAY_CLASS=GxEPDDisplay", "-DEINK_DISPLAY_MODEL=GxEPD2_122_T61"]), ) def environment(case): return dict(board=case["board"], build_flags=list(case["flags"])) def manifest(case, target=None, proof=True): return dict(target=target or case["target"], verified=True, platform=case["platform"], source_commit=SOURCE, build_profile="full", capabilities=[case["capability"]] if proof else [], verification=[dict(capability=case["capability"], present=True, source="packaged application", evidence=case["marker"])] if proof else []) def record(case, target=None, proof=True): row = manifest(case, target, proof) return dict(manifest=row, files=[row["target"] + ".bin"]) class ConsolidatedProfileTests(unittest.TestCase): def test_r8_rotation_proof_covers_all_four_supported_roles(self): case = next(case for case in CASES if case["key"] == "displayRotation") for role in ("repeater", "room_server", "sensor", "companion_radio_full"): target = "heltec_v4_r8_tft_" + role with self.subTest(target=target): self.assertEqual(consolidated.runtime_metadata(manifest(case, target), environment(case), SOURCE), dict(displayRotation=True, displayRotationSource=SOURCE)) for role in ("kiss_modem", "partition_expander", "other", "companion_radio_wifi"): with self.subTest(role=role), self.assertRaises(ValueError): consolidated.runtime_metadata(manifest(case, "heltec_v4_r8_tft_" + role), environment(case), SOURCE) def test_original_v4_tft_full_kit_alias_keeps_identical_wiring_and_battery_guard(self): from test_upstream_observer_recipes import Recipes from test_t114_autodisplay_recipes import defines recipes = Recipes() canonical = "env:heltec_v4_tft_companion_radio_full_femon" legacy = "env:heltec_v4_expansionkit_tft_companion_radio_full_femon" flags = defines(recipes.value(canonical, "build_flags")) self.assertEqual(flags, defines(recipes.value(legacy, "build_flags"))) for key, value in dict(HELTEC_V4_TFT_KIT_COMPAT="1", DISPLAY_CLASS="ST7789LCDDisplay", AUTO_SHUTDOWN_MILLIVOLTS="3400", MESHCORE_REQUIRES_COMPANION_RADIO_FULL="1", PIN_BOARD_SDA="4", PIN_BOARD_SCL="3", PIN_TFT_RST="18", PIN_TFT_LEDA_CTL="21", PIN_TFT_CS="15", PIN_TFT_DC="16", PIN_TFT_SCL="17", PIN_TFT_SDA="33").items(): self.assertEqual(flags.get(key), value, key) for option in ("board", "board_build.partitions", "board_build.flash_mode", "build_src_filter", "lib_deps"): self.assertEqual(recipes.value(canonical, option), recipes.value(legacy, option), option) def test_exact_current_configuration_and_packaged_marker_emit_source_bound_metadata(self): for case in CASES: with self.subTest(target=case["target"]): self.assertEqual(consolidated.runtime_metadata(manifest(case), environment(case), SOURCE), {case["key"]: True, case["key"] + "Source": SOURCE}) self.assertEqual(consolidated.runtime_metadata(manifest(case, proof=False), environment(case), SOURCE), {}, "Current recipes must not relabel a historical application") def test_metadata_rejects_unverified_or_wrong_application_evidence(self): for case in CASES: base = manifest(case) invalid_rows = ( {"verified": False}, {"platform": "OTHER_PLATFORM"}, {"target": "other_board_repeater"}, {"verification": []}, {"verification": [{**base["verification"][0], "present": False}]}, {"verification": [{**base["verification"][0], "present": 1}]}, {"verification": [{**base["verification"][0], "source": "linked image"}]}, {"verification": [{**base["verification"][0], "evidence": "a familiar driver name"}]}, {"verification": [{**base["verification"][0], "capability": "other.capability"}]}, ) for changed in invalid_rows: with self.subTest(target=case["target"], changed=changed), self.assertRaises(ValueError): consolidated.runtime_metadata({**base, **changed}, environment(case), SOURCE) def test_metadata_requires_full_matching_release_source(self): for case in CASES: for source in (None, "a" * 8, "g" * 40, "A" * 40, "b" * 40): with self.subTest(target=case["target"], source=source), self.assertRaises(ValueError): consolidated.runtime_metadata({**manifest(case), "source_commit": source}, environment(case), SOURCE) for release_source in (None, "a" * 8, "b" * 40): with self.subTest(target=case["target"], release_source=release_source), self.assertRaises(ValueError): consolidated.runtime_metadata(manifest(case), environment(case), release_source) def test_metadata_rejects_unknown_board_and_later_define_or_undefine_overrides(self): for case in CASES: with self.subTest(target=case["target"], board="unknown"), self.assertRaises(ValueError): consolidated.runtime_metadata(manifest(case), {**environment(case), "board": "unknown"}, SOURCE) for flag in case["flags"]: key = flag[2:].split("=", 1)[0] for override in ("-U" + key, "-D" + key + "=0"): changed = {**environment(case), "build_flags": case["flags"] + [override]} with self.subTest(target=case["target"], override=override), self.assertRaises(ValueError): consolidated.runtime_metadata(manifest(case), changed, SOURCE) def test_replacements_are_limited_to_known_exact_legacy_targets(self): for case in CASES: self.assertIn((case["target"], case["key"]), consolidated.replacements(case["legacy"])) self.assertEqual(consolidated.replacements(case["target"]), []) self.assertEqual(consolidated.replacements(case["legacy"] + "_custom"), []) for target in ("other_PicoW_companion_radio_usb", "heltec_v4_expansionkit_sensor", "heltec_v4_r8_tft_portrait_sensor_observer_mqtt", "other_without_display_repeater"): with self.subTest(target=target): self.assertEqual(consolidated.replacements(target), []) def test_configuration_replacement_requires_both_known_matching_boards(self): for case in CASES: canonical = "env:" + case["target"] legacy = "env:" + case["legacy"] envs = {canonical: environment(case), legacy: environment(case)} self.assertEqual(consolidated.configuration_replacement(case["legacy"], envs), case["target"]) for changed in ({legacy: environment(case)}, {canonical: environment(case)}, {canonical: environment(case), legacy: {**environment(case), "board": "other"}}, {canonical: {**environment(case), "board": "unknown"}, legacy: {**environment(case), "board": "unknown"}}): with self.subTest(target=case["target"], envs=changed): self.assertIsNone(consolidated.configuration_replacement(case["legacy"], changed)) def test_configuration_replacement_preserves_candidate_flag_overrides(self): for case in CASES: for flag in case["flags"]: key = flag[2:].split("=", 1)[0] for override in ("-U" + key, "-D" + key + "=0"): envs = {"env:" + case["legacy"]: environment(case), "env:" + case["target"]: {**environment(case), "build_flags": case["flags"] + [override]}} with self.subTest(target=case["target"], override=override): self.assertIsNone(consolidated.configuration_replacement(case["legacy"], envs)) def test_omission_requires_matching_profile_policy_and_real_packaged_successor(self): for case in CASES: legacy = record(case, case["legacy"], proof=False) canonical = record(case) legacy["manifest"]["capabilities"].append("sensor.profile.full") canonical["manifest"]["capabilities"].append("sensor.profile.full") before = deepcopy([legacy, canonical]) for inputs in ([legacy, canonical], [canonical, legacy]): with self.subTest(target=case["target"], order=[r["manifest"]["target"] for r in inputs]): self.assertEqual(consolidated.omit_redundant_records(inputs), [canonical]) self.assertEqual([legacy, canonical], before) self.assertEqual(consolidated.omit_redundant_records([legacy]), [legacy]) for changed in (dict(build_profile="reduced"), dict(verified=False), dict(verification=[]), dict(platform="OTHER_PLATFORM"), dict(target=case["target"] + "_custom"), dict(capabilities=[case["capability"], "sensor.profile.reduced"]), dict(verification=[{**canonical["manifest"]["verification"][0], "source": "linked image"}]), dict(verification=[{**canonical["manifest"]["verification"][0], "evidence": "other marker"}])): candidate = {**canonical, "manifest": {**canonical["manifest"], **changed}} with self.subTest(target=case["target"], changed=changed): self.assertEqual(consolidated.omit_redundant_records([legacy, candidate]), [legacy, candidate]) def test_historical_manifests_are_never_relabelled_by_current_recipe_defaults(self): for case in CASES: legacy, current_name_old_image = record(case, case["legacy"], False), record(case, proof=False) inputs = [legacy, current_name_old_image] before = deepcopy(inputs) self.assertEqual(consolidated.omit_redundant_records(inputs), inputs) self.assertEqual(inputs, before) def test_rc32_headless_choice_requires_exact_canonical_autodetection_marker(self): target, legacy_target = "heltec_rc32_repeater", "heltec_rc32_without_display_repeater" canonical = dict(manifest=dict(target=target, platform="ESP32_PLATFORM", verified=True, build_profile="full", capabilities=["display.autodetect"], verification=[dict( capability="display.autodetect", present=True, source="packaged application", evidence="meshcore.capability.display.autodetect.rc32.v1")]), files=[target + ".bin"]) legacy = dict(manifest=dict(target=legacy_target, platform="ESP32_PLATFORM", verified=True, build_profile="full", capabilities=[], verification=[]), files=[legacy_target + ".bin"]) before = deepcopy([legacy, canonical]) self.assertEqual(consolidated.omit_redundant_records([legacy, canonical]), [canonical]) self.assertEqual([legacy, canonical], before) for changed in (dict(verified=False), dict(platform="NRF52_PLATFORM"), dict(capabilities=[]), dict(verification=[]), dict(build_profile="reduced"), dict(capabilities=["display.autodetect", "sensor.profile.reduced"]), dict(verification=[{**canonical["manifest"]["verification"][0], "source": "linked image"}]), dict(verification=[{**canonical["manifest"]["verification"][0], "present": False}]), dict(verification=[{**canonical["manifest"]["verification"][0], "evidence": "meshcore.capability.display.autodetect.heltec_r8.v1"}])): candidate = {**canonical, "manifest": {**canonical["manifest"], **changed}} with self.subTest(changed=changed): self.assertEqual(consolidated.omit_redundant_records([legacy, candidate]), [legacy, candidate]) self.assertEqual(consolidated.omit_redundant_records([legacy]), [legacy]) def test_rc32_configuration_successor_requires_resolved_auto_display_recipe(self): target, legacy = "heltec_rc32_companion_radio_full", "heltec_rc32_without_display_companion_radio_full" flags = ["-DRC32_DISPLAY_AUTODETECT=1", "-DHELTEC_RC32=1", "-DDISPLAY_CLASS=NV3001BDisplay"] env = dict(board="heltec-rc32", build_flags=flags) envs = {"env:" + target: env, "env:" + legacy: env} self.assertEqual(consolidated.configuration_replacement(legacy, envs), target) for override in ("-URC32_DISPLAY_AUTODETECT", "-DRC32_DISPLAY_AUTODETECT=0", "-UHELTEC_RC32", "-DHELTEC_RC32=0", "-DDISPLAY_CLASS=NullDisplayDriver"): changed = {**envs, "env:" + target: {**env, "build_flags": flags + [override]}} with self.subTest(override=override): self.assertIsNone(consolidated.configuration_replacement(legacy, changed)) changed = {key: {**value, "board": "unknown"} for key, value in envs.items()} self.assertIsNone(consolidated.configuration_replacement(legacy, changed)) @unittest.skipIf(os.name == "nt", "Bash release contracts run on Linux/WSL") def test_bulk_shell_wiring_omits_aliases_preserves_direct_identity_and_demands_application_marker(self): config = [] setup, checks = [], [] for case in CASES: for target in (case["target"], case["legacy"]): config.append(["env:" + target, [["board", case["board"]], ["build_flags", case["flags"]]]]) setup.append("SUPPORTED_PIO_ENVS+=(" + shlex.quote(target) + ")") for mapping, value in (("PIO_ENV_PLATFORM_BY_NAME", case["platform"]), ("PIO_ENV_BOARD_BY_NAME", case["board"]), ("PIO_ENV_BUILD_BASE_BY_NAME", target)): setup.append(mapping + "[" + shlex.quote(target) + "]=" + shlex.quote(value)) checks.append("check_choice " + " ".join(shlex.quote(value) for value in ( case["legacy"], case["target"], case["platform"], case["capability"] + "=" + case["marker"]))) program = r''' set -euo pipefail source build.sh pio() { echo 'PlatformIO must not run in this test' >&2; return 99; } @SETUP@ ALL_PIO_ENVS=("${SUPPORTED_PIO_ENVS[@]}") PIO_CONFIG_JSON=@CONFIG@ BUILD_PROFILE_FOR_TARGET=full check_choice() { local legacy=$1 canonical=$2 platform=$3 expected=$4 [ "$(get_consolidated_hardware_replacement "$legacy")" = "$canonical" ] is_redundant_bulk_build_target "$legacy" is_known_pio_env "$legacy" [ "$(get_pio_build_env "$legacy")" = "$legacy" ] ! get_consolidated_hardware_replacement "$canonical" BUILD_APPLICATION_EXPECTATIONS=() BUILD_EXPECTATIONS=() BUILD_CAPABILITIES=() BUILD_REDUCTIONS=() declare_build_capability_contract "$canonical" "$platform" [[ " ${BUILD_APPLICATION_EXPECTATIONS[*]} " == *" $expected "* ]] } @CHECKS@ PIO_CONFIG_JSON=@OVERRIDDEN@ @OVERRIDE_CHECKS@ ''' overridden = deepcopy(config) for case in CASES: row = next(row for row in overridden if row[0] == "env:" + case["target"]) row[1][1][1].append("-U" + case["flags"][0][2:].split("=", 1)[0]) program = program.replace("@SETUP@", "\n".join(setup)) program = program.replace("@CHECKS@", "\n".join(checks)) program = program.replace("@CONFIG@", shlex.quote(json.dumps(config))) program = program.replace("@OVERRIDDEN@", shlex.quote(json.dumps(overridden))) program = program.replace("@OVERRIDE_CHECKS@", "\n".join( "! get_consolidated_hardware_replacement " + shlex.quote(case["legacy"]) for case in CASES)) result = subprocess.run(["bash", "-c", program], cwd=ROOT, capture_output=True, text=True, timeout=30) self.assertEqual(result.returncode, 0, result.stdout + result.stderr) def test_actual_controls_generator_requires_packaged_proof_from_its_exact_config(self): import generate_picker_controls as controls family = "v1.18.0.1.preview1-aaaaaaaa" with tempfile.TemporaryDirectory(prefix="consolidated-controls-") as folder: stage = Path(folder) (stage / "companion").mkdir() (stage / "release-plan.json").write_text(json.dumps(dict( source=SOURCE, groups=[dict(key="companion", tag=family)]))) path = stage / "companion/TARGET-MANIFEST.json" for case in CASES: row = manifest(case) row.update(platformio_env=case["target"], files=[case["target"] + "-" + family + ".bin"]) path.write_text(json.dumps([row])) config = [["env:" + case["target"], [["board", case["board"]], ["build_flags", case["flags"]]]]] profile = controls.generate(stage, config)["profiles"][case["target"]] self.assertIs(profile[case["key"]], True) self.assertEqual(profile[case["key"] + "Source"], SOURCE) changed = deepcopy(config) changed[0][1][1][1].append("-U" + case["flags"][0][2:].split("=", 1)[0]) with self.subTest(target=case["target"], reason="overridden recipe"), self.assertRaises(ValueError): controls.generate(stage, changed) row["verification"] = [] path.write_text(json.dumps([row])) with self.subTest(target=case["target"], reason="missing packaged evidence"), self.assertRaises(ValueError): controls.generate(stage, config) row["capabilities"] = [] path.write_text(json.dumps([row])) historical = controls.generate(stage, config)["profiles"][case["target"]] self.assertNotIn(case["key"], historical) self.assertNotIn(case["key"] + "Source", historical) def test_generated_accessory_full_targets_require_exact_radio_board_and_optional_display_recipe(self): for case in ACCESSORY_CASES: canonical = case["base"] + "_companion_radio_full" for base_role in ("usb", "ble"): canonical_base = case["base"] + "_companion_radio_" + base_role legacy_base = case["legacy"] + "_companion_radio_" + base_role env = dict(board=case["board"], build_flags=case["flags"]) envs = {"env:" + canonical_base: env, "env:" + legacy_base: env} for role in case["roles"]: target = case["legacy"] + "_companion_radio_" + role # A BLE request needs its real BLE source; generated Full # can resolve either attached-transport source recipe. role_envs = {**envs, "env:" + target: env} if role != "full" else envs with self.subTest(target=target, canonical_base=canonical_base): self.assertEqual(consolidated.configuration_replacement(target, role_envs), canonical) for changed in ( {**env, "board": "unknown"}, {**env, "build_flags": case["flags"] + ["-U" + case["flag"]]}, {**env, "build_flags": case["flags"] + ["-D" + case["flag"] + "=0"]}, {**env, "build_flags": case["flags"] + ["-DDISPLAY_CLASS=NullDisplayDriver"]}): invalid = {**role_envs, "env:" + canonical_base: changed} with self.subTest(target=target, changed=changed): self.assertIsNone(consolidated.configuration_replacement(target, invalid)) if case["base"].startswith("muzi_base_"): other = next(row for row in ACCESSORY_CASES if row["board"] == "muzi_base" and row["base"] != case["base"]) wrong_radio = {**envs, "env:" + canonical_base: dict(board=case["board"], build_flags=other["flags"])} with self.subTest(target=canonical, wrong_radio=other["base"]): self.assertIsNone(consolidated.configuration_replacement( case["legacy"] + "_companion_radio_full", wrong_radio)) other_name = other["base"] + "_companion_radio_" + base_role different_hardware = {"env:" + other_name: env, "env:" + legacy_base: env} self.assertIsNone(consolidated.configuration_replacement( case["legacy"] + "_companion_radio_full", different_hardware)) def test_accessory_full_omission_requires_packaged_display_and_both_linked_transports(self): for case in ACCESSORY_CASES: canonical_target = case["base"] + "_companion_radio_full" display = dict(capability="display.autodetect", present=True, source="packaged application", evidence="meshcore.capability.display.autodetect." + case["marker"] + ".v1") transport = [dict(capability=key, present=True, source="linked image", evidence=symbol) for key, symbol in (("companion.usb", "+++MESHCORE-TERM-START"), ("companion.bluetooth", "Companion: starting Bluetooth"))] canonical = dict(manifest=dict(target=canonical_target, platform="NRF52_PLATFORM", verified=True, build_profile="full", capabilities=["display.autodetect", "companion.usb", "companion.bluetooth"], verification=[display, *transport]), files=[canonical_target + ".uf2"]) for role in case["roles"]: target = case["legacy"] + "_companion_radio_" + role legacy = dict(manifest=dict(target=target, platform="NRF52_PLATFORM", verified=True, build_profile="full" if role == "full" else "standard", capabilities=[], verification=[]), files=[target + ".uf2"]) before = deepcopy([legacy, canonical]) with self.subTest(target=target): self.assertEqual(consolidated.omit_redundant_records([legacy, canonical]), [canonical]) self.assertEqual([legacy, canonical], before) self.assertEqual(consolidated.omit_redundant_records([legacy]), [legacy]) invalid = [dict(verified=False), dict(platform="ESP32_PLATFORM"), dict(verification=[]), dict(capabilities=["companion.usb", "companion.bluetooth"]), dict(verification=[{**display, "source": "linked image"}, *transport]), dict(verification=[{**display, "evidence": "meshcore.capability.display.autodetect.rc32.v1"}, *transport])] for key in ("companion.usb", "companion.bluetooth"): invalid.append(dict(capabilities=[cap for cap in canonical["manifest"]["capabilities"] if cap != key])) for change in (dict(present=False), dict(source="packaged application"), dict(evidence="")): invalid.append(dict(verification=[{**row, **change} if row["capability"] == key else row for row in canonical["manifest"]["verification"]])) if role == "full": invalid.append(dict(build_profile="standard")) if case["base"].startswith("muzi_base_"): other = "muzi_base_duo" if case["base"] == "muzi_base_uno" else "muzi_base_uno" invalid.append(dict(target=other + "_companion_radio_full")) for changed in invalid: candidate = {**canonical, "manifest": {**canonical["manifest"], **changed}} with self.subTest(target=target, changed=changed): self.assertEqual(consolidated.omit_redundant_records([legacy, candidate]), [legacy, candidate]) if __name__ == "__main__": unittest.main()