From 8b425a97185033885c7a45583be2497560e79782 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 11 Sep 2026 15:30:17 -0400 Subject: [PATCH] [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 --- MobileGL/MG_IntegrationTest/CMakeLists.txt | 134 ++++++++++++------ .../Harness/ScenarioFixture.h | 73 +++++++--- .../MG_IntegrationTest/Harness/SplitLane.h | 85 +++++------ .../Harness/SplitRuntimePeek.cpp | 88 ++++++++++++ .../Harness/SplitRuntimePeek.h | 82 +++++++++++ .../PersistentCoherentMapScenario.cpp | 4 +- .../Scenarios/TriangleScenario.cpp | 4 +- 7 files changed, 353 insertions(+), 117 deletions(-) create mode 100644 MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.cpp create mode 100644 MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.h diff --git a/MobileGL/MG_IntegrationTest/CMakeLists.txt b/MobileGL/MG_IntegrationTest/CMakeLists.txt index 2f091d53..4ebf41d2 100644 --- a/MobileGL/MG_IntegrationTest/CMakeLists.txt +++ b/MobileGL/MG_IntegrationTest/CMakeLists.txt @@ -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() diff --git a/MobileGL/MG_IntegrationTest/Harness/ScenarioFixture.h b/MobileGL/MG_IntegrationTest/Harness/ScenarioFixture.h index 63fedc44..e3055c04 100644 --- a/MobileGL/MG_IntegrationTest/Harness/ScenarioFixture.h +++ b/MobileGL/MG_IntegrationTest/Harness/ScenarioFixture.h @@ -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,23 +123,35 @@ 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. - if (const std::string splitSkip = SplitLane::SkipReasonForSplitOnlyAssertions(); - SplitLane::IsSplitLane() && !splitSkip.empty()) { - GTEST_SKIP() << splitSkip; + // 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(); + !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(state.implementedVerbs)); + RecordProperty("split_emit_seq_at_setup", static_cast(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 - @@ -141,6 +180,8 @@ namespace MGITest { } bool m_ready = false; + bool m_splitAssertionsArmed = false; + unsigned long long m_emitSeqAtSetUp = 0; }; } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Harness/SplitLane.h b/MobileGL/MG_IntegrationTest/Harness/SplitLane.h index 87162c96..35b7df60 100644 --- a/MobileGL/MG_IntegrationTest/Harness/SplitLane.h +++ b/MobileGL/MG_IntegrationTest/Harness/SplitLane.h @@ -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 #include +#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 diff --git a/MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.cpp b/MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.cpp new file mode 100644 index 00000000..87c936c5 --- /dev/null +++ b/MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.cpp @@ -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 + +#include +#include +#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 diff --git a/MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.h b/MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.h new file mode 100644 index 00000000..3ee52772 --- /dev/null +++ b/MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.h @@ -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 + +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 diff --git a/MobileGL/MG_IntegrationTest/Scenarios/PersistentCoherentMapScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/PersistentCoherentMapScenario.cpp index c8e141eb..c6e08c73 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/PersistentCoherentMapScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/PersistentCoherentMapScenario.cpp @@ -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; diff --git a/MobileGL/MG_IntegrationTest/Scenarios/TriangleScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/TriangleScenario.cpp index fe0b69bf..7f0e67ce 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/TriangleScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/TriangleScenario.cpp @@ -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;