"""Exercise embedded-bootloader build identity and packaged-image gates without PIO.""" import argparse import hashlib import importlib.util import json import os from pathlib import Path import struct import subprocess import sys import tempfile from types import SimpleNamespace import unittest from unittest import mock import zipfile ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / "scripts")) sys.path.insert(0, str(ROOT / "tools/mota")) import motalib as ml import qualify_nrf52_embedded_bootloader as qualifier SPEC = importlib.util.spec_from_file_location( "embedded_variant_recipe", ROOT / "scripts/firmware_build_recipe.py") recipe = importlib.util.module_from_spec(SPEC) SPEC.loader.exec_module(recipe) TARGET = "RAK_4631_repeater" VERSION = "v1.18.0.1-halo-keymind-cascade-dev-12345678" class BuildIdentityTests(unittest.TestCase): def bash(self, script, *arguments): environment = os.environ.copy() environment.pop("MESHCORE_NRF52_EMBED_BOOTLOADER", None) environment.pop("MESHCORE_NRF52_LEGACY_BOOTLOADER", None) return subprocess.run( ["bash", "-c", 'source "$1/build_legacy.sh"\n' 'pio() { echo FORBIDDEN_PLATFORMIO >&2; return 99; }\n' + script, "embedded-variant", str(ROOT), *arguments], cwd=ROOT, env=environment, text=True, capture_output=True, timeout=10) def test_option_accepts_only_explicit_pinned_release_and_retains_target(self): result = self.bash(''' shift parse_cli_options "$@" || exit $? printf '%s\n' "$MESHCORE_NRF52_EMBED_BOOTLOADER" "${PARSED_COMMAND_ARGS[@]}" ''', "build-firmware", TARGET, "--with-bootloader", "2.4.12") self.assertEqual(result.returncode, 0, result.stdout + result.stderr) self.assertEqual(result.stdout.splitlines(), ["2.4.12", "build-firmware", TARGET]) for arguments in (("--with-bootloader",), ("--with-bootloader", "2.4.11"), ("--with-bootloader", "latest")): with self.subTest(arguments=arguments): result = self.bash('shift\nparse_cli_options "$@"\n', *arguments) self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr) self.assertIn("pinned release 2.4.12", result.stdout + result.stderr) def filename(self, *, embedded=False, ota=True, infix="", full=False): return self.bash(''' PIO_ENV_PLATFORM_BY_NAME[RAK_4631_repeater]=NRF52_PLATFORM PIO_ENV_OTA_BY_NAME[RAK_4631_repeater]=$3 BUILD_PROFILE_FOR_TARGET=auto FIRMWARE_FILENAME_INFIX=$4 ESP32_FULL_BUILD=$5 if [ "$2" = yes ]; then export MESHCORE_NRF52_EMBED_BOOTLOADER=2.4.12; fi embedded_bootloader_artifact_target RAK_4631_repeater || exit $? get_firmware_filename RAK_4631_repeater "$6" ''', "yes" if embedded else "no", "1" if ota else "0", infix, "1" if full else "0", VERSION) def test_ordinary_names_and_ota_classification_survive_publication_suffix(self): for embedded in (False, True): for ota in (False, True): with self.subTest(embedded=embedded, ota=ota): result = self.filename(embedded=embedded, ota=ota) self.assertEqual(result.returncode, 0, result.stdout + result.stderr) artifact = TARGET + ("-with-bootloader-2.4.12" if embedded else "") filename = artifact + ("-ota" if ota else "") + "-" + VERSION self.assertEqual(result.stdout.splitlines(), [artifact, filename]) def test_full_ota_profile_keeps_both_publication_markers(self): for embedded in (False, True): for infix, full in (("full-ota", False), ("full", True), ("full-logging", True), ("full-usb-wifi", True)): with self.subTest(embedded=embedded, infix=infix, full=full): result = self.filename(embedded=embedded, infix=infix, full=full) self.assertEqual(result.returncode, 0, result.stdout + result.stderr) artifact = TARGET + ("-with-bootloader-2.4.12" if embedded else "") expected_infix = infix if infix.endswith("-ota") else infix + "-ota" self.assertEqual(result.stdout.splitlines(), [artifact, artifact + "-" + expected_infix + "-" + VERSION]) def test_invalid_inherited_release_cannot_create_publishable_name(self): result = self.bash(''' export MESHCORE_NRF52_EMBED_BOOTLOADER=2.4.11 embedded_bootloader_artifact_target RAK_4631_repeater ''') self.assertNotEqual(result.returncode, 0) self.assertEqual(result.stdout, "") class PackagedApplicationTests(unittest.TestCase): def setUp(self): self.temporary = tempfile.TemporaryDirectory(prefix="mesh-embedded-package-") self.addCleanup(self.temporary.cleanup) self.directory = Path(self.temporary.name) self.resource = self.directory / "bootloader-migration-resource" self.resource.mkdir() self.manifest_path = self.directory / "firmware.capabilities.json" self.manifest = {"platform": "NRF52_PLATFORM", "verified": True, "target": TARGET, "artifact_target": TARGET + "-with-bootloader-2.4.12", "capabilities": ["ota.update.lora", "bootloader.embedded"], "source_commit": "a" * 40} self.manifest_path.write_text(json.dumps(self.manifest)) self.candidate = (struct.pack("