mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-17 00:28:31 +09:00
[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:
@@ -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,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<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 -
|
||||
@@ -141,6 +180,8 @@ namespace MGITest {
|
||||
}
|
||||
|
||||
bool m_ready = false;
|
||||
bool m_splitAssertionsArmed = false;
|
||||
unsigned long long m_emitSeqAtSetUp = 0;
|
||||
};
|
||||
|
||||
} // namespace MGITest
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user