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.
This commit is contained in:
Executable
+157
@@ -0,0 +1,157 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user