Files
ESP32_Serial_Swiss_Army_Knife/tests/mdns_membership/run.py
T
Commander1024 8902b25d78 Complete Phase 12 dual-stack networking
Add IPv6-aware Wi-Fi state, HTTPS/SSH listeners, mDNS service
reconciliation, and browser Wi-Fi administration.

Include a guarded build-local fix for mDNS 1.12.0 membership handling,
focused regression suites, and Phase 12 acceptance documentation.
2026-09-20 22:35:34 +02:00

158 lines
7.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""Host-only regression and configure integration tests; no firmware build."""
import hashlib
import os
import resource
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
ROOT = Path(__file__).resolve().parents[2]
HERE = Path(__file__).resolve().parent
COMPONENT = ROOT / "managed_components/espressif__mdns"
HELPER = ROOT / "cmake/mdns_membership.py"
def run(command, *, succeeds=True):
result = subprocess.run([str(item) for item in command], capture_output=True, text=True)
if (result.returncode == 0) != succeeds:
raise AssertionError(f"Unexpected result: {command}\n{result.stdout}\n{result.stderr}")
return result.stdout + result.stderr
def function(source, signature):
start = source.index(signature)
brace = source.index("{", start)
depth = 1
end = brace + 1
while depth:
depth += (source[end] == "{") - (source[end] == "}")
end += 1
return source[start:end] + "\n"
def extract(source):
# Keep the original provenance header and actual state declarations too.
header = source[:source.index("#include")]
state = source[source.index("enum interface_protocol"):source.index("static const char *TAG")]
signatures = ["static esp_err_t pcb_init(void)", "static void pcb_deinit(void)",
"bool mdns_priv_if_ready(", "static bool is_any_pcb_in_use(void)",
"static void pcb_if_deinit(", "static esp_err_t pcb_if_init("]
return header + state + "\n".join(function(source, name) for name in signatures)
def cmake_fixture(work, component, mode, succeeds=True):
fixture = work / f"cmake-{mode}"
fixture.mkdir()
(fixture / "dummy.c").write_text("int dummy;\n")
source = component / "mdns_networking_lwip.c"
sources = f'"{source}"'
if mode == "missing":
sources = ""
if mode == "duplicate":
sources += " " + sources
# Test relative as well as absolute target source properties.
if mode == "relative":
shutil.copyfile(source, fixture / source.name)
shutil.copyfile(component / "idf_component.yml", fixture / "idf_component.yml")
component = fixture
sources = source.name
version = "1.13.0" if mode == "version" else "1.12.0"
(fixture / "CMakeLists.txt").write_text(f'''cmake_minimum_required(VERSION 3.16)
project(mdns_overlay_fixture C)
add_library(mdns STATIC dummy.c {sources})
function(idf_component_get_property output component property)
if(property STREQUAL "COMPONENT_DIR")
set(value "{component}")
elseif(property STREQUAL "COMPONENT_LIB")
set(value mdns)
elseif(property STREQUAL "COMPONENT_VERSION")
set(value "{version}")
else()
message(FATAL_ERROR "Unexpected component property")
endif()
set(${{output}} "${{value}}" PARENT_SCOPE)
endfunction()
function(idf_build_get_property output property)
if(NOT property STREQUAL "PYTHON")
message(FATAL_ERROR "Unexpected build property")
endif()
set(${{output}} "{sys.executable}" PARENT_SCOPE)
endfunction()
set(CONFIG_MDNS_NETWORKING_SOCKET {"ON" if mode == "socket" else "OFF"})
include("{ROOT / 'cmake/mdns_membership.cmake'}")
get_target_property(sources mdns SOURCES)
file(WRITE "${{CMAKE_BINARY_DIR}}/selected.txt" "${{sources}}")
''')
build = fixture / "build"
output = run(["cmake", "-S", fixture, "-B", build], succeeds=succeeds)
if not succeeds:
assert "mDNS" in output, output
return
selected = (build / "selected.txt").read_text().split(";")
if mode == "socket":
assert str(source) in selected
assert not (build / "mdns_membership").exists()
else:
overlay = build / "mdns_membership/mdns_networking_lwip.c"
assert selected == ["dummy.c", str(overlay)], selected
before = overlay.stat().st_mtime_ns
run(["cmake", "-S", fixture, "-B", build])
assert overlay.stat().st_mtime_ns == before
def main():
resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
original = (COMPONENT / "mdns_networking_lwip.c").read_bytes()
with tempfile.TemporaryDirectory(prefix="mdns-membership-") as directory:
work = Path(directory)
overlay = work / "overlay/mdns_networking_lwip.c"
run([sys.executable, HELPER, COMPONENT, overlay])
patched = overlay.read_text()
assert patched[:patched.index("#include")] == original.decode()[:original.decode().index("#include")]
timestamp = overlay.stat().st_mtime_ns
run([sys.executable, HELPER, COMPONENT, overlay])
assert overlay.stat().st_mtime_ns == timestamp
(work / "actual_functions.inc").write_text(extract(patched))
executable = work / "test"
command = [os.environ.get("CC", "cc"), "-std=c11", "-Wall", "-Wextra", "-Werror",
"-pedantic", "-I", work, HERE / "test.c", "-o", executable]
run(command)
print(run([executable]), end="")
# Prove the harness detects each original bug independently.
for name in ("pcb_if_deinit", "pcb_if_init"):
signature = ("static void " if name.endswith("deinit") else "static esp_err_t ") + name + "("
mutated = patched.replace(function(patched, signature), function(original.decode(), signature))
(work / "actual_functions.inc").write_text(extract(mutated))
run(command)
run([executable], succeeds=False)
print("PASS: both original defects independently fail the same harness")
copied = work / "component"
copied.mkdir()
shutil.copyfile(COMPONENT / "idf_component.yml", copied / "idf_component.yml")
(copied / "mdns_networking_lwip.c").write_bytes(original + b"\n")
output = run([sys.executable, HELPER, copied, work / "rejected.c"], succeeds=False)
assert "SHA-256 mismatch" in output and not (work / "rejected.c").exists()
(copied / "mdns_networking_lwip.c").write_bytes(original)
manifest = (copied / "idf_component.yml").read_text()
(copied / "idf_component.yml").write_text(manifest.replace("version: 1.12.0", "version: 1.13.0"))
output = run([sys.executable, HELPER, copied, work / "rejected.c"], succeeds=False)
assert "exactly version 1.12.0" in output and not (work / "rejected.c").exists()
(copied / "idf_component.yml").write_text(manifest)
for mode in ("absolute", "relative", "socket", "missing", "duplicate", "version"):
cmake_fixture(work, copied, mode, succeeds=mode not in ("missing", "duplicate", "version"))
# Also exercise hash failure through configure, not only the helper CLI.
(copied / "mdns_networking_lwip.c").write_bytes(original + b"\n")
cmake_fixture(work, copied, "hash", succeeds=False)
print("PASS: source/version guards, CMake replacement, relative paths, socket bypass, repeat configure")
assert hashlib.sha256((COMPONENT / "mdns_networking_lwip.c").read_bytes()).digest() == hashlib.sha256(original).digest()
print("PASS: managed networking source unchanged")
if __name__ == "__main__":
main()