#!/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()