mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
P7 exit gate 1 asks for integration green "under Magma push and split". Until now it was not
merely unmet, it was UNMEASURABLE: every arm of mgl_itest_register_split_arms hardcoded
MOBILEGL_BACKEND_TYPE=DirectGLES (:2002/:2025/:2031) and MGL_ITEST_MAGMA_WIRE_ENVIRONMENT
hardcoded MOBILEGL_TRANSPORT=inproc (:2456), so the shared integration-split / -spawn / -tcp
labels held ZERO DirectVulkan entries and the 73 integration-magma-split cases only ever ran in
one process.
The registration macro now takes the backend as its first argument; the old name is a thin
wrapper, so all 32 call sites are untouched and every DirectGLES name, label and private log
path is byte-for-byte what it was. Two tiers come out of it, and the CMake comment states both:
* GATED - integration-magma-{split,spawn,tcp}. The six hand-laid Magma lanes (Fm, Buffers,
RunAhead, Caches, NamedBlit, P5fRsp) grow arms through one shared shape macro; the inproc
arm keeps its names, its labels and its log paths, so the CI steps and the two magma_*
checker scripts that name them by hand keep working unchanged.
* INFORMATIONAL - integration-magma-all-{split,spawn,tcp}, in no gating step. Every scenario
the split arms run, on Magma, on all three transports, generated by REPLAYING the macro's
recorded call list - so a scenario added to the split arms tomorrow joins the Magma census
the same day. Its job is to be able to go red while CI stays green; a case moves up into the
gated tier with the commit that retires its blocker.
The label spellings are chosen against ctest -L's regex (ID-131 again): the tier word goes in
the MIDDLE, because integration-magma-spawn-all WOULD have been selected by
-L integration-magma-spawn and pulled ~100 expected-red entries into a gated lane. Sub-labels
mirror it: the inproc arm keeps integration-magma-buffers, the others put the arm word first.
MagmaRunAheadScenario and MagmaWireCacheScenario stay on the inproc arm alone, and that is
MEASURED rather than assumed: with all three arms registered, all 21 entries red on spawn and
tcp with no Fatal in either role log - the first arms its hold through
ServerLoopInstance().SetBeforeRetireHookForTesting, a hook on THIS process's server loop, and
the second asserts transportName == "inproc" in its SetUp. Both are named in the CMakeLists and
in spawn_lane_parity.py rather than allowed to widen a tolerance.
split_log_paths.py's is_split prefixes gain DirectVulkan.Spawn. / .Tcp. - on those arms two
processes append to one path, so a shared log does not merely blur attribution, it interleaves
two sessions. spawn_lane_parity.py gains the Magma dimension, normalising only the ARM segment:
RunAhead.Credit1. and .Credit3. are two entries of one case and a tail-stripping normaliser
would fold them together and then report an equal-sized set that had lost one.
Measured on this tree (lavapipe): magma-split 73 unchanged and green, magma-spawn 52/52,
magma-tcp 52/52, the three informational lanes 102 each, GLES split 186 / spawn 102 / tcp 104
all unchanged and green. G14 against ~/w7/p7-before: 4155 to 4565, removed 0, added 410.
173 lines
9.0 KiB
Python
173 lines
9.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Exit gate 9.2: integration-spawn's case-name set equals integration-split's.
|
|
|
|
WHY A GATE AND NOT A COMMENT. The two lanes are registered from one macro, so today they agree
|
|
by construction - but a macro is one edit away from a second hand-written block, and a spawn lane
|
|
covering LESS than the split lane goes green while proving less. Nothing about a smaller green
|
|
lane looks wrong. This asserts the sets rather than the counts, because a count can agree while
|
|
the membership does not.
|
|
|
|
ARM PREFIXES ARE STRIPPED BEFORE COMPARING. The lanes deliberately differ in name -
|
|
`DirectGLES.Split.X` against `DirectGLES.Spawn.X` - and the sub-lane tails (`F1.`, `Ct.`) are
|
|
part of neither the case nor the arm, so both come off.
|
|
|
|
ONE SANCTIONED ASYMMETRY, LISTED HERE AND NOWHERE ELSE. `integration-split` also labels the
|
|
PUSH-MONOLITH arm (the CMakeLists explains why: the label means "the set the disaggregation gate
|
|
runs", not "runs inproc"), and that arm's scenario is monolith-only by construction. It cannot
|
|
have a spawn counterpart, so it is named as an exception rather than allowed to widen the
|
|
tolerance for everything else.
|
|
|
|
P7 PACKAGE L ADDS THE SAME QUESTION FOR MAGMA, IN TWO TIERS, AND THE NORMALISATION IS
|
|
DIFFERENT ON PURPOSE. The Espryt half above strips the sub-lane tails `F1.` and `Ct.` because
|
|
they name neither the case nor the arm. The Magma half must NOT: `RunAhead.Credit1.` and
|
|
`RunAhead.Credit3.` are two entries of the same case, differing in the credit the lane pins, and
|
|
a normaliser that dropped the tail would fold them together and then report an equal-sized set
|
|
that had silently lost one. So there the arm - and only the arm, the SECOND dotted segment - is
|
|
what comes off.
|
|
"""
|
|
import argparse
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
# Registered only on the push-monolith arm, which has no spawn counterpart by construction.
|
|
MONOLITH_ONLY = {"MonolithAttachmentClearScenario"}
|
|
|
|
CASE = re.compile(r"DirectGLES\.[A-Za-z0-9]+\.(?:F1\.|Ct\.)?([A-Za-z0-9_]+Scenario\.[A-Za-z0-9_]+)")
|
|
|
|
# `<Backend>.<Arm>.<everything else>` -> `<Backend>.<everything else>`. The arm is the only
|
|
# segment two arms of one lane are allowed to differ in.
|
|
ARM = re.compile(r"^(DirectGLES|DirectVulkan)\.(Split|Spawn|Tcp|TcpDevice)\.(.+)$")
|
|
TEST_LINE = re.compile(r"^\s*Test\s+#\d+:\s*(\S+)\s*$", re.MULTILINE)
|
|
|
|
# `ctest -N -L <tcp label>` lists the FIXTURE entries too, because every tcp entry declares
|
|
# FIXTURES_REQUIRED mobilegl-tcp and ctest pulls the setup/cleanup in with them. They are the
|
|
# supervisor, not cases, and they exist on exactly one arm by construction. Named here so that
|
|
# any OTHER unparseable name is still an error rather than a silent drop.
|
|
FIXTURE_ENTRIES = {"TcpServer.Start", "TcpServer.Stop"}
|
|
|
|
# THE MAGMA TIER'S SANCTIONED ASYMMETRIES, LISTED HERE AND NOWHERE ELSE - the same discipline
|
|
# MONOLITH_ONLY above gets. Both are inproc-only BY CONSTRUCTION, not by omission:
|
|
# MagmaRunAheadScenario arms its hold through ServerLoopInstance().SetBeforeRetireHookForTesting,
|
|
# a hook on the CALLING process's server loop. With the server in another
|
|
# process the hold never activates and the wait times out.
|
|
# MagmaWireCacheScenario SetUp asserts runtime.transportName == "inproc" outright: the lane reads
|
|
# the server's own view/attachment identity out of this process.
|
|
# A cross-process hold and a cross-process cache peek are server-side knobs neither exists yet;
|
|
# until they do, naming the two scenarios here keeps the tolerance from widening for anything else.
|
|
MAGMA_INPROC_ONLY = ("MagmaRunAheadScenario.", "MagmaWireCacheScenario.")
|
|
|
|
|
|
def lane_names(build_dir, label):
|
|
"""Every ctest entry name under an ANCHORED label (`ctest -L` is a regex, not a name)."""
|
|
out = subprocess.run(["ctest", "-N", "-L", "^" + label + "$"], cwd=build_dir,
|
|
capture_output=True, text=True, check=True).stdout
|
|
return {m.group(1) for m in TEST_LINE.finditer(out)}
|
|
|
|
|
|
def arm_keys(names):
|
|
return {f"{m.group(1)}.{m.group(3)}" for m in (ARM.match(n) for n in names) if m}
|
|
|
|
|
|
def lane_cases(build_dir, label):
|
|
out = subprocess.run(["ctest", "-N", "-L", "^" + label + "$"], cwd=build_dir,
|
|
capture_output=True, text=True, check=True).stdout
|
|
return {m.group(1) for m in CASE.finditer(out)}
|
|
|
|
|
|
def compare_arms(build_dir, tier, labels):
|
|
"""The three arms of one tier must name the same set after the arm segment comes off.
|
|
|
|
Returns True on failure, the way main() below counts them."""
|
|
sets = {}
|
|
for arm, label in labels.items():
|
|
names = lane_names(build_dir, label) - FIXTURE_ENTRIES
|
|
sets[arm] = arm_keys(names)
|
|
print(f"{tier}: {label} has {len(names)} case entrie(s), "
|
|
f"{len(sets[arm])} after normalisation")
|
|
if len(names) != len(sets[arm]):
|
|
print(f"::error::{label}: {len(names) - len(sets[arm])} entry name(s) do not parse "
|
|
f"as <Backend>.<Arm>.<case>, so this comparison silently drops them: "
|
|
+ ", ".join(sorted(n for n in names if not ARM.match(n))), file=sys.stderr)
|
|
return True
|
|
if not any(sets.values()):
|
|
print(f"::note::{tier}: no entries under {sorted(labels.values())} - nothing to compare")
|
|
return False
|
|
failed = False
|
|
reference = "split"
|
|
comparable = {k for k in sets[reference]
|
|
if not any(only in k for only in MAGMA_INPROC_ONLY)}
|
|
if len(comparable) != len(sets[reference]):
|
|
print(f"{tier}: {len(sets[reference]) - len(comparable)} inproc-only entrie(s) excluded "
|
|
f"from the comparison by name ({', '.join(MAGMA_INPROC_ONLY)})")
|
|
for arm, keys in sets.items():
|
|
if arm == reference:
|
|
continue
|
|
missing, extra = sorted(comparable - keys), sorted(keys - comparable)
|
|
if missing or extra:
|
|
print(f"::error::{tier}: the {arm} arm does not match the {reference} arm: "
|
|
f"missing={missing}, extra={extra}. Every arm of a tier is emitted from one "
|
|
f"loop over the same case list, so a difference here is a registration bug, "
|
|
f"not a coverage decision.", file=sys.stderr)
|
|
failed = True
|
|
return failed
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("build_dir")
|
|
parser.add_argument("--require-device", action="store_true",
|
|
help="Also require the configured integration-tcp-device lane")
|
|
args = parser.parse_args()
|
|
|
|
split = lane_cases(args.build_dir, "integration-split")
|
|
spawn = lane_cases(args.build_dir, "integration-spawn")
|
|
if not split or not spawn:
|
|
print(f"::error::a lane is EMPTY (split={len(split)}, spawn={len(spawn)}). An empty set "
|
|
f"compares equal to nothing and would pass this gate silently, which is the one "
|
|
f"way it could stop meaning anything.", file=sys.stderr)
|
|
return 1
|
|
|
|
split_comparable = {c for c in split if c.split(".", 1)[0] not in MONOLITH_ONLY}
|
|
missing = sorted(split_comparable - spawn)
|
|
extra = sorted(spawn - split_comparable)
|
|
|
|
print(f"spawn-lane parity: split {len(split)} ({len(split_comparable)} comparable), "
|
|
f"spawn {len(spawn)}")
|
|
if missing:
|
|
print("::error::cases in integration-split with no integration-spawn counterpart: " +
|
|
", ".join(missing) + ". Exit gate 9.2 requires the sets to be equal - a spawn lane "
|
|
"that covers less goes green while proving less. Register through "
|
|
"mgl_itest_register_split_arms, which emits both arms from one filter.",
|
|
file=sys.stderr)
|
|
if extra:
|
|
print("::error::cases in integration-spawn that integration-split does not run: " +
|
|
", ".join(extra) + ". The split arm is the control; a case only the spawn arm runs "
|
|
"has no baseline to be compared against.", file=sys.stderr)
|
|
failed = bool(missing or extra)
|
|
labels = ["integration-tcp"]
|
|
if args.require_device:
|
|
labels.append("integration-tcp-device")
|
|
for label in labels:
|
|
cases = lane_cases(args.build_dir, label)
|
|
missing, extra = sorted(spawn - cases), sorted(cases - spawn)
|
|
print(f"{label} parity: spawn {len(spawn)}, tcp {len(cases)}")
|
|
if not cases or missing or extra:
|
|
print(f"::error::{label}: missing={missing}, extra={extra}", file=sys.stderr)
|
|
failed = True
|
|
|
|
# P7 package L, exit gate 1's half of the same question: the Magma arms.
|
|
failed |= compare_arms(args.build_dir, "magma gated tier",
|
|
{"split": "integration-magma-split",
|
|
"spawn": "integration-magma-spawn",
|
|
"tcp": "integration-magma-tcp"})
|
|
failed |= compare_arms(args.build_dir, "magma informational tier",
|
|
{"split": "integration-magma-all-split",
|
|
"spawn": "integration-magma-all-spawn",
|
|
"tcp": "integration-magma-all-tcp"})
|
|
return 1 if failed else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|