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.
158 lines
7.0 KiB
Python
Executable File
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()
|