import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
HOST = "crates/hclient-core/src/host.rs"
NATIVE = "crates/hclient-native/src/connect.rs"
H3 = "crates/hclient-h3/src/lib.rs"
BODY = """ host.strip_prefix('[')
.and_then(|inner| inner.strip_suffix(']'))
.unwrap_or(host)"""
MUTATIONS = [
(
"M1 the TLS server name keeps the URI's brackets",
NATIVE,
"server_name: hclient_core::bare_host(host),",
"server_name: host,",
1,
),
(
"M2 the QUIC server name keeps the URI's brackets",
H3,
".connect_with(cfg, addr, hclient_core::bare_host(&key.host))",
".connect_with(cfg, addr, &key.host)",
1,
),
(
"M3 the h3 literal shortcut is asked about the bracketed host",
H3,
"if let Ok(ip) = hclient_core::bare_host(host).parse::<std::net::IpAddr>() {",
"if let Ok(ip) = host.parse::<std::net::IpAddr>() {",
1,
),
(
"M4 the strip fires on every host, bracketed or not",
HOST,
BODY,
" host.get(1..host.len().saturating_sub(1)).unwrap_or(host)",
1,
),
(
"M5 only the opening bracket is stripped",
HOST,
BODY,
" host.strip_prefix('[').unwrap_or(host)",
1,
),
(
"M6 brackets are trimmed repeatedly rather than one pair",
HOST,
BODY,
" host.trim_start_matches('[').trim_end_matches(']')",
1,
),
(
"M7 a bracketed empty host is handed back bracketed",
HOST,
BODY,
""" host.strip_prefix('[')
.and_then(|inner| inner.strip_suffix(']'))
.filter(|inner| !inner.is_empty())
.unwrap_or(host)""",
1,
),
]
def run_suite():
return subprocess.run(
[
"cargo",
"nextest",
"run",
"--workspace",
"--all-features",
"--no-fail-fast",
],
cwd=ROOT,
capture_output=True,
text=True,
)
def failing_tests(out):
names = []
for line in (out.stdout + out.stderr).splitlines():
if "FAIL" in line and "::" in line:
names.append(line.split()[-1].strip())
return sorted(set(names))
def main():
only = sys.argv[1:]
for label, filename, find, replace, expected in MUTATIONS:
if only and not any(label.startswith(o) for o in only):
continue
path = ROOT / filename
original = path.read_text()
count = original.count(find)
if count != expected:
print(f"{label}: ANCHOR MISMATCH — matched {count}, expected {expected}")
continue
path.write_text(original.replace(find, replace))
try:
out = run_suite()
if out.returncode == 0:
print(f"{label}: SURVIVED (anchors {count})")
else:
dead = failing_tests(out)
if not dead:
print(f"{label}: KILLED — build failure (anchors {count})")
else:
print(f"{label}: KILLED by {', '.join(dead[:40])} (anchors {count})")
finally:
path.write_text(original)
sys.stdout.flush()
if __name__ == "__main__":
main()