[Test] (MG_IntegrationTest): arm the split lanes from the running process - MG_Config::Transport, ClientSession::Active() and ImplementedVerbCount() - instead of from a grep over c0's stub messages, which a one-word rename disarmed into eight green monolith runs, and require the encoder's record ordinal to move before any split case may pass

This commit is contained in:
2026-09-11 15:30:17 -04:00
parent 7ad0ff3c33
commit 8b425a9718
7 changed files with 353 additions and 117 deletions
+87 -47
View File
@@ -57,6 +57,7 @@ add_executable(MobileGLIntegrationTest
Harness/P4aSeamPeek.cpp
Harness/P4aFinalFixPeek.cpp
Harness/PersistentMapPeek.cpp
Harness/SplitRuntimePeek.cpp
Scenarios/OrientationScenario.cpp
Scenarios/CrossFrameBufferScenario.cpp
Scenarios/ResidentIndexScenario.cpp
@@ -1779,26 +1780,47 @@ endif()
# ambient DirectGLES./DirectVulkan. registrations of every build - they are ordinary GL scenarios
# and their monolith behaviour is the baseline the split arm is compared against.
#
# WHAT HAPPENS BEFORE PACKAGES c1, s1 AND v1 LAND. MOBILEGL_TRANSPORT is parsed only under
# MOBILEGL_BUILD_DISAGGREGATED (CONTRACT-P5 5) and, until something consumes it, a parsed
# `inproc` still runs monolith. A Split entry that went green there would be the exact failure
# the lane exists to prevent, so the probes below arm the cases and they SKIP - naming the
# missing thing - until they answer yes. The registration is never deleted: a ctest name may
# never disappear (gate G14).
# WHAT ARMS THE SPLIT LANES, AND WHY IT IS NOT A PROBE OVER SOURCE TEXT.
#
# THE EXISTENCE OF THE DIRECTORY IS NOT THE QUESTION, and finding that out cost a false green
# here. Package c0 landed MG_Remote/{Client,Server} as SIGNATURE STUBS - CapsMirror.cpp,
# ClientSession.cpp, EmitTables.cpp, PipeApplier.cpp, ServerLoop.cpp, ServerSession.cpp all
# exist, all compile, and every entry point in them is `MGLOG_F("Fatal{Unimplemented...")` +
# abort. A probe for the c1 symbols alone therefore answered YES against a tree where
# RemoteEmitTable() aborts on sight and ImplementedVerbCount() returns 0, and the Split lanes
# armed and asserted monolith behaviour.
# Until a real client session exists, MOBILEGL_TRANSPORT=inproc is parsed (CONTRACT-P5 5) and then
# nothing consumes it, so a Split entry would go green having run monolith end to end - the exact
# failure the lane exists to prevent. The first version of this file answered "has the client
# landed" with a CMake conjunction over c0's stub files: the c1 symbols present AND no
# `Fatal.Unimplemented` surviving. REVIEW FINDING M-1 FALSIFIED IT BY PERFORMING IT - a
# `sed -i 's/Fatal{Unimplemented/Fatal{NotYetImplemented/'` over c0's six stubs, with every entry
# point still aborting and ImplementedVerbCount() still 0, armed all eleven lanes and EIGHT WENT
# GREEN. A single comment line carrying the marker did the opposite and would keep them dark
# forever (M-2), and two stub files outside the two probed directories allowed a partial arm (M-3).
#
# So the arming condition is a CONJUNCTION with a NEGATIVE half: the client names its own
# symbols AND no source under Client or Server still carries a c0 signature stub. The negative
# half is the sharp one and it is layout-independent in the way the house rule demands - the
# owning packages have to DELETE those aborts to implement anything, whatever files they
# choose to put the implementation in. Both halves are printed at configure time.
# A statement about source text can always be falsified by editing source text, and the people
# most likely to edit it are the ones landing the packages the probe watches for. So the arming
# condition moved into the PROCESS: Harness/SplitRuntimePeek.cpp reads MG_Config::Transport,
# ClientSession::Active() and ImplementedVerbCount() - three values c0 shipped and documented,
# none of which a message edit can move - and every Split case skips, naming the first one that is
# not true. All this file has to decide now is whether that peek can be COMPILED, which is exactly
# "did this build compile MG_Remote", which is exactly the build option.
if (MOBILEGL_BUILD_DISAGGREGATED)
target_compile_definitions(MobileGLIntegrationTest PRIVATE -DMGITEST_SPLIT_RUNTIME_PEEK=1)
message(STATUS "Integration tests: the DirectGLES.Split. lanes arm from the RUNNING PROCESS - "
"MG_Config::Transport, ClientSession::Active() and ImplementedVerbCount() - and "
"skip naming the first of those that is not true")
endif()
# Q-3, made a build fact rather than documentation. ConfigLoader logs the resolved transport at
# INFO, and that line is what run_trace_case.cmake reads back as proof a retrace really went
# split. At WARN or above it is compiled out and every split retrace reds for a reason that is not
# a defect; at DEBUG, ConfigLoader's unconditional env dump prints a confusable KEY=VALUE line in
# a PULL build too (review M-5, also fixed on the reading side). The integration lanes themselves
# no longer depend on the log level at all - they read the variable - so this is a WARNING.
if (MOBILEGL_BUILD_DISAGGREGATED AND DEFINED MOBILEGL_LOG_ACTIVE_LEVEL
AND NOT MOBILEGL_LOG_ACTIVE_LEVEL STREQUAL "MOBILEGL_LOG_LEVEL_INFO"
AND NOT MOBILEGL_LOG_ACTIVE_LEVEL STREQUAL "MOBILEGL_LOG_LEVEL_DEBUG")
message(WARNING "Integration tests: MOBILEGL_LOG_ACTIVE_LEVEL=${MOBILEGL_LOG_ACTIVE_LEVEL} in a "
"disaggregated build. ConfigLoader's transport line is MGLOG_I, so the "
"trace-replay split arm's transport-resolution assertion cannot see it and every "
"split retrace will red. Use MOBILEGL_LOG_LEVEL_INFO.")
endif()
# Package b1's client-side persistent-map tracker, which is what Harness/PersistentMapPeek.cpp
# asks the membership question of (b1-v1.md 4.1 item 2). TWO halves, and both are needed:
# MOBILEGL_BUILD_DISAGGREGATED because a build that never compiled MG_Remote cannot LINK the call,
@@ -1820,31 +1842,6 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
endif()
endif()
mgl_itest_probe_for_symbol(MGL_ITEST_REMOTE_CLIENT_SIGNATURES
"${MGL_ITEST_ROOT}/MobileGL/MG_Remote/Client"
"kRemoteEmitSlotCount|RemoteEmitTable|CapsMirror")
mgl_itest_probe_for_symbol(MGL_ITEST_REMOTE_CLIENT_STUB
"${MGL_ITEST_ROOT}/MobileGL/MG_Remote/Client" "Fatal.Unimplemented")
mgl_itest_probe_for_symbol(MGL_ITEST_REMOTE_SERVER_STUB
"${MGL_ITEST_ROOT}/MobileGL/MG_Remote/Server" "Fatal.Unimplemented")
set(MGL_ITEST_SPLIT_CAPABILITY_ENV "")
if (MGL_ITEST_REMOTE_CLIENT_SIGNATURES AND NOT MGL_ITEST_REMOTE_CLIENT_STUB AND
NOT MGL_ITEST_REMOTE_SERVER_STUB)
message(STATUS "Integration tests: MG_Remote/{Client,Server} carry no c0 signature stub and "
"the client names its own symbols (${MGL_ITEST_REMOTE_CLIENT_SIGNATURES}) - "
"the DirectGLES.Split. lanes assert")
list(APPEND MGL_ITEST_SPLIT_CAPABILITY_ENV "MGITEST_REMOTE_CLIENT_PRESENT=1")
elseif (MGL_ITEST_REMOTE_CLIENT_STUB OR MGL_ITEST_REMOTE_SERVER_STUB)
message(STATUS "Integration tests: MG_Remote still carries c0 signature stubs "
"(${MGL_ITEST_REMOTE_CLIENT_STUB} ${MGL_ITEST_REMOTE_SERVER_STUB}) - packages "
"c1/s1/v1 have not landed, so every DirectGLES.Split. entry stays registered "
"and SKIPS")
else()
message(STATUS "Integration tests: no source under MobileGL/MG_Remote/Client names "
"kRemoteEmitSlotCount/RemoteEmitTable/CapsMirror - there is no client at all, "
"so every DirectGLES.Split. entry stays registered and SKIPS")
endif()
# ARCHITECTURE.md:543 - every new ctest ENVIRONMENT and add_trace_replay_test's SPLIT branch
# carries MOBILEGL_IPC_SERVER_PATH, because the dladdr fallback cannot find the server from a
# binary that links MobileGL_s statically. P5 is inproc-only and nothing reads the value yet; it
@@ -1857,7 +1854,7 @@ set(MGL_ITEST_SPLIT_SERVER_PATH "${CMAKE_BINARY_DIR}/libMobileGLServer.so")
mgl_itest_join_environment(MGL_ITEST_GLES_SPLIT_ENVIRONMENT
"MOBILEGL_BACKEND_TYPE=DirectGLES" "MOBILEGL_TRANSPORT=inproc"
"MOBILEGL_IPC_SERVER_PATH=${MGL_ITEST_SPLIT_SERVER_PATH}" "MGITEST_SPLIT_LANE=1"
${MGL_ITEST_SPLIT_CAPABILITY_ENV} ${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_COMMON_ENV})
${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_COMMON_ENV})
# The two counting lanes, one per transport, and they are a PAIR: exit gate E3(c) asks that the
# split arm's `mpr` equal the monolith arm's, and one test process can only ever see its own. So
@@ -1875,8 +1872,15 @@ mgl_itest_join_environment(MGL_ITEST_GLES_SPLIT_ENVIRONMENT
# PERSISTENT|WRITE|COHERENT map is a property of the driver and the build, so the case RECORDS it
# there and skips the assertion. Only the split lane - where R-6 pins the adopt tier at T2 -
# declares one.
# MOBILEGL_TRANSPORT=monolith IS NAMED HERE ON PURPOSE, and it is review finding N-6. This entry
# is the MONOLITH half of a pair; without the property a job-level or gate-level
# `MOBILEGL_TRANSPORT=inproc` export reaches it (observed: its own log then carried
# `Config: MOBILEGL_TRANSPORT=inproc`) and, in the inproc arm of the three-arm A/B, BOTH halves of
# the pair were inproc. Nothing failed - each half asserts the same constant - but the pair was
# not the pair its name describes. A ctest ENVIRONMENT property overrides the job environment,
# which is the one case where that is what you want.
mgl_itest_join_environment(MGL_ITEST_GLES_PMAP_ARM_ENVIRONMENT
"MOBILEGL_BACKEND_TYPE=DirectGLES" "MGITEST_PMAP_LANE=1"
"MOBILEGL_BACKEND_TYPE=DirectGLES" "MGITEST_PMAP_LANE=1" "MOBILEGL_TRANSPORT=monolith"
"MOBILEGL_PIPE_STATS=1" "MOBILEGL_PIPE_STATS_PERIOD=1"
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/persistent-map-arm-DirectGLES.log"
${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_COMMON_ENV})
@@ -1937,10 +1941,9 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
"MOBILEGL_BACKEND_TYPE=DirectGLES" "MOBILEGL_TRANSPORT=inproc"
"MOBILEGL_IPC_SERVER_PATH=${MGL_ITEST_SPLIT_SERVER_PATH}" "MGITEST_SPLIT_LANE=1"
"MGITEST_PMAP_LANE=1" "MGITEST_PERSISTENT_MAP_ARM=emulated"
"MGITEST_SPLIT_EXPECT_TRANSPORT=inproc"
"MOBILEGL_PIPE_STATS=1" "MOBILEGL_PIPE_STATS_PERIOD=1"
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/persistent-map-arm-split-DirectGLES.log"
${MGL_ITEST_SPLIT_CAPABILITY_ENV} ${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_COMMON_ENV})
${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_COMMON_ENV})
gtest_discover_tests(MobileGLIntegrationTest
TEST_PREFIX "DirectGLES.Split.PersistentMapArm."
@@ -1952,4 +1955,41 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
RESOURCE_LOCK persistent-map-arm-split-DirectGLES.log
ENVIRONMENT "${MGL_ITEST_GLES_SPLIT_PMAP_ARM_ENVIRONMENT}"
)
# --- EXIT GATE E3(e): the same checks with a ring small enough to force back-pressure -----
#
# BRIEF 7 E3(e) - "以上五条再跑一遍 ring 小到足以至少发生一次背压等待的配置" - was missing from
# the first cut of this package and missing from its report (review finding M-7). It is a
# LANE, not a new scenario: the same three split scenarios with SEG_CMD and SEG_STAGE at their
# floor, so a workload that fits comfortably in the 8 MiB / 32 MiB defaults has to wrap and
# wait at least once.
#
# 1 MiB is ConfigLoader's minimum for both (Config.h / ConfigLoader.cpp:363-364, floors of 1
# rather than 0 because a ring caps ONE record at half its size). That caps a record at 512
# KiB, which every record on the reduced path is far under, so the ring is legal and small
# rather than unusable.
#
# WHAT THIS LANE DOES NOT YET ASSERT, stated rather than implied: that a back-pressure wait
# ACTUALLY happened. That needs a counter only package s1 can publish (a producer-parked or
# ring-full tally on RingControl); until it exists this lane proves the five checks survive a
# small ring, not that the small ring bit. The gap is named in t1-v1.md's debts rather than
# left for someone to discover from a green.
mgl_itest_join_environment(MGL_ITEST_GLES_SPLIT_SMALL_RING_ENVIRONMENT
"MOBILEGL_BACKEND_TYPE=DirectGLES" "MOBILEGL_TRANSPORT=inproc"
"MOBILEGL_IPC_SERVER_PATH=${MGL_ITEST_SPLIT_SERVER_PATH}" "MGITEST_SPLIT_LANE=1"
"MGITEST_SMALL_RING_LANE=1" "MOBILEGL_IPC_RING_MB=1" "MOBILEGL_IPC_STAGE_MB=1"
${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_COMMON_ENV})
foreach(mglItestSmallRingScenario ClearThenReadPixelsScenario TriangleScenario
PersistentCoherentMapScenario)
gtest_discover_tests(MobileGLIntegrationTest
TEST_PREFIX "DirectGLES.Split.SmallRing."
TEST_FILTER "${mglItestSmallRingScenario}.*"
DISCOVERY_TIMEOUT 30
PROPERTIES
LABELS "integration-gpu\;integration-split"
TIMEOUT ${MGL_ITEST_TIMEOUT}
ENVIRONMENT "${MGL_ITEST_GLES_SPLIT_SMALL_RING_ENVIRONMENT}"
)
endforeach()
endif()
@@ -67,6 +67,33 @@ namespace MGITest {
class ScenarioTest : public ::testing::Test {
protected:
// THE BEHAVIOURAL HALF OF THE SPLIT LANE'S CLAIM, and it is in the DESTRUCTOR rather than
// in TearDown() on purpose: gtest calls only the MOST DERIVED TearDown, and every scenario
// that overrides it would have to remember to chain here. The fixture destructor always
// runs, and it runs before the test result is finalized, so ADD_FAILURE() is recorded.
//
// What it asserts: a case that ran in an ARMED split lane must have moved the client
// encoder's record ordinal. Everything else in the lane - the pixels, the readbacks, the
// arm assertion - is equally true of a monolith run of the same workload; this is the one
// statement that is only true if records crossed the ring. An emit table that resolves the
// transport and then falls through to the driver passes every other assertion in the file
// and fails exactly here.
~ScenarioTest() override {
if (!m_splitAssertionsArmed) return;
if (IsSkipped() || HasFailure()) return;
const SplitRuntimeState after = PeekSplitRuntime();
if (after.emitSeq <= m_emitSeqAtSetUp) {
ADD_FAILURE() << "this case ran in an armed DirectGLES.Split. lane and the client "
"encoder's record ordinal did not move: EmitSeq was "
<< m_emitSeqAtSetUp << " at SetUp and is " << after.emitSeq
<< " now. The workload drew, cleared and read pixels, so records were "
"due - a sequence that did not advance means the emit table "
"resolved the transport and then did not put anything on the wire, "
"which every other assertion in this lane is blind to because a "
"monolith run of the same workload produces the same pixels.";
}
}
void SetUp() override {
m_ready = false;
HeadlessGL& gl = HeadlessGL::Get();
@@ -96,24 +123,36 @@ namespace MGITest {
FAIL() << "MOBILEGL_ITEST_REQUIRE_HARDWARE_GPU is set but the context landed on a software "
<< "rasterizer: " << gl.RendererString();
}
// P5's DirectGLES.Split. lanes, in ONE place rather than in each scenario they
// point at. A Split entry runs with MOBILEGL_TRANSPORT=inproc, and until packages
// c1/s1/v1 land there is nothing to consume that variable: ConfigLoader parses it,
// logs it, and the process then runs monolith. Every case in the lane would go GREEN
// against the monolith path under a name that says it tested the split one - the one
// outcome the whole lane exists to prevent, and the reason CONTRACT-P5 5 also
// demands a BUILD-level `nm ... | grep MG_Remote` check for the other half of this
// question. The registrations are never deleted (gate G14: a ctest name may never
// disappear); they skip, naming exactly what is missing.
// P5's DirectGLES.Split. lanes, in ONE place rather than in each scenario they point
// at - the Split family also points at ClearThenReadPixelsScenario, which is target A
// of the reduced path and predates P5, and any later Split lane gets the same
// guarantee without anyone having to remember it.
//
// Here rather than in TriangleScenario/PersistentCoherentMapScenario because the
// Split family also points at ClearThenReadPixelsScenario, which is target A of the
// reduced path and predates P5 - and any later Split lane gets the same guarantee
// without anyone having to remember it.
// THE ARMING QUESTION IS ASKED OF THE PROCESS, not of the source tree. Until a real
// client session exists, MOBILEGL_TRANSPORT=inproc is parsed and then nothing consumes
// it, so every case in the lane would go GREEN against the monolith path under a name
// that says it tested the split one. The first version of this guard answered the
// question with a CMake grep over c0's stub files, and review finding M-1 falsified it
// by renaming one string: eleven lanes armed and eight went green. Harness/
// SplitRuntimePeek.h now answers it from MG_Config::Transport, ClientSession::Active()
// and ImplementedVerbCount(), none of which a message edit can move. Registrations are
// never deleted (gate G14); they skip, naming exactly which fact is not true.
if (SplitLane::IsSplitLane()) {
if (const std::string splitSkip = SplitLane::SkipReasonForSplitOnlyAssertions();
SplitLane::IsSplitLane() && !splitSkip.empty()) {
!splitSkip.empty()) {
GTEST_SKIP() << splitSkip;
}
// Armed. Take the wire's baseline, so the destructor can require that this case
// actually PUT SOMETHING THROUGH IT (review finding N-5: ten of the eleven Split
// entries had no runtime evidence of anything, and their green meant only "the
// same GL workload passed").
const SplitRuntimeState state = PeekSplitRuntime();
m_splitAssertionsArmed = true;
m_emitSeqAtSetUp = state.emitSeq;
RecordProperty("split_transport", state.transportName);
RecordProperty("split_implemented_verbs", static_cast<int>(state.implementedVerbs));
RecordProperty("split_emit_seq_at_setup", static_cast<int>(state.emitSeq));
}
// A scenario starts from a clean slate but shares the context (and so
// the renderer's memos) with every other scenario in this process -
// which is exactly the situation both shipped bugs needed.
@@ -141,6 +180,8 @@ namespace MGITest {
}
bool m_ready = false;
bool m_splitAssertionsArmed = false;
unsigned long long m_emitSeqAtSetUp = 0;
};
} // namespace MGITest
+35 -50
View File
@@ -6,35 +6,20 @@
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
//
// The four harness markers the `DirectGLES.Split.` ctest entries set, and the one question
// every Split case has to ask before it asserts anything.
// The harness markers the `DirectGLES.Split.` ctest entries set.
//
// WHY A MARKER AND NOT MOBILEGL_TRANSPORT. The library's own variable says what was ASKED FOR,
// not what happened. In a build without -DMOBILEGL_BUILD_DISAGGREGATED the whole
// MOBILEGL_TRANSPORT parser does not exist (CONTRACT-P5.md 5: putting a complaint in the
// unconditional part of ConfigLoader would move a pull-build symbol and break G1), so
// MOBILEGL_TRANSPORT=inproc is accepted by the environment and SILENTLY IGNORED. A Split case
// that keyed off MOBILEGL_TRANSPORT would therefore run monolith and go green - which is
// precisely the failure the whole lane exists to make impossible. The build-level guard against
// that is `nm --defined-only libMobileGL.so | grep -i MG_Remote` in build-split, asserted by the
// gate's part 1 and by the build-linux-split CI job; the markers here are the TEST-level guard
// for the other half of the same question, "is there a client to assert about yet".
// WHAT IS AND IS NOT DECIDED HERE. These markers say what the LANE asked for. Whether the lane
// GOT it is a different question and it is answered by Harness/SplitRuntimePeek.h, out of the
// running process - see the long argument in that header. The split of responsibility matters:
// an environment variable is a request, and this package's first version treated a request (plus
// a grep over source text) as evidence that the request had been honoured. Review finding M-1
// falsified that by renaming one string in six files, which armed eleven lanes and turned eight
// of them green against the monolith path.
//
// MGITEST_SPLIT_LANE=1
// Set by the DirectGLES.Split.* entries and by nothing else. A scenario's split-only
// assertions are the ones that are meaningless in the ambient DirectGLES./DirectVulkan.
// lanes, and this is how a case tells the two apart in ONE binary that is registered many
// times over.
//
// MGITEST_REMOTE_CLIENT_PRESENT=1
// Set by a CMake content probe over MobileGL/MG_Remote/Client (package c1's directory), for
// a SYMBOL and never a filename - the reason is argued at length above
// mgl_itest_probe_for_symbol in MG_IntegrationTest/CMakeLists.txt: the owning package picks
// its own file layout, and a filename probe answers "no" forever the moment it moves the
// code. Until c1 lands there is no emitter, so `MOBILEGL_TRANSPORT=inproc` reaches a library
// that parses it, logs it and then runs monolith anyway. A Split entry in that state must
// SKIP NAMING THE MISSING THING; it must not be deleted (gate G14 - a ctest name may never
// disappear) and it must not go green.
// Set by the DirectGLES.Split.* entries and by nothing else. It is how a case in ONE binary,
// registered many times over, knows which registration it is running under. It is NOT
// evidence of anything about the transport.
//
// MGITEST_PERSISTENT_MAP_ARM=adopted|emulated
// The arm the LANE declares. AcquireMemoryRange adopts a PERSISTENT|WRITE map that is not
@@ -43,18 +28,27 @@
// driver and the build rather than by the test, and MOBILEGL_DISABLE_LARGE_BUFFER_ADOPTION
// does NOT separate them (it guards TryAdoptLargeStorage's 16 MiB path, which a
// scenario-sized buffer never reaches at all). So the lane states which arm it expects and
// the scenario asserts it landed there. R-6 pins the split lane at T2 = emulated.
// PersistentCoherentMapScenario asserts it landed there, through
// Harness/PersistentMapPeek.h's read of IsBackendPersistentMapped(). R-6 pins the split lane
// at T2 = declined = emulated.
//
// MGITEST_SPLIT_EXPECT_TRANSPORT=inproc|monolith
// What the lane expects MG_Config::Transport to have resolved to, for the case that reads
// the library's own log back. Only meaningful in a lane that gave itself a private
// MOBILEGL_LOG_FILE_PATH.
// MGITEST_PMAP_LANE=1
// The one counting entry per transport that reads the library's summary line back. It has a
// MOBILEGL_LOG_FILE_PATH of its own and a RESOURCE_LOCK on it.
//
// MGITEST_SMALL_RING_LANE=1
// Exit gate E3(e)'s lane: the same split scenarios with MOBILEGL_IPC_RING_MB and
// MOBILEGL_IPC_STAGE_MB at their floor, so that the ring is small enough to make at least one
// back-pressure wait happen. A case uses it only to say so in its recorded properties; the
// ring sizes themselves reach the library through MOBILEGL_IPC_*.
#pragma once
#include <cstdlib>
#include <string>
#include "SplitRuntimePeek.h"
namespace MGITest::SplitLane {
inline std::string MarkerValue(const char* name) {
@@ -67,33 +61,24 @@ namespace MGITest::SplitLane {
// True in the DirectGLES.Split.* entries only.
inline bool IsSplitLane() { return MarkerIsOne("MGITEST_SPLIT_LANE"); }
// True once any source under MG_Remote/Client names one of the symbols CONTRACT-P5 fixes
// for it. See the probe in CMakeLists.txt.
inline bool RemoteClientPresent() { return MarkerIsOne("MGITEST_REMOTE_CLIENT_PRESENT"); }
// True in exit gate E3(e)'s small-ring lane.
inline bool IsSmallRingLane() { return MarkerIsOne("MGITEST_SMALL_RING_LANE"); }
// Empty when this case may assert; otherwise the reason to GTEST_SKIP() with. The reason
// is spelled out rather than summarised because a skip line is the only thing anyone reads
// when they ask "did the split lane actually run".
// Empty when this case may assert; otherwise the reason to GTEST_SKIP() with. The reason is
// spelled out rather than summarised because a skip line is the only thing anyone reads when
// they ask "did the split lane actually run" - and because the previous version of this
// message named the wrong missing thing (review finding N-1): it said MG_Remote/Client did
// not exist, on a tree where it existed and compiled and every entry point aborted.
inline std::string SkipReasonForSplitOnlyAssertions() {
if (!IsSplitLane()) {
return "not the split lane (MGITEST_SPLIT_LANE is unset): this case's split-only "
"assertions are about MOBILEGL_TRANSPORT=inproc and say nothing in a monolith "
"process";
"assertions are about a live MG_Remote client session and say nothing in a "
"monolith process";
}
if (!RemoteClientPresent()) {
return "MobileGL/MG_Remote/Client does not exist yet - no source there names "
"kRemoteEmitSlotCount / BackendObject_Remote / CapsMirror, so package c1 has "
"not landed and MOBILEGL_TRANSPORT=inproc reaches a library that parses it and "
"then runs monolith. Passing here would be a green that means 'the thing I test "
"does not exist yet'; the entry stays registered (G14) and skips instead";
}
return {};
return SplitRuntimeSkipReason();
}
// "adopted", "emulated", or empty when the lane declared nothing.
inline std::string DeclaredPersistentMapArm() { return MarkerValue("MGITEST_PERSISTENT_MAP_ARM"); }
// "inproc", "monolith", or empty when the lane declared nothing.
inline std::string DeclaredTransport() { return MarkerValue("MGITEST_SPLIT_EXPECT_TRANSPORT"); }
} // namespace MGITest::SplitLane
@@ -0,0 +1,88 @@
// MobileGL - MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.cpp
// Copyright (c) 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
#include "SplitRuntimePeek.h"
// MGITEST_SPLIT_RUNTIME_PEEK is defined by MG_IntegrationTest/CMakeLists.txt under
// MOBILEGL_BUILD_DISAGGREGATED and nowhere else. NO SOURCE PROBE decides it: the three symbols
// below are c0's, they exist in every disaggregated build from the contract commit onward, and
// their VALUES are what answer the question. That is the whole of the fix for review findings
// M-1, M-2 and M-3 - there is no longer a string for anyone to rename, comment out, or land
// outside a probed directory.
#if defined(MGITEST_SPLIT_RUNTIME_PEEK) && !defined(__ANDROID__)
#include <Config.h>
#include <MG_Remote/Client/ClientSession.h>
#include <MG_Remote/Client/EmitTables.h>
#define MGITEST_SPLIT_RUNTIME_PEEK_LIVE 1
#endif
namespace MGITest {
SplitRuntimeState PeekSplitRuntime() {
SplitRuntimeState state;
#if defined(MGITEST_SPLIT_RUNTIME_PEEK_LIVE)
using MobileGL::MG_Config::TransportMode;
state.peekAvailable = true;
state.totalVerbSlots = MobileGL::MG_Remote::Client::kRemoteEmitSlotCount;
switch (MobileGL::MG_Config::Transport) {
case TransportMode::Monolith: state.transportName = "monolith"; break;
case TransportMode::InProcess: state.transportName = "inproc"; break;
default: state.transportName = "non-monolith"; break;
}
state.transportResolved = MobileGL::MG_Config::Transport != TransportMode::Monolith;
// Active() is c0's one deliberately non-aborting accessor: "does a session exist" has a
// legitimate no. Everything below it is only reached through a live session, so nothing
// here can trip one of c0's Fatal stubs.
MobileGL::MG_Remote::Client::ClientSession* session =
MobileGL::MG_Remote::Client::ClientSession::Active();
state.sessionActive = session != nullptr;
state.implementedVerbs = MobileGL::MG_Remote::Client::ImplementedVerbCount();
if (session != nullptr) {
// Encoder() returns the member; EmitSeq() returns m_emitSeq. Neither is a stub, and
// neither emits anything - this is a read.
state.emitSeq = session->Encoder().EmitSeq();
}
#endif
return state;
}
std::string SplitRuntimeSkipReason() {
const SplitRuntimeState state = PeekSplitRuntime();
if (!state.peekAvailable) {
return "this build did not compile MG_Remote (no -DMOBILEGL_BUILD_DISAGGREGATED=ON), or "
"this is the Android binary, which links the shipping libMobileGL.so built "
"-fvisibility=hidden and can reach no internal symbol. MOBILEGL_TRANSPORT is "
"ACCEPTED AND SILENTLY IGNORED in such a build (CONTRACT-P5 5), so a green here "
"would be a monolith run under a name that says split";
}
if (!state.transportResolved) {
return "MG_Config::Transport resolved to '" + state.transportName +
"', not to a split transport. The variable is read from the process, not from a "
"log line - a DEBUG-level pull build prints the same KEY=VALUE string out of "
"ConfigLoader's env dump (review M-5). Check MOBILEGL_TRANSPORT reached this "
"process";
}
if (!state.sessionActive) {
return "MG_Remote::Client::ClientSession::Active() is null: no client session exists in "
"this process. c0 shipped Start() as a Fatal stub and Active() as a deliberate "
"null, so this is what 'packages s1 (construction and handshake) and c1 have not "
"landed' looks like from inside a running test. The entry stays registered (gate "
"G14) and skips rather than passing against the monolith path";
}
if (state.implementedVerbs == 0) {
return "MG_Remote::Client::ImplementedVerbCount() is 0 of " +
std::to_string(state.totalVerbSlots) +
": the emit table has no real emitter, so every verb this scenario issues would "
"take the Fatal{UnmigratedVerb} arm or fall through. Package c1 owns it";
}
return {};
}
} // namespace MGITest
@@ -0,0 +1,82 @@
// MobileGL - MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.h
// Copyright (c) 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
//
// WHETHER THIS PROCESS IS REALLY RUNNING SPLIT, asked of the process rather than of the source
// tree. This is what arms every `DirectGLES.Split.` entry.
//
// WHY IT REPLACED A CONTENT PROBE, and the lesson is worth the paragraph. The first version of
// this arming condition was a CMake `file(STRINGS ... REGEX)` conjunction: "some source under
// MG_Remote/Client names the c1 symbols AND no source under Client or Server still matches
// `Fatal.Unimplemented`". Review finding M-1 did not argue with it, it PERFORMED it: a
// `sed -i 's/Fatal{Unimplemented/Fatal{NotYetImplemented/'` over c0's six stub files - every
// entry point still ending in std::abort(), RemoteEmitTable() still aborting on sight,
// ImplementedVerbCount() still returning 0 - armed all eleven lanes and EIGHT OF THEM WENT GREEN
// having run monolith end to end. A comment line containing the marker did the opposite and kept
// them dark forever (M-2), and two further stub files outside the two probed directories made a
// partial arm possible (M-3).
//
// The general form of that defect: A STATEMENT ABOUT SOURCE TEXT CAN ALWAYS BE FALSIFIED BY
// EDITING SOURCE TEXT, and the people most likely to edit it are the ones landing the packages
// the probe is watching for. A statement about what the process actually did cannot. So the three
// facts below are read out of the running process, and each is structurally impossible in a
// monolith build:
//
// 1. `MG_Config::Transport != Monolith`. In a build without MOBILEGL_BUILD_DISAGGREGATED,
// `Transport` is a `constexpr` Monolith (Config.h:514) and this whole translation unit is
// compiled out. Read from the VARIABLE, never from a log line - the log-grep spelling of
// this question is satisfied by a DEBUG-level pull build's `Config: Accepted env variable:
// MOBILEGL_TRANSPORT=inproc` (review finding M-5).
// 2. `ClientSession::Active() != nullptr`. c0 made this one deliberately return null rather
// than Fatal, because "does a session exist" has a legitimate "no" - it is the monolith
// answer (ClientSession.cpp:31). So it is exactly "a client session exists in this process",
// and no amount of editing stub MESSAGES makes a null pointer non-null.
// 3. `ImplementedVerbCount() > 0`. c0's stub returns 0; the contract gives this function the
// job of making "a table that silently lost an emitter" distinguishable from "a table that
// never had one" (EmitTables.h). Zero means there is no emitter to test.
//
// And one BEHAVIOURAL fact, which is the half that says the run went through the wire rather than
// merely that it could have: `ClientSession::Active()->Encoder().EmitSeq()`, the highest record
// ordinal this client has produced. A scenario that armed, drew, and emitted nothing has a
// sequence that did not move, and that is the shape of an emit table that resolves the transport
// and then falls through to the driver.
//
// Every entry point returns false, touching nothing, where the state is out of reach: in a build
// that never compiled MG_Remote, and on Android where this module links the shipping
// libMobileGL.so built -fvisibility=hidden. A caller that gets false must SKIP.
#pragma once
#include <string>
namespace MGITest {
// What this process can say about itself. Every field is false/0 where the peek cannot look.
struct SplitRuntimeState {
// The peek is compiled in at all (MOBILEGL_BUILD_DISAGGREGATED, not Android).
bool peekAvailable = false;
// MG_Config::Transport != Monolith - this process RESOLVED a split transport.
bool transportResolved = false;
// The resolved transport, for a message: "monolith", "inproc", "spawn", "unix", "pipe".
std::string transportName = "monolith";
// ClientSession::Active() != nullptr.
bool sessionActive = false;
// ImplementedVerbCount(), out of kRemoteEmitSlotCount (71).
unsigned int implementedVerbs = 0;
unsigned int totalVerbSlots = 0;
// The encoder's highest produced record ordinal, or 0 when there is no session.
unsigned long long emitSeq = 0;
};
SplitRuntimeState PeekSplitRuntime();
// Empty when this process is a real split run that can be asserted about; otherwise the
// reason to GTEST_SKIP() with, naming the first fact that is not true and the package that
// owns it.
std::string SplitRuntimeSkipReason();
} // namespace MGITest
@@ -72,8 +72,8 @@ namespace MGITest {
namespace {
constexpr const char* kVertexSource = R"(#version 330 core
in vec2 aPos;
in vec3 aColor;
layout(location = 0) in vec2 aPos;
layout(location = 1) in vec3 aColor;
out vec3 vColor;
void main() {
vColor = aColor;
@@ -64,8 +64,8 @@ namespace MGITest {
// MESA_GLSL_VERSION_OVERRIDE pins and what every other scenario in this module that does
// not need a later feature uses.
constexpr const char* kVertexSource = R"(#version 330 core
in vec2 aPos;
in vec3 aColor;
layout(location = 0) in vec2 aPos;
layout(location = 1) in vec3 aColor;
out vec3 vColor;
void main() {
vColor = aColor;