#!/usr/bin/env python3 # MobileGL - scripts/ci/wait_boundary_negative_control.py # Copyright (c) 2025-2026 MobileGL-Dev # Licensed under the GNU Lesser General Public License v3.0: # https://www.gnu.org/licenses/gpl-3.0.txt # https://www.gnu.org/licenses/lgpl-3.0.txt # SPDX-License-Identifier: LGPL-3.0-only # End of Source File Header """E1: green/red/restored-green proof of real ClientSession wait boundaries. The peer holds apply until the actual transport parks or the emit call returns. VERB_BARRIER=0 must fail only the two owed waits; ordinary run-ahead draws need not fail. Each arm is checked from its own CTest JUnit testcase, never a global grep, stale library log, arbitrary nonzero exit, or timing-dependent pixel. """ import argparse import os from pathlib import Path import re import subprocess import sys import xml.etree.ElementTree as ET PREFIX = "RemoteWaitBoundaryControl." CASES = { "AppliedClassWaitsWithRunAheadCap": ("ApplierReset", 1, True, "E1 wait boundary: ApplierReset returned before apply"), "MissingServerCapKeepsClearLockstep": ("Clear", 0, True, "E1 wait boundary: Clear without server cap returned before apply"), "ServerCapAllowsClearToRunAhead": ("Clear", 1, False, ""), } def own_negative(text, expected_observation, diagnostic): return expected_observation in text and diagnostic in text def inspect(path, selected, barrier, negative, rc): root = ET.parse(path).getroot() cases = root.findall(".//testcase") names = [case.get("name") for case in cases] if len(names) != len(set(names)) or set(names) != set(selected): raise ValueError("selected testcase names are missing, duplicated or unexpected") for case in cases: name = case.get("name") if case.find("skipped") is not None or case.get("status") in {"notrun", "disabled"}: raise ValueError(f"{name}: selected testcase skipped or did not run") failed = case.find("failure") is not None or case.find("error") is not None op, cap, waits, diagnostic = CASES[name.removeprefix(PREFIX)] waited = int(waits and barrier == 1) observation = (f"E1 observation: op={op} cap={cap} barrier={barrier} parked={waited} " f"applied_before_return={waited} emitted=1 peer_seen=1") text = "\n".join(case.itertext()) if negative: if not failed or rc == 0: raise ValueError(f"{name}: disabled barrier did not fail the owed wait") if not own_negative(text, observation, diagnostic): raise ValueError(f"{name}: red lacks its own observed wait-boundary failure") elif failed or rc != 0 or observation not in text: raise ValueError(f"{name}: baseline/restored arm did not execute its green boundary proof") def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--ctest", default=os.environ.get("CTEST", "ctest")) parser.add_argument("--out", type=Path, required=True) args = parser.parse_args() args.out.mkdir(parents=True, exist_ok=True) all_cases = [PREFIX + name for name in CASES] negative_cases = [PREFIX + name for name, (_, _, waits, _) in CASES.items() if waits] ok = True for arm, barrier, negative, selected in ( ("baseline", 1, False, all_cases), ("control", 0, True, negative_cases), ("restored", 1, False, all_cases), ): # Never run a negative arm against a failed baseline. A failed negative # still proceeds through restoration and must not consume stale XML. if arm == "control" and not ok: continue result = args.out / f"e1-{arm}.xml" result.unlink(missing_ok=True) env = os.environ.copy() env["MOBILEGL_IPC_VERB_BARRIER"] = str(barrier) selector = "^(" + "|".join(re.escape(name) for name in selected) + ")$" command = [args.ctest, "-L", "unit", "-R", selector, "--no-tests=error", "--output-on-failure", "--output-junit", str(result)] try: run = subprocess.run(command, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, timeout=90) (args.out / f"e1-{arm}.out").write_text(run.stdout, encoding="utf-8") print(run.stdout, end="", flush=True) inspect(result, selected, barrier, negative, run.returncode) print(f"E1 {arm}: {'EXPECTED_RED' if negative else 'GREEN'} ({len(selected)} cases)", flush=True) except (OSError, ValueError, ET.ParseError, subprocess.TimeoutExpired) as exc: print(f"E1 FAILED: {arm}: {exc}", file=sys.stderr, flush=True) ok = False if ok: print("negative control E1: owed waits failed by their own observed boundary assertions; baseline and restoration green") return 0 if ok else 1 if __name__ == "__main__": raise SystemExit(main())