mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-20 19:41:59 +00:00
112 lines
6.9 KiB
Python
112 lines
6.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Run the sole repeater ESP-NOW production lifecycle and CLI with fault injection."""
|
|
from pathlib import Path
|
|
import shutil
|
|
import re
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
from test_replay_reset_integration import extract_braced
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
class ESPNowRuntimeTests(unittest.TestCase):
|
|
def test_production_defaults_aliases_routing_retries_and_ota_exclusion(self):
|
|
compiler = shutil.which("g++") or shutil.which("clang++")
|
|
self.assertIsNotNone(compiler, "a host C++ compiler is required")
|
|
cli = (ROOT / "src/helpers/CommonCLI.cpp").read_text(encoding="ascii")
|
|
header = (ROOT / "examples/simple_repeater/MyMesh.h").read_text(encoding="ascii")
|
|
callback_header = (ROOT / "src/helpers/CommonCLI.h").read_text(encoding="ascii")
|
|
implementation = (ROOT / "examples/simple_repeater/MyMesh.cpp").read_text(encoding="ascii")
|
|
defaults_start = implementation.index("// bridge defaults")
|
|
defaults_end = implementation.index("// GPS defaults", defaults_start)
|
|
boot_start = implementation.index('mesh::hilStartupTrace("mesh_bridge_begin");')
|
|
boot_start = implementation.index("#if defined(WITH_BRIDGE)", boot_start)
|
|
boot_end = implementation.index('mesh::hilStartupTrace("mesh_bridge_ready");', boot_start)
|
|
lifecycle_signatures = (
|
|
"AbstractBridge* activeBridge() {", "const AbstractBridge* activeBridge() const {",
|
|
"bool startSharedEspNowBridgeIfReady()", "bool isBridgeRunning() const override",
|
|
"bool setBridgeState(bool enable) override",
|
|
"bool restartBridge() override",
|
|
)
|
|
routing = {}
|
|
for name in ("logRx", "logTx"):
|
|
start = implementation.index("void MyMesh::" + name + "(")
|
|
start = implementation.index("#ifdef WITH_MQTT_BRIDGE\n", start)
|
|
end = implementation.index(" if (_logging)", start)
|
|
routing[name] = implementation[start:end]
|
|
service_start = implementation.index("#if defined(WITH_ESPNOW_BRIDGE)",
|
|
implementation.index("MyMesh::servicePostMeshLoop()"))
|
|
service_end = implementation.index(" if (next_flood_advert", service_start)
|
|
pending_start = implementation.index("#if defined(WITH_BRIDGE)",
|
|
implementation.index("bool MyMesh::hasPendingWork()"))
|
|
pending_end = implementation.index(" if (radio_driver.isWatchdogObserving()", pending_start)
|
|
commands = "\n".join(extract_braced(cli, signature) for signature in (
|
|
"void CommonCLI::handleSetCmd(", "void CommonCLI::handleGetCmd("))
|
|
template = (ROOT / "test/fixtures/espnow_runtime.cpp").read_text(encoding="ascii")
|
|
for key, value in {
|
|
"DEFAULTS": implementation[defaults_start:defaults_end],
|
|
"BOOT": implementation[boot_start:boot_end],
|
|
"CALLBACK_FORWARDS": "\n".join(extract_braced(callback_header, signature) for signature in (
|
|
"virtual bool setEspNowBridgeState(bool enable)",
|
|
"virtual bool restartEspNowBridge()", "virtual bool isEspNowBridgeRunning()")),
|
|
"PARSERS": "\n".join(extract_braced(cli, signature) for signature in (
|
|
"static bool parseOnOffStrict(", "static bool configKeyEquals(")),
|
|
"ROUTE_RX": routing["logRx"], "ROUTE_TX": routing["logTx"],
|
|
"SERVICE": implementation[service_start:service_end],
|
|
"PENDING": implementation[pending_start:pending_end],
|
|
}.items():
|
|
template = template.replace("@" + key + "@", value)
|
|
for profile, macros in (
|
|
("merged", ["ESPNOW_BRIDGE_MERGED=1"]),
|
|
("legacy_dedicated", []),
|
|
("merged_default_on", ["ESPNOW_BRIDGE_MERGED=1", "ESPNOW_BRIDGE_DEFAULT_ON=1"]),
|
|
):
|
|
with self.subTest(profile=profile), tempfile.TemporaryDirectory(prefix="espnow-runtime-") as directory:
|
|
work = Path(directory)
|
|
flags = ["-DWITH_BRIDGE=1", "-DWITH_ESPNOW_BRIDGE=1",
|
|
*["-D" + macro for macro in macros]]
|
|
processed = subprocess.run([compiler, "-E", "-P", "-x", "c++", *flags, "-"],
|
|
input=commands, capture_output=True, text=True, check=True).stdout
|
|
processed_header = subprocess.run([compiler, "-E", "-P", "-x", "c++", *flags, "-"],
|
|
input=re.sub(r'^\s*#include[^\n]*', '', header, flags=re.M),
|
|
capture_output=True, text=True, check=True).stdout
|
|
lifecycle = "\n".join(extract_braced(processed_header, signature)
|
|
for signature in lifecycle_signatures)
|
|
sets = "\nelse ".join(extract_braced(processed, signature) for signature in (
|
|
'if (memcmp(config, "espnow.enabled ", 15) == 0)',
|
|
'if (memcmp(config, "bridge.enabled ", 15) == 0)',
|
|
'if (memcmp(config, "bridge.source ", 14) == 0)',
|
|
'if (memcmp(config, "bridge.channel ", 15) == 0)',
|
|
'if (memcmp(config, "bridge.secret ", 14) == 0)',
|
|
'if (memcmp(config, "bridge.format ", 14) == 0)',
|
|
))
|
|
gets = "\nelse ".join(extract_braced(processed, signature) for signature in (
|
|
'if (configKeyEquals(config, "bridge.type"))',
|
|
'if (configKeyEquals(config, "bridge.enabled"))',
|
|
'if (configKeyEquals(config, "bridge.running"))',
|
|
'if (configKeyEquals(config, "espnow.enabled"))',
|
|
'if (configKeyEquals(config, "espnow.running"))',
|
|
'if (configKeyEquals(config, "bridge.channel"))',
|
|
'if (configKeyEquals(config, "bridge.secret"))',
|
|
'if (configKeyEquals(config, "bridge.format"))',
|
|
))
|
|
source, binary = work / "main.cpp", work / "test"
|
|
source.write_text(template.replace("@SET_BRANCHES@", sets)
|
|
.replace("@GET_BRANCHES@", gets)
|
|
.replace("@LIFECYCLE@", lifecycle), encoding="ascii")
|
|
result = subprocess.run([compiler, "-std=c++17", "-Wall", "-Wextra", "-Werror",
|
|
"-fsanitize=address,undefined", "-fno-sanitize-recover=all", "-fno-pie", "-no-pie",
|
|
*flags, "-I" + str(ROOT / "src"), str(source), "-o", str(binary)],
|
|
capture_output=True, text=True, timeout=60)
|
|
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
|
result = subprocess.run([str(binary)], capture_output=True, text=True, timeout=10)
|
|
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
|
self.assertIn("ESP-NOW runtime checks passed", result.stdout)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|