[Test] (.github/workflows, docs/ci, MG_IntegrationTest, MG_IntegrationTest/Scenarios, MG_Test/Pipe): restore the fatal-arm, per-verb capability and verb-scope checks

- Armed requires fatal=1; the monolith control envs share the ambient lane's base list
- the capability field is corrupted through the knob at Clear, DrawArrays and ReadPixels
- the library's poison omission is checked to match its verb, not its class
This commit is contained in:
2026-10-18 10:02:02 -04:00
parent b2daab61b7
commit ead1a78bec
5 changed files with 107 additions and 54 deletions
+4 -3
View File
@@ -905,8 +905,10 @@ jobs:
# The lane's own control entries, by name: each must have run and passed, and a skip is a
# failure here although ctest reports it green. VerifyArming asserts in-test that the
# comparator printed its armed line, VerifyCorrupted that a corrupted field is reported by
# name, and PoisonOmitted that an omitted stamp kills a forked child with its own Fatal.
# comparator printed its armed line with fatal=1, under the ambient entries' own environment
# (MG_IntegrationTest/CMakeLists.txt builds both from one list), VerifyCorrupted that a
# corrupted field is reported by name, and PoisonOmitted that an omitted stamp kills a
# forked child with its own Fatal.
- name: Verify lane control entries
run: |
python3 scripts/ci/junit_tally.py "${RUNNER_TEMP}/verify.xml" \
@@ -917,7 +919,6 @@ jobs:
--require-entry-passed DirectGLES.PoisonOmitted.PoisonOmissionScenario.OmittedFieldAbortsOnThatVerb \
--require-entry-passed DirectVulkan.PoisonOmitted.PoisonOmissionScenario.OmittedFieldAbortsOnThatVerb
- name: Upload verify lane logs
if: ${{ always() }}
uses: actions/upload-artifact@v7
+30 -41
View File
@@ -1260,17 +1260,22 @@ if (MOBILEGL_PIPE_VERIFY)
# boundary and again at every accessor read, which the design budgets at 5-10x.
set(MGL_ITEST_VERIFY_TIMEOUT 900)
mgl_itest_join_environment(MGL_ITEST_GLES_VERIFY_ENVIRONMENT
"MOBILEGL_TRANSPORT=monolith"
"MOBILEGL_BACKEND_TYPE=DirectGLES" "MOBILEGL_PIPE_VERIFY=1"
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-verify-DirectGLES.log"
# What "the monolith verify lane" means, per backend, in one place: the ambient Verify. entries
# and every control entry below are this list plus their own log path and markers. So the
# controls run the configuration the ambient entries run, and an edit that leaves that lane
# unarmed or non-fatal turns PipeVerifyArmingScenario.Armed red, which asserts both.
set(MGL_ITEST_GLES_VERIFY_BASE_ENV
"MOBILEGL_TRANSPORT=monolith" "MOBILEGL_BACKEND_TYPE=DirectGLES" "MOBILEGL_PIPE_VERIFY=1"
${MGL_ITEST_COMMON_ENV})
mgl_itest_join_environment(MGL_ITEST_VULKAN_VERIFY_ENVIRONMENT
"MOBILEGL_TRANSPORT=monolith"
"MOBILEGL_BACKEND_TYPE=DirectVulkan" "MOBILEGL_PIPE_VERIFY=1"
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-verify-DirectVulkan.log"
set(MGL_ITEST_VULKAN_VERIFY_BASE_ENV
"MOBILEGL_TRANSPORT=monolith" "MOBILEGL_BACKEND_TYPE=DirectVulkan" "MOBILEGL_PIPE_VERIFY=1"
${MGL_ITEST_VULKAN_ENV})
mgl_itest_join_environment(MGL_ITEST_GLES_VERIFY_ENVIRONMENT ${MGL_ITEST_GLES_VERIFY_BASE_ENV}
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-verify-DirectGLES.log")
mgl_itest_join_environment(MGL_ITEST_VULKAN_VERIFY_ENVIRONMENT ${MGL_ITEST_VULKAN_VERIFY_BASE_ENV}
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-verify-DirectVulkan.log")
# The arming assertion's own lane, one case per backend, with a log path nothing else writes to.
#
# PipeVerifyArmingScenario.Armed reads the library's log, and the log is a per-LANE resource: it
@@ -1280,33 +1285,23 @@ if (MOBILEGL_PIPE_VERIFY)
# the primgen reroute, the point-size demotion) is registered exactly like this for the same
# reason. MGITEST_PIPE_ARMING_LANE is a harness marker - the library never reads it - and it is
# what makes the case skip in the ambient lane instead of racing there.
mgl_itest_join_environment(MGL_ITEST_GLES_VERIFY_ARMING_ENVIRONMENT
"MOBILEGL_TRANSPORT=monolith"
"MOBILEGL_BACKEND_TYPE=DirectGLES" "MOBILEGL_PIPE_VERIFY=1" "MGITEST_PIPE_ARMING_LANE=1"
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-verify-arming-DirectGLES.log"
${MGL_ITEST_COMMON_ENV})
mgl_itest_join_environment(MGL_ITEST_VULKAN_VERIFY_ARMING_ENVIRONMENT
"MOBILEGL_TRANSPORT=monolith"
"MOBILEGL_BACKEND_TYPE=DirectVulkan" "MOBILEGL_PIPE_VERIFY=1" "MGITEST_PIPE_ARMING_LANE=1"
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-verify-arming-DirectVulkan.log"
${MGL_ITEST_VULKAN_ENV})
mgl_itest_join_environment(MGL_ITEST_GLES_VERIFY_ARMING_ENVIRONMENT ${MGL_ITEST_GLES_VERIFY_BASE_ENV}
"MGITEST_PIPE_ARMING_LANE=1"
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-verify-arming-DirectGLES.log")
mgl_itest_join_environment(MGL_ITEST_VULKAN_VERIFY_ARMING_ENVIRONMENT ${MGL_ITEST_VULKAN_VERIFY_BASE_ENV}
"MGITEST_PIPE_ARMING_LANE=1"
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-verify-arming-DirectVulkan.log")
# Negative control A (G4). MOBILEGL_PIPE_VERIFY_FATAL=0 so the process SURVIVES its own
# divergence and the case can read the report back out of the log; the other half, that with
# FATAL at its default of 1 a divergence aborts, is PipeInputsTest's
# VerifyCorruptKnobNamesTheFieldAtEntry.
mgl_itest_join_environment(MGL_ITEST_GLES_VERIFY_CORRUPT_ENVIRONMENT
"MOBILEGL_TRANSPORT=monolith"
"MOBILEGL_BACKEND_TYPE=DirectGLES" "MOBILEGL_PIPE_VERIFY=1"
mgl_itest_join_environment(MGL_ITEST_GLES_VERIFY_CORRUPT_ENVIRONMENT ${MGL_ITEST_GLES_VERIFY_BASE_ENV}
"MOBILEGL_PIPE_VERIFY_CORRUPT=GetRenderStateParameters" "MOBILEGL_PIPE_VERIFY_FATAL=0"
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-verify-corrupt-DirectGLES.log"
${MGL_ITEST_COMMON_ENV})
mgl_itest_join_environment(MGL_ITEST_VULKAN_VERIFY_CORRUPT_ENVIRONMENT
"MOBILEGL_TRANSPORT=monolith"
"MOBILEGL_BACKEND_TYPE=DirectVulkan" "MOBILEGL_PIPE_VERIFY=1"
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-verify-corrupt-DirectGLES.log")
mgl_itest_join_environment(MGL_ITEST_VULKAN_VERIFY_CORRUPT_ENVIRONMENT ${MGL_ITEST_VULKAN_VERIFY_BASE_ENV}
"MOBILEGL_PIPE_VERIFY_CORRUPT=GetRenderStateParameters" "MOBILEGL_PIPE_VERIFY_FATAL=0"
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-verify-corrupt-DirectVulkan.log"
${MGL_ITEST_VULKAN_ENV})
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-verify-corrupt-DirectVulkan.log")
# Negative control B (G5). The omission skips the STAMP of one field for one verb while still
# copying its value, which is indistinguishable from a fill row nobody wrote; the scenario
@@ -1317,18 +1312,12 @@ if (MOBILEGL_PIPE_VERIFY)
# the fill (around its neutral-pack readback). GetActiveTextureUnit at GenerateMipmap was the
# frontend arm's read; the record arm resolves the texture from the verb's own handle and never
# reads it, so that pair could no longer go red for its own reason.
mgl_itest_join_environment(MGL_ITEST_GLES_POISON_OMIT_ENVIRONMENT
"MOBILEGL_TRANSPORT=monolith"
"MOBILEGL_BACKEND_TYPE=DirectGLES" "MOBILEGL_PIPE_VERIFY=1"
mgl_itest_join_environment(MGL_ITEST_GLES_POISON_OMIT_ENVIRONMENT ${MGL_ITEST_GLES_VERIFY_BASE_ENV}
"MGITEST_POISON_OMIT=ReadPixels:GetPixelStoreParameters"
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-poison-omit-DirectGLES.log"
${MGL_ITEST_COMMON_ENV})
mgl_itest_join_environment(MGL_ITEST_VULKAN_POISON_OMIT_ENVIRONMENT
"MOBILEGL_TRANSPORT=monolith"
"MOBILEGL_BACKEND_TYPE=DirectVulkan" "MOBILEGL_PIPE_VERIFY=1"
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-poison-omit-DirectGLES.log")
mgl_itest_join_environment(MGL_ITEST_VULKAN_POISON_OMIT_ENVIRONMENT ${MGL_ITEST_VULKAN_VERIFY_BASE_ENV}
"MGITEST_POISON_OMIT=ReadPixels:GetPixelStoreParameters"
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-poison-omit-DirectVulkan.log"
${MGL_ITEST_VULKAN_ENV})
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-poison-omit-DirectVulkan.log")
# The whole suite again, per backend, with the comparator armed. Same scenarios, same
# assertions, but every backend read of frontend state is now checked against a snapshot taken
@@ -1350,9 +1339,9 @@ if (MOBILEGL_PIPE_VERIFY)
)
# The arming assertion, one entry per backend. This is the entry that fails a lane whose library
# never armed: it runs the same library and the same MOBILEGL_PIPE_VERIFY=1 as the ambient
# entries above, but unlike them it cannot be green against a library with no comparator
# compiled in. Its log is its own, so `-j 4` cannot make it flake.
# never armed: it runs the same library and the same base list as the ambient entries above,
# but unlike them it cannot be green against a library with no comparator compiled in, or with
# one that only logs a divergence. Its log is its own, so `-j 4` cannot make it flake.
mgl_itest_lane("DirectGLES.VerifyArming." "PipeVerifyArmingScenario.Armed"
LABELS "integration-gpu\;integration-verify\;integration-monolith-control" TIMEOUT ${MGL_ITEST_VERIFY_TIMEOUT} ENV "${MGL_ITEST_GLES_VERIFY_ARMING_ENVIRONMENT}")
mgl_itest_lane("DirectVulkan.VerifyArming." "PipeVerifyArmingScenario.Armed"
@@ -16,10 +16,11 @@
// clean pass. That is the failure mode this scenario exists to make impossible:
//
// Armed - the environment says the comparator is on for this process, so the
// library must SAY it armed. It asserts a library observable against
// the environment, the same shape UnlocatedIoBlockScenario's arming
// case and AsyncCompileScenario::ExtensionStringMatchesTheConfiguration
// use. A lane whose library never armed FAILS here; it never passes.
// library must SAY it armed, with fatal=1. It asserts a library
// observable against the environment, the same shape
// UnlocatedIoBlockScenario's arming case and
// AsyncCompileScenario::ExtensionStringMatchesTheConfiguration use. A
// lane whose library never armed FAILS here; it never passes.
// CorruptedFieldIsReported - the negative control for the comparator itself (gate G4). With
// MOBILEGL_PIPE_VERIFY_CORRUPT naming a field, the snapshot arm is
// perturbed before the entry compare, so a comparator that works must
@@ -230,13 +231,28 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); }
// of the process, which may already have happened during the harness bring-up. The file
// is truncated at this process's first log write, so it still carries nothing else.
const std::string whole = LibraryLog();
EXPECT_NE(whole.find(kArmedLine), std::string::npos)
const std::size_t armedAt = whole.find(kArmedLine);
EXPECT_NE(armedAt, std::string::npos)
<< "MOBILEGL_PIPE_VERIFY=1 is set for this process and a frame was cleared, drawn and "
"read back, and the library never reported arming the comparator. Either this "
"library was not built with -DMOBILEGL_PIPE_VERIFY=ON (in which case the whole lane "
"is verifying nothing), or the arming MGLOG_I is gone. Log:\n"
<< whole;
// Fatal as well as armed: an ambient entry fails on a divergence only through the abort,
// so a lane armed non-fatal logs every divergence and stays green. This entry is the
// ambient entries' environment plus its marker and log (CMakeLists.txt builds both from
// one list), so its arming line answers for theirs.
if (armedAt != std::string::npos) {
const std::size_t eol = whole.find('\n', armedAt);
const std::string line =
whole.substr(armedAt, eol == std::string::npos ? std::string::npos : eol - armedAt);
EXPECT_NE(line.find("fatal=1"), std::string::npos)
<< "the comparator armed, but not fatal, so a divergence in any entry of this lane "
"is logged and the entry stays green. Arming line:\n"
<< line;
}
const std::string appended = LibraryLogSince(before);
EXPECT_EQ(appended.find(kDifferPrefix), std::string::npos)
<< "the comparator reported a push/pull divergence on an ordinary frame:\n"
+48 -1
View File
@@ -146,7 +146,8 @@ namespace {
// Negative control B, layer 1 (P1 brief D6 / G5): the omitted (verb, field) pair is the
// ONLY thing that goes stale - every other bit of the verb class's mask is fresh, every
// field outside it is not, and the verb after it neither heals the field (not in kDraw's
// mask) nor loses one of its own. The omission is the test's own (FillWithholdingTheStamp).
// mask) nor loses one of its own. The omission is the test's own (FillWithholdingTheStamp); a
// verify build also checks the scope of the library's (MGPipeSetPoisonOmission).
TEST_F(PipeInputsTest, OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale) {
#if !MOBILEGL_PIPE_POISON
GTEST_SKIP() << "poison not compiled in (MOBILEGL_PIPE_POISON=0)";
@@ -182,6 +183,24 @@ TEST_F(PipeInputsTest, OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale) {
// And the omission is scoped to its fill: the next verb of the same class stamps it again.
MGPipeValidateForVerb(MGPipeVerb::BindImageTexture);
EXPECT_TRUE(Fresh(MGPipeInputField::GetActiveTextureUnit));
#if MOBILEGL_PIPE_VERIFY && MGTEST_HAVE_FORK
// The library's own omission, the one the PoisonOmitted entries arm, stays armed across fills
// and is scoped to its VERB: a different verb of the same class keeps the stamp. In a child,
// because nothing disarms it.
const ChildResult r = RunInChild([] {
if (!MGPipeSetPoisonOmission("GenerateMipmap", "GetActiveTextureUnit")) ::_exit(3);
MGPipeValidateForVerb(MGPipeVerb::GenerateMipmap);
if (Fresh(MGPipeInputField::GetActiveTextureUnit)) ::_exit(4);
MGPipeValidateForVerb(MGPipeVerb::BindImageTexture);
if (!Fresh(MGPipeInputField::GetActiveTextureUnit)) ::_exit(5);
});
EXPECT_TRUE(ExitedWith(r, 0)) << DescribeStatus(r)
<< " (3: the setter refused the names; 4: GenerateMipmap stamped the omitted "
"field; 5: BindImageTexture did not stamp it, so the omission matched the "
"class rather than the verb)\n"
<< r.Log;
#endif
#endif
}
@@ -335,6 +354,34 @@ TEST_F(PipeInputsTest, EveryCorruptedFieldIsNamedByTheComparator) {
EXPECT_EQ(named, field) << "a corrupted " << name(field) << " was reported as " << name(named);
}
EXPECT_EQ(corruptedFields, kMGPipeInputFieldCount - kMGPipeForwardedFieldCount);
#if MGTEST_HAVE_FORK
// And the capability field through the knob and each verb's own mask, at the verbs a clear,
// draw and readback frame crosses: every one whose class fills the field must report it at
// entry, and at least one must fill it.
ASSERT_FALSE(g_logPath.empty()) << "main() did not set MOBILEGL_LOG_FILE_PATH";
static constexpr MGPipeVerb kFrameVerbs[] = {MGPipeVerb::Clear, MGPipeVerb::DrawArrays, MGPipeVerb::ReadPixels};
const ChildResult r = RunInChild([] {
MG_Config::Features.PipeVerify = true;
MG_Config::Features.PipeVerifyFatal = false;
::setenv("MOBILEGL_PIPE_VERIFY_CORRUPT", "IsCapabilityEnabled", 1);
for (const MGPipeVerb verb : kFrameVerbs)
MGPipeValidateForVerb(verb);
});
ASSERT_TRUE(ExitedWith(r, 0)) << DescribeStatus(r) << "\n" << r.Log;
SizeT filling = 0;
for (const MGPipeVerb verb : kFrameVerbs) {
const auto cls = static_cast<SizeT>(kMGPipeVerbClass[static_cast<SizeT>(verb)]);
const Bool fills = MGPipeFieldMaskHas(kMGPipeClassFieldMask[cls], MGPipeInputField::IsCapabilityEnabled);
filling += fills ? 1 : 0;
const std::string report = std::string("Fatal{PipeVerifyDiffer, \"IsCapabilityEnabled@") +
kMGPipeVerbNames[static_cast<SizeT>(verb)] + "\"";
EXPECT_EQ(r.Log.find(report) != std::string::npos, fills)
<< report << (fills ? " missing" : " unexpected") << " in\n"
<< r.Log;
}
EXPECT_GT(filling, 0u) << "no verb of a clear, draw and readback frame fills IsCapabilityEnabled";
#endif
#endif
}
+4 -4
View File
@@ -59,10 +59,10 @@ them wrong.
| 2026-10-18 | `scripts/ci/control_smoke_test.sh`, `scripts/ci/testdata/stub_ctest.sh`, `scripts/ci/testdata/split_private_log_smoke.sh`, and the `reset` / `results` / `assertion` / `evidence` modes of `split_log_paths.py` | Test / pipe-gates (R-16) | smoke cases and helpers of the step above only | none needed |
| 2026-10-18 | unit `RemoteRunAhead.ClearingTheVerbBarrierDisarmsRunAheadEvenWithTheCapBit`, and the `MOBILEGL_IPC_VERB_BARRIER` knob with its arm in the shipped client (`ClientSession`: the free-running `EmitAndWaitTails` return, the run-ahead latch term, the `gPipeInputs` guard's early return, the `verb-barrier=` field of the `Config: IPC` line) | Test / unit (split); product code | `0` was never a product mode, only E1's negative control, retired above; the unit case pinned that the control also disarmed run-ahead | `RemoteWaitBoundaryControl.AnUnwaitedPublishIsSeenAsNoWait`; `MOBILEGL_IPC_RUN_AHEAD=0` stays the lockstep A/B arm |
| 2026-10-18 | unit `SplitBufferSet.AZeroBlockSizeTurnsThePushOffRatherThanMakingItUnlimited` (and its skip stub in the non-disaggregated half), and the `MOBILEGL_IPC_PERSISTENT_BLOCK_KB` knob with its `0` arm in the shipped tracker (`PersistentMapTracker::PushBlocksForChecked`'s early return and once-per-process warning, `BufferObject::NotePersistentMapStateChanged`'s, the `persistent-block=` field of the `Config: IPC` line) | Test / unit; product code | `0` was never a product mode, only E3(a)'s negative control, retired above; the unit case pinned that `0` shipped nothing; the block size is now the constant 64 KiB (`PersistentMapTracker::BlockBytes()`) | `PersistentCoherentMapScenario.ThePushIsTheOnlyRouteAWriteTakesToTheServer` |
| 2026-10-18 | `Negative control A (corrupted snapshot)` | Test / `integration-verify` | it re-ran the five `DirectGLES.Verify.ClearThenReadPixelsScenario` entries with `MOBILEGL_PIPE_VERIFY_CORRUPT=GetRenderStateParameters` exported job-wide and accepted any red | the in-lane `DirectGLES/DirectVulkan.VerifyCorrupted.PipeVerifyArmingScenario.CorruptedFieldIsReported` entries, required by name (`Verify lane control entries`), which require `Fatal{PipeVerifyDiffer, "GetRenderStateParameters@...` in the log; the abort with `MOBILEGL_PIPE_VERIFY_FATAL` at its default is unit `PipeInputsTest.VerifyCorruptKnobNamesTheFieldAtEntry` on the verify runtime |
| 2026-10-18 | `Negative control A2 (corrupted capability)` | Test / `integration-verify` | the same five-entry rerun with `MOBILEGL_PIPE_VERIFY_CORRUPT=IsCapabilityEnabled`, accepting any red; it proved that the comparator compares the capability field (P13 W3c moved the residual block's cross-check there) | unit `PipeInputsTest.EveryCorruptedFieldIsNamedByTheComparator` (`Unit tests on the verify runtime`): every field with storage, the capability field among them, corrupted in turn must be the field the comparator names |
| 2026-10-18 | `Negative control A (corrupted snapshot)` and `Keep lane logs before controls` | Test / `integration-verify-split` | the first re-ran the ten `VerifySplit.ClearThenReadPixelsScenario` entries of both backends with the same knob and accepted any red; the second only kept the lane's logs from being overwritten by that rerun | the in-lane `VerifySplitCorrupted` and `VerifySplitReadCorrupted` entries of both backends, required by name (`Verify-split control entries`) |
| 2026-10-18 | unit `PipeInputsTest.{PoisonOmitKnobArmsTheOmission, BadPoisonOmitKnobIsFatalNamingTheKnob}`, and the `MOBILEGL_PIPE_POISON_OMIT` knob: `Features.PipePoisonOmit`, its parser on every verb (`MGPipeValidateForVerb`'s `ParsePoisonOmissionKnob`, compiled into every build) and the server stamp's own parse of it | Test / unit (every poison lane); product code | the knob existed only for negative control B, and the two cases pinned its parser and its `Fatal{PipeVerifyBadKnob}` | the PoisonOmitted and VerifySplitPoisonOmitted entries set the harness marker `MGITEST_POISON_OMIT` to the same pair, and the re-executed child arms it through `MGPipeSetPoisonOmission`, compiled only with `-DMOBILEGL_PIPE_VERIFY=ON` (`Harness/PoisonOmissionArm.h`); `OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale`, `ReadingAnOmittedFieldAbortsNamingTheVerb` and `TheMutationNoticeRefreshesTheValueButNotTheStamp` withhold the stamp test-side and still run in every poison lane |
| 2026-10-18 | `Negative control A (corrupted snapshot)` | Test / `integration-verify` | it re-ran the five `DirectGLES.Verify.ClearThenReadPixelsScenario` entries with `MOBILEGL_PIPE_VERIFY_CORRUPT=GetRenderStateParameters` exported job-wide and accepted any red | the in-lane `DirectGLES/DirectVulkan.VerifyCorrupted.PipeVerifyArmingScenario.CorruptedFieldIsReported` entries, required by name (`Verify lane control entries`), which require `Fatal{PipeVerifyDiffer, "GetRenderStateParameters@...` in the log; the abort with `MOBILEGL_PIPE_VERIFY_FATAL` at its default is unit `PipeInputsTest.VerifyCorruptKnobNamesTheFieldAtEntry` on the verify runtime; that the ambient entries run the comparator armed and fatal is `VerifyArming.PipeVerifyArmingScenario.Armed`, whose environment is now the ambient entries' list plus its marker and log, and which requires `fatal=1` on the arming line |
| 2026-10-18 | `Negative control A2 (corrupted capability)` | Test / `integration-verify` | the same five-entry rerun with `MOBILEGL_PIPE_VERIFY_CORRUPT=IsCapabilityEnabled`, accepting any red; it proved that the comparator compares the capability field (P13 W3c moved the residual block's cross-check there) | unit `PipeInputsTest.EveryCorruptedFieldIsNamedByTheComparator` (`Unit tests on the verify runtime`): every field with storage, the capability field among them, corrupted in turn must be the field the comparator names; then, in a forked child with the knob set to `IsCapabilityEnabled`, each of Clear, DrawArrays and ReadPixels whose class fills the field must report it at entry, and one must |
| 2026-10-18 | `Negative control A (corrupted snapshot)` and `Keep lane logs before controls` | Test / `integration-verify-split` | the first re-ran the ten `VerifySplit.ClearThenReadPixelsScenario` entries of both backends with the same knob and accepted any red; the second only kept the lane's logs from being overwritten by that rerun | the in-lane `VerifySplitCorrupted` and `VerifySplitReadCorrupted` entries of both backends, required by name (`Verify-split control entries`); that the ambient entries arm is the per-entry arm proof, and that they run fatal is `VerifySplitArming.PipeVerifyArmingScenario.Armed` (the ambient environment plus its marker and log), which requires `fatal=1` on the arming line |
| 2026-10-18 | unit `PipeInputsTest.{PoisonOmitKnobArmsTheOmission, BadPoisonOmitKnobIsFatalNamingTheKnob}`, and the `MOBILEGL_PIPE_POISON_OMIT` knob: `Features.PipePoisonOmit`, its parser on every verb (`MGPipeValidateForVerb`'s `ParsePoisonOmissionKnob`, compiled into every build) and the server stamp's own parse of it | Test / unit (every poison lane); product code | the knob existed only for negative control B, and the two cases pinned its parser and its `Fatal{PipeVerifyBadKnob}` | the PoisonOmitted and VerifySplitPoisonOmitted entries set the harness marker `MGITEST_POISON_OMIT` to the same pair, and the re-executed child arms it through `MGPipeSetPoisonOmission`, compiled only with `-DMOBILEGL_PIPE_VERIFY=ON` (`Harness/PoisonOmissionArm.h`); `OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale`, `ReadingAnOmittedFieldAbortsNamingTheVerb` and `TheMutationNoticeRefreshesTheValueButNotTheStamp` withhold the stamp test-side and still run in every poison lane; the setter's scope to its verb, not its class, is checked by `OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale` in a forked child, on the verify runtime only |
## Kept after investigation