Files
MeshCore/test/test_indicator_repeater_profile.py
T

139 lines
6.7 KiB
Python

"""Resolve the real Indicator repeater recipes without PlatformIO or hardware."""
import configparser
import fnmatch
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import unittest
ROOT = Path(__file__).resolve().parents[1]
N8 = "SenseCapIndicator-LoRa_repeater"
N16 = "SenseCapIndicator-LoRa-N16R2_repeater"
class IndicatorRepeaterProfileTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.config = configparser.ConfigParser(interpolation=None, strict=False)
cls.config.read([ROOT / "platformio.ini",
ROOT / "variants/sensecap_indicator-espnow/platformio.ini"], encoding="ascii")
def option(self, section, option, depth=0):
self.assertLess(depth, 30)
if self.config.has_option(section, option):
value = self.config.get(section, option)
return re.sub(r"\$\{([^}]+)\}", lambda match:
self.option(*match[1].rsplit(".", 1), depth=depth + 1), value)
for parent in self.config.get(section, "extends", fallback="").split(","):
if parent.strip():
try:
return self.option(parent.strip(), option, depth + 1)
except KeyError:
pass
raise KeyError((section, option))
def flags(self, target):
return [line.strip() for line in self.option("env:" + target, "build_flags").splitlines()
if line.strip() and not line.lstrip().startswith((";", "#"))]
def definitions(self, target):
definitions = {}
for flag in self.flags(target):
match = re.fullmatch(r"-([DU])\s*(\w+)(?:=(.*))?", flag)
if not match:
continue
kind, name, value = match.groups()
if kind == "U":
definitions.pop(name, None)
else:
definitions[name] = (value or "1").strip("'")
return definitions
def selected(self, target, source):
result = False
rules = self.option("env:" + target, "build_src_filter")
for action, pattern in re.findall(r"([+-])<([^>]+)>", rules):
if "/" not in pattern and "/" in source:
continue
match = fnmatch.fnmatchcase(source, pattern)
if not any(character in pattern for character in "*?["):
match = match or source.startswith(pattern.rstrip("/") + "/")
if match:
result = action == "+"
return result
def test_real_radio_and_display_are_retained_in_both_memory_variants(self):
for target in (N8, N16):
with self.subTest(target=target):
definitions = self.definitions(target)
self.assertIn("SENSECAP_INDICATOR_LORA", definitions)
self.assertNotIn("MESH_PRIMARY_ESPNOW", definitions)
self.assertNotIn("DISABLE_WIFI_OTA", definitions)
self.assertEqual(definitions["WRAPPER_CLASS"], "IndicatorSX1262Wrapper")
self.assertEqual(definitions["DISPLAY_CLASS"], "SCIndicatorDisplay")
self.assertEqual(definitions["INDICATOR_WIFI_FONT_RECOVERY"], "1")
self.assertEqual(definitions["MOTA_HW_ID"], '"' + target[:-9] + '"')
self.assertEqual([definitions[key] for key in ("P_LORA_NSS", "P_LORA_RESET",
"P_LORA_BUSY", "P_LORA_DIO_1")], ["64", "65", "66", "67"])
for source in ("../examples/simple_repeater/main.cpp", "../examples/simple_repeater/MyMesh.cpp",
"../variants/sensecap_indicator-espnow/target.cpp",
"../variants/sensecap_indicator-espnow/IndicatorRadioHal.cpp",
"../variants/sensecap_indicator-espnow/IndicatorFontClient.cpp",
"helpers/ui/LGFXDisplay.cpp", "helpers/ota/OtaContext.cpp",
"helpers/esp32/WebConfigServer.cpp"):
self.assertTrue(self.selected(target, source), source)
for source in ("helpers/esp32/ESPNOWRadio.cpp", "../examples/companion_radio/main.cpp"):
self.assertFalse(self.selected(target, source), source)
self.assertIn("AsyncElegantOTA", self.option("env:" + target, "lib_deps"))
def test_flash_partition_and_psram_modes_are_explicit(self):
for target, flash, mode, psram, table, size in (
(N8, "8MB", "qio_opi", "opi", "dual_ota_2560k_preserve_spiffs.csv", "2621440"),
(N16, "16MB", "qio_qspi", "qspi", "dual_ota_6400k_preserve_spiffs.csv", "6553600")):
with self.subTest(target=target):
for key, expected in (("board_upload.flash_size", flash),
("board_build.arduino.memory_type", mode), ("board_build.psram_type", psram),
("board_upload.maximum_size", size)):
self.assertEqual(self.option("env:" + target, key), expected)
self.assertEqual(self.option("env:" + target, "board_build.partitions"),
"variants/sensecap_indicator-espnow/" + table)
def test_actual_full_overlay_uses_lora_not_overridden_primary_espnow_flag(self):
rows = [["env:" + target, [["build_flags", self.flags(target)]]] for target in (N8, N16)]
environment = dict(os.environ, INDICATOR_TEST_CONFIG=json.dumps(rows))
script = r'''
set -euo pipefail
source build.sh
PIO_CONFIG_JSON=$INDICATOR_TEST_CONFIG
for target in SenseCapIndicator-LoRa_repeater SenseCapIndicator-LoRa-N16R2_repeater; do
PIO_ENV_PLATFORM_BY_NAME[$target]=ESP32_PLATFORM
PIO_ENV_BOARD_BY_NAME[$target]=esp32-s3-devkitc-1
PIO_ENV_FULL_BUILD_BY_NAME[$target]=1
PIO_ENV_MQTT_BY_NAME[$target]=0
supports_esp32_full_shared_espnow "$target"
is_esp32_full_only_bulk_target "$target"
[[ $(get_exact_identity_full_pio_env "$target") == "$target" ]]
ESP32_FULL_BUILD=1
PLATFORMIO_BUILD_FLAGS=''
PLATFORMIO_BUILD_SRC_FILTER=''
apply_esp32_full_shared_bridge_profile "$target"
[[ $PLATFORMIO_BUILD_FLAGS == *-DWITH_UDP_BRIDGE=1* ]]
[[ $PLATFORMIO_BUILD_FLAGS == *-DWITH_ESPNOW_BRIDGE=1* ]]
[[ $PLATFORMIO_BUILD_SRC_FILTER == *UDPBridgeSettings.cpp* ]]
done
PIO_CONFIG_JSON='[["env:bad", [["build_flags", ["-DMESH_PRIMARY_ESPNOW=1", "-UMESH_PRIMARY_ESPNOW", "-DMESH_PRIMARY_ESPNOW=1"]]]]]'
pio_env_has_effective_define bad MESH_PRIMARY_ESPNOW
PIO_CONFIG_JSON='[["env:bad", [["build_flags", ["-DMESH_ESPNOW_RADIO=1"]]]]]'
pio_env_has_effective_define bad MESH_ESPNOW_RADIO
'''
result = subprocess.run(["bash", "-c", script], cwd=ROOT, env=environment,
capture_output=True, text=True, timeout=30)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
if __name__ == "__main__":
unittest.main()