[MG_IntegrationTest, scripts] (P7): a server-side magma test knob is registered on split and spawn only, and the parity gate names the five entries tcp cannot carry

ID-P7-31 review, should-fix 1.

Every MGITEST_MAGMA_FORCE_* knob is read BY THE SERVER (the getenv readers in
WireFramebuffer.inc run on the apply thread), and a ctest ENVIRONMENT property only
reaches the process ctest starts - the client. Inproc runs the server in that process
and ServerSpawn.cpp copies ::environ into the spawned child, so both arms see the knob;
the tcp arm's server is the lane-wide TcpServer.Start fixture, started once from
scripts/ci/tcp_server_fixture.py's own os.environ, and nothing a per-case property says
reaches it. The tcp copy of each knob=1 entry ran the DEFAULT arm under a name claiming
otherwise.

Measured, not deduced: a temporary fatal in each of the three readers killed all five
knob=1 entries on split and on spawn and left all five green on tcp.

So ShaderMip1, ShaderMip2, DepthMip, DefaultBlitShape1 and MsResolve1 are registered on
split+spawn only (the first four are package B's pre-existing vacuity of the same
shape). The knob=0 twins DefaultBlitShape0 / MsResolve0 keep their tcp entries: zero is
the default and means the same thing on every arm.

spawn_lane_parity.py names the exception, following ID-P7-14's MAGMA_INPROC_ONLY:
MAGMA_SERVER_ENV_KNOB_NO_TCP, applied through a new compare_arms(no_tcp=) that drops the
keys from the tcp comparison ONLY - folding them into inproc_only would stop requiring
them on spawn, which is where they do most of their work.
This commit is contained in:
2026-09-22 20:05:28 -04:00
parent 3c80cd62fe
commit 9fed478dfd
2 changed files with 90 additions and 4 deletions
+50 -1
View File
@@ -3005,7 +3005,43 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
# the shared Fm environment would silently retarget GenerateMipmapPixels,
# GenerateMipmapPackedFloatPixels, GenerateMipmapDepthPixels and the two base/view cases
# as well - four green entries that would stop measuring the arm they were written for.
# ---- P7 wave 2-B2: A SERVER-SIDE KNOB HAS NO TCP ARM, AND THAT IS STRUCTURAL ---------
#
# Every MGITEST_MAGMA_FORCE_* knob in this block is read BY THE SERVER
# (WireFramebuffer.inc's three `std::getenv` readers run on the apply thread). A ctest
# ENVIRONMENT property reaches the process ctest starts - the CLIENT - and the two arms
# differ in where the server comes from:
# inproc the server is a thread of that same process, so it sees the knob;
# spawn ServerSpawn.cpp copies ::environ into the child, so it sees the knob;
# tcp the server is the LANE-WIDE TcpServer.Start fixture, started once from the
# fixture's own os.environ (scripts/ci/tcp_server_fixture.py) long before any
# case runs, and it forks a session child per Hello. Nothing a per-test
# ENVIRONMENT says can reach it.
#
# So a tcp copy of a knob entry does not exercise the arm it names - it runs the
# DEFAULT arm under a name that claims otherwise, which for ShaderMip is precisely what
# that case's own skip message says must not happen ("this case would assert the native
# blit twice over"). MEASURED, not deduced: a temporary fatal in each of the three
# readers killed all five knob=1 entries on split and on spawn and left ALL FIVE GREEN
# on tcp.
#
# Registering them on split+spawn only is therefore a coverage statement, not a gap, and
# it is NAMED in scripts/ci/spawn_lane_parity.py (MAGMA_SERVER_ENV_KNOB_NO_TCP) the way
# MAGMA_INPROC_ONLY and MONOLITH_ONLY are, so the parity gate keeps its teeth for
# everything else. The knob=0 twins (DefaultBlitShape0, MsResolve0) DO stay on tcp: zero
# is the default, so those entries mean the same thing on all three arms.
#
# Making the tcp lane able to carry these would need the fixture to take a per-case
# server environment - a supervisor control, not a ctest property - and that is a debt,
# recorded in notes/p7/magma-b2.md §6.
set(mglItestServerEnvKnobArm TRUE)
if(mglItestArm STREQUAL "Tcp")
set(mglItestServerEnvKnobArm FALSE)
endif()
foreach(shaderMipTier 1 2)
if(NOT mglItestServerEnvKnobArm)
continue()
endif()
gtest_discover_tests(MobileGLIntegrationTest
TEST_PREFIX "DirectVulkan.${mglItestArm}.ShaderMip${shaderMipTier}."
TEST_FILTER "F1WireScenario.GenerateMipmapWithoutNativeBlitPixels"
@@ -3017,7 +3053,9 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
# The DEPTH half of the same knob (CONTRACT-P7 §5.1 (A)). ONE TIER, not two: the depth
# pass renders into a depth ATTACHMENT view of the destination level, which a depth
# image always has usage for - there is no "cannot be attached" shape to force, so
# tier 2 would register a second copy of tier 1's run.
# tier 2 would register a second copy of tier 1's run. Split+spawn only, for the
# server-side-knob reason above.
if(mglItestServerEnvKnobArm)
gtest_discover_tests(MobileGLIntegrationTest
TEST_PREFIX "DirectVulkan.${mglItestArm}.DepthMip."
TEST_FILTER "F1WireScenario.GenerateMipmapDepthWithoutNativeBlitPixels"
@@ -3025,6 +3063,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
PROPERTIES LABELS "${mglItestMagmaLabels}" TIMEOUT ${MGL_ITEST_TIMEOUT}
${mglItestArmProperties}
ENVIRONMENT "${mglItestMagmaWireEnvironment}\;MGITEST_MAGMA_FORCE_SHADER_MIPMAP=1\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p7-depthmip${mglItestArmLogTag}.log")
endif()
# NO KNOB on the decline: it is decided on the aspect before the blit-support question
# is asked, so the plain Fm environment reaches it on every driver.
gtest_discover_tests(MobileGLIntegrationTest
@@ -3098,7 +3137,12 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
# VK_SURFACE_TRANSFORM_IDENTITY, so the scratch path that exists FOR rotation would
# never run. Forced onto an identity surface it must produce the SAME pixels as the
# fall-through, which is what the shared assertions check.
# The knob=1 copy is split+spawn only (the server-side-knob note above); knob=0 is the
# default and means the same thing on all three arms, so it keeps its tcp entry.
foreach(defaultBlitScratch 0 1)
if(defaultBlitScratch AND NOT mglItestServerEnvKnobArm)
continue()
endif()
gtest_discover_tests(MobileGLIntegrationTest
TEST_PREFIX "DirectVulkan.${mglItestArm}.DefaultBlitShape${defaultBlitScratch}."
TEST_FILTER "F1WireScenario.ColorBlitToDefaultFromANonSampleableSourceDegrades"
@@ -3149,6 +3193,11 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
# ITS OWN ENTRIES RATHER THAN A LANE-WIDE FLIP, the shader-mip block's reason: the knob
# changes how EVERY depth/stencil resolve in the process is performed.
foreach(shaderDepthResolve 0 1)
# Same split+spawn restriction, same reason, same parity exception: the knob is read
# on the server and the tcp server is the lane fixture. MsResolve0 stays everywhere.
if(shaderDepthResolve AND NOT mglItestServerEnvKnobArm)
continue()
endif()
gtest_discover_tests(MobileGLIntegrationTest
TEST_PREFIX "DirectVulkan.${mglItestArm}.MsResolve${shaderDepthResolve}."
TEST_FILTER "DepthStencilReadbackMatrixScenario.AResolvedMultisampleDepthReadsBackFromTheDestination"
+40 -3
View File
@@ -57,6 +57,24 @@ FIXTURE_ENTRIES = {"TcpServer.Start", "TcpServer.Stop"}
# until they do, naming the two scenarios here keeps the tolerance from widening for anything else.
MAGMA_INPROC_ONLY = ("MagmaRunAheadScenario.", "MagmaWireCacheScenario.")
# P7 wave 2-B2: THE SANCTIONED ASYMMETRY THAT IS NOT INPROC-ONLY - these exist on split AND
# spawn and cannot exist on tcp. Every MGITEST_MAGMA_FORCE_* knob is read BY THE SERVER
# (WireFramebuffer.inc's getenv readers run on the apply thread), and a ctest ENVIRONMENT
# property only reaches the process ctest starts, the CLIENT. Under inproc the server is a
# thread of that process and under spawn ServerSpawn.cpp copies ::environ into the child, so
# both see it; under tcp the server is the LANE-WIDE TcpServer.Start fixture, started once from
# the fixture's own os.environ before any case runs, so nothing a per-test property says can
# reach it. A tcp copy would run the DEFAULT arm under a name claiming otherwise.
#
# MEASURED (a temporary fatal in each of the three readers): all five knob=1 entries died on
# split and on spawn and ALL FIVE PASSED on tcp.
#
# These are TAILS, not scenarios - the normaliser above keeps the sub-lane tail for the Magma
# tier on purpose, so `.MsResolve1.` names the knob entry and `.MsResolve0.` (the default, which
# does keep its tcp entry) is untouched.
MAGMA_SERVER_ENV_KNOB_NO_TCP = (".ShaderMip1.", ".ShaderMip2.", ".DepthMip.",
".DefaultBlitShape1.", ".MsResolve1.")
def lane_names(build_dir, label):
"""Every ctest entry name under an ANCHORED label (`ctest -L` is a regex, not a name)."""
@@ -75,9 +93,15 @@ def lane_cases(build_dir, label):
return {m.group(1) for m in CASE.finditer(out)}
def compare_arms(build_dir, tier, labels, inproc_only=()):
def compare_arms(build_dir, tier, labels, inproc_only=(), no_tcp=()):
"""The three arms of one tier must name the same set after the arm segment comes off.
`inproc_only` drops a key from the comparison for EVERY non-split arm; `no_tcp` drops it for
the tcp arm alone, which is a different shape and needs to be: a server-side test knob does
reach a spawned server and does not reach the tcp lane's shared fixture, so the entry is
required on spawn and forbidden on tcp. Folding it into `inproc_only` would stop requiring
it on spawn, which is where it does most of its work.
Returns True on failure, the way main() below counts them."""
sets = {}
for arm, label in labels.items():
@@ -100,10 +124,19 @@ def compare_arms(build_dir, tier, labels, 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(inproc_only)})")
if no_tcp:
dropped = {k for k in comparable if any(only in k for only in no_tcp)}
if dropped:
print(f"{tier}: {len(dropped)} server-env-knob entrie(s) excluded from the TCP "
f"comparison only ({', '.join(no_tcp)}) - the knob is read on the server and "
f"the tcp server is the lane fixture, not a per-case process")
for arm, keys in sets.items():
if arm == reference:
continue
missing, extra = sorted(comparable - keys), sorted(keys - comparable)
expected = comparable
if arm == "tcp" and no_tcp:
expected = {k for k in comparable if not any(only in k for only in no_tcp)}
missing, extra = sorted(expected - keys), sorted(keys - expected)
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 "
@@ -163,11 +196,15 @@ def main():
# every arm - it is a whole-binary filter, and there they simply SKIP for want of their
# lane marker - so excluding them there would turn a present-on-all-arms case into a
# spurious "extra".
# The server-env knob entries are gated-tier only for the same per-tier reason: the
# full-suite census is a whole-binary filter with no knob in its environment at all, so
# there is nothing there to exclude.
failed |= compare_arms(args.build_dir, "magma gated tier",
{"split": "integration-magma-split",
"spawn": "integration-magma-spawn",
"tcp": "integration-magma-tcp"},
inproc_only=MAGMA_INPROC_ONLY)
inproc_only=MAGMA_INPROC_ONLY,
no_tcp=MAGMA_SERVER_ENV_KNOB_NO_TCP)
failed |= compare_arms(args.build_dir, "magma informational tier",
{"split": "integration-magma-all-split",
"spawn": "integration-magma-all-spawn",