From 9b5a60b2f73987009b34eb6baa361bd3477d26f8 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 18 Oct 2026 04:09:05 -0400 Subject: [PATCH] [Refactor, Test] (.github/workflows, docs/ci, docs/Disaggregated, MG_Impl/Pipe, MG_IntegrationTest, MG_IntegrationTest/Scenarios, MG_Test/Pipe, MobileGL, scripts/data): read MOBILEGL_PIPE_VERIFY_CORRUPT only in verify builds - ArmVerify reads it through LookupSetting; Features.PipeVerifyCorrupt is gone - unit PipeInputsTest.EveryCorruptedFieldIsNamedByTheComparator replaces negative control A2 - integration-verify's controls A and A2 and integration-verify-split's control A with its log copy step are retired - PipeVerifyArmingScenario.Armed no longer skips when the knob is set - skip census: the new case is a listed skip in the unit, unit-split and unit-strict lanes --- .github/workflows/test.yml | 107 +----------------- MobileGL/Config.h | 15 +-- MobileGL/ConfigLoader.cpp | 1 - MobileGL/MG_Impl/Pipe/PipeFill.cpp | 23 ++-- MobileGL/MG_IntegrationTest/CMakeLists.txt | 13 +-- .../Scenarios/PipeVerifyArmingScenario.cpp | 10 +- MobileGL/MG_Test/Pipe/PipeInputsTest.cpp | 56 ++++++++- .../design/09-build-switches-counters.md | 2 +- docs/ci/ci-cleanup-log.md | 3 + scripts/data/skip_census_baseline.json | 5 + 10 files changed, 88 insertions(+), 147 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f794f174..40dad9f8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -799,9 +799,9 @@ jobs: if-no-files-found: error # The verify lane itself, plus the controls that keep it falsifiable: its own control entries, - # checked by name, and the two corrupted-snapshot steps. They are always on, not a manual - # exercise: a gate that can only be shown to work by someone remembering to break it on purpose - # is a gate that has already stopped working. + # checked by name, and the comparator's unit cases on its library. They are always on, not a + # manual exercise: a gate that can only be shown to work by someone remembering to break it on + # purpose is a gate that has already stopped working. integration-verify: runs-on: ubuntu-26.04 timeout-minutes: 180 @@ -879,7 +879,8 @@ jobs: # skip in the `test` job's build and assert only here, as do poison-gated cases on branches # where that build is a monolith. The packaging step above ships MG_Test whole, so the cost # is the run itself. --no-tests=error, so a packaging change that dropped the unit binaries - # fails here instead of passing with nothing run. + # fails here instead of passing with nothing run. Among them is the comparator's per-field + # control, PipeInputsTest.EveryCorruptedFieldIsNamedByTheComparator. - name: Unit tests on the verify runtime (G6, G10) working-directory: build-verify run: ctest --output-on-failure -L unit --no-tests=error -j "$(nproc)" --output-junit "${RUNNER_TEMP}/unit-verify.xml" @@ -916,58 +917,6 @@ jobs: --require-entry-passed DirectGLES.PoisonOmitted.PoisonOmissionScenario.OmittedFieldAbortsOnThatVerb \ --require-entry-passed DirectVulkan.PoisonOmitted.PoisonOmissionScenario.OmittedFieldAbortsOnThatVerb - # NEGATIVE CONTROL A (gate G4). The knob perturbs one field in the snapshot arm before the - # entry compare, so a working comparator must abort the run. This step passes when ctest - # FAILS - `if ctest ...; then error` - which is the only shape that can catch a comparator - # that silently compares nothing. - # - # The knob reaches the test process through the JOB environment: no ctest ENVIRONMENT - # property on the ambient Verify. entries names it (MG_IntegrationTest/CMakeLists.txt says - # so out loud), and a property entry would otherwise override this and the control would - # prove nothing. Same precedent as MOBILEGL_ESPRYT_DISABLE_INVALIDATE_FLUSH in `integration`. - - name: Negative control A (corrupted snapshot) - working-directory: build-verify - env: - MOBILEGL_ITEST_REQUIRE_GPU: "1" - MOBILEGL_PIPE_VERIFY_CORRUPT: GetRenderStateParameters - run: | - FILTER='DirectGLES\.Verify\..*ClearThenReadPixels' - # An empty selection would ALSO make ctest exit non-zero (--no-tests=error), and this - # step reads non-zero as "the control worked" - so the selection is counted first. A - # control that passes because it ran nothing is worse than no control. - matched=$(ctest -N -L integration-verify -R "${FILTER}" | grep -cE '^ *Test *#[0-9]+:') - if [ "${matched}" -lt 1 ]; then - echo "::error::negative control A selected ${matched} tests; its filter no longer matches anything" - exit 1 - fi - if ctest --output-on-failure -L integration-verify -R "${FILTER}" --no-tests=error; then - echo "::error::MOBILEGL_PIPE_VERIFY_CORRUPT=GetRenderStateParameters left ${matched} verify entries GREEN. The comparator is not comparing, so every green entry above is green for no reason." - exit 1 - fi - echo "the corrupted field turned ${matched} selected entries red, as it must" - - # NEGATIVE CONTROL A2 (P13 W3c): THE CAPABILITY CROSS-CHECK MOVED HERE. ResidualValueBlock - # carried the 35 capability bits redundantly so the applier could trip on a capability no - # record carried; P13 deleted it, and the intent is the comparator's now. So a corrupted - # capability field must turn the same selection red, or the block's deletion lost its - # check rather than moving it. - - name: Negative control A2 (corrupted capability) - working-directory: build-verify - env: - MOBILEGL_ITEST_REQUIRE_GPU: "1" - MOBILEGL_PIPE_VERIFY_CORRUPT: IsCapabilityEnabled - run: | - FILTER='DirectGLES\.Verify\..*ClearThenReadPixels' - matched=$(ctest -N -L integration-verify -R "${FILTER}" | grep -cE '^ *Test *#[0-9]+:') - if [ "${matched}" -lt 1 ]; then - echo "::error::negative control A2 selected ${matched} tests; its filter no longer matches anything" - exit 1 - fi - if ctest --output-on-failure -L integration-verify -R "${FILTER}" --no-tests=error; then - echo "::error::MOBILEGL_PIPE_VERIFY_CORRUPT=IsCapabilityEnabled left ${matched} verify entries GREEN. The comparator does not compare the capability field, so deleting the residual block (P13 W3c) lost the check it carried." - exit 1 - fi - echo "the corrupted capability field turned ${matched} selected entries red, as it must" - name: Upload verify lane logs if: ${{ always() }} @@ -995,7 +944,7 @@ jobs: if-no-files-found: ignore # The comparator on the split (inproc) arm, both backends, with its control entries checked by - # name and the corrupted-snapshot control red on that arm. + # name. # # ITS OWN BUILD, because neither existing one can carry it: build-linux-verify configures # without MOBILEGL_BUILD_DISAGGREGATED (so MG_Remote and the transport parser do not exist and @@ -1115,49 +1064,6 @@ jobs: done python3 scripts/ci/junit_tally.py "${RUNNER_TEMP}/verify-split.xml" "${args[@]}" - # THE GREEN LANE'S LOGS, KEPT BEFORE THE CONTROLS OVERWRITE THEM. The control step below - # re-runs ambient VerifySplit. entries with a knob exported, and an ambient entry writes to - # ITS OWN per-entry path under verify-split-logs/ - which the library opens "w". So by the - # time the upload step at the end of this job runs, the selected files are the CONTROL - # runs' logs sitting under the LANE's names: an artifact showing deliberate divergences, - # with the green run they were meant to be evidence of already gone. `if: always()` on the - # upload made that worse rather than better, because a failure investigation is exactly - # when the overwritten half is the half somebody needs. - - name: Keep lane logs before controls - if: ${{ always() }} - working-directory: build-verify-split/MobileGL/MG_IntegrationTest - run: | - rm -rf verify-split-logs-lane - cp -a verify-split-logs verify-split-logs-lane - cp -p "${RUNNER_TEMP}/verify-split.xml" verify-split-logs-lane/ || true - echo "kept $(ls verify-split-logs-lane | wc -l) file(s) from the green lane run" - - # NEGATIVE CONTROL A ON THE SPLIT ARM - integration-verify's step A, same knob, same shape - # (this step passes when ctest FAILS), aimed at the ambient VerifySplit. entries of BOTH - # backends. Measured at landing: 10 of 10 red, each with - # Fatal{PipeVerifyDiffer, "GetRenderStateParameters@Clear"|"@DrawArrays", ..., where=entry}. - - name: Negative control A (corrupted snapshot) - working-directory: build-verify-split - env: - MOBILEGL_ITEST_REQUIRE_GPU: "1" - MOBILEGL_PIPE_VERIFY_CORRUPT: GetRenderStateParameters - run: | - FILTER='(DirectGLES|DirectVulkan)\.VerifySplit\..*ClearThenReadPixels' - matched=$(ctest -N -L '^integration-verify-split$' -R "${FILTER}" | grep -cE '^ *Test *#[0-9]+:') - if [ "${matched}" -lt 2 ]; then - echo "::error::split negative control A selected ${matched} tests; its filter no longer matches both backends" - exit 1 - fi - if ctest --output-on-failure -L '^integration-verify-split$' -R "${FILTER}" --no-tests=error; then - echo "::error::MOBILEGL_PIPE_VERIFY_CORRUPT=GetRenderStateParameters left ${matched} verify-split entries GREEN. The comparator is not comparing on the split arm." - exit 1 - fi - echo "the corrupted field turned ${matched} selected split entries red, as it must" - - # BOTH SETS, AND THEY ARE DIFFERENT EVIDENCE. verify-split-logs-lane/ is the green run the - # arm proof read; verify-split-logs/ is whatever the control step left behind, which for - # its entries is a deliberately red re-run under the same name. Uploading only the - # second - which is what this step did before the copy above existed - loses the first. # The run log needs a signed-in reader; annotations do not. Name every failed ctest entry, # with the lines of its output that say why, where anyone reading the run can see them. - name: Annotate failed entries @@ -1170,7 +1076,6 @@ jobs: with: name: integration-verify-split-logs path: | - build-verify-split/MobileGL/MG_IntegrationTest/verify-split-logs-lane/** build-verify-split/MobileGL/MG_IntegrationTest/verify-split-logs/** build-verify-split/MobileGL/MG_IntegrationTest/pipe-*split*.log* if-no-files-found: warn diff --git a/MobileGL/Config.h b/MobileGL/Config.h index e49f0d49..d1a56f64 100644 --- a/MobileGL/Config.h +++ b/MobileGL/Config.h @@ -463,18 +463,12 @@ namespace MobileGL::MG_Config { // the semantic gate that replaces byte identity, and it catches the dangerous // direction - a dirty bit that fires too RARELY - which no purity gate can see. Bool PipeVerify = false; - // The three knobs of the MOBILEGL_PIPE_VERIFY build (P1 brief D2). Compiled only - // under MOBILEGL_PIPE_PUSH so the pull build's FeaturesTable does not change size. // MOBILEGL_PIPE_VERIFY_FATAL: the first divergence aborts (default). 0 logs and // counts instead, for triage and for the lane that must survive to read its own - // log. Tri-state parse like PipeLegacyMemos: only an explicit falsy value turns it - // off. + // log. Tri-state parse: only an explicit falsy value turns it off. The comparator's + // negative control, MOBILEGL_PIPE_VERIFY_CORRUPT, has no field here: only a verify + // build reads it, when the comparator arms (MG_Impl/Pipe/PipeFill.cpp, ArmVerify). Bool PipeVerifyFatal = true; - // MOBILEGL_PIPE_VERIFY_CORRUPT: a field name from kMGPipeInputFieldNames[]; the - // comparator perturbs that field in the SNAPSHOT arm before the entry compare, so a - // green verify run goes red naming it (negative control A). Unknown name is - // Fatal{PipeVerifyBadKnob}. - String PipeVerifyCorrupt; // MOBILEGL_PIPE_POISON_OMIT: :; the filler skips the STAMP (not // the value) of that field for that verb, an omission indistinguishable from a // forgotten FillPoints.def row, so that verb's read of it is @@ -513,8 +507,7 @@ namespace MobileGL::MG_Config { // in MG_Backend/Init.cpp compiles away entirely rather than becoming a branch nobody can // take. A FeaturesTable member is a runtime field in every build, which is the opposite // of that; it would also resize MG_Config::Features and break G1 (the pull build's - // symbol set must not move) for the same reason the MOBILEGL_PIPE_VERIFY knobs above sit - // behind their own #if. + // symbol set must not move). // // ONE CONSEQUENCE, STATED SO IT IS NOT REDISCOVERED: in a build without the option, // MOBILEGL_TRANSPORT=inproc is ACCEPTED BY THE ENVIRONMENT AND SILENTLY IGNORED - the diff --git a/MobileGL/ConfigLoader.cpp b/MobileGL/ConfigLoader.cpp index 9487e7e6..b1d2925e 100644 --- a/MobileGL/ConfigLoader.cpp +++ b/MobileGL/ConfigLoader.cpp @@ -452,7 +452,6 @@ namespace MobileGL::MG_ConfigLoader { // Defaults ON: read as a tri-state so only an explicitly falsy value turns it off. features.PipeVerifyFatal = QueryEnvQuirkOverride("MOBILEGL_PIPE_VERIFY_FATAL") != MG_Config::QuirkOverride::ForceOff; - QueryEnvVariable("MOBILEGL_PIPE_VERIFY_CORRUPT", features.PipeVerifyCorrupt, ""); QueryEnvVariable("MOBILEGL_PIPE_POISON_OMIT", features.PipePoisonOmit, ""); features.PipeStats = QueryEnvFlag("MOBILEGL_PIPE_STATS"); features.PipeIndexMirrorMb = QueryEnvUint32("MOBILEGL_PIPE_INDEX_MIRROR_MB", 64, 0, 4096); diff --git a/MobileGL/MG_Impl/Pipe/PipeFill.cpp b/MobileGL/MG_Impl/Pipe/PipeFill.cpp index 09ab3d8e..4a5848bd 100644 --- a/MobileGL/MG_Impl/Pipe/PipeFill.cpp +++ b/MobileGL/MG_Impl/Pipe/PipeFill.cpp @@ -10,8 +10,7 @@ // the push arm that reads MG_State::pGLContext. Holds the per-verb filler, the F-class // forwarders, IsLive, the MOBILEGL_PIPE_POISON_OMIT knob and - in a verify build - the // second arm (SnapshotFromGLContext), the entry compare, the compare-at-read hook and the -// MOBILEGL_PIPE_VERIFY_CORRUPT / _FATAL knobs. Compiled only under MOBILEGL_PIPE_PUSH -// (CMakeLists.txt appends it to SOURCE_FILES there). +// MOBILEGL_PIPE_VERIFY_CORRUPT knob, which only this build reads. #include #include // C-1: the vertex-elements CSO's death path raises the backend notice from here, between the @@ -44,6 +43,8 @@ #if MOBILEGL_PIPE_VERIFY #include #include +// MG_ConfigLoader::LookupSetting: ArmVerify reads MOBILEGL_PIPE_VERIFY_CORRUPT itself. +#include #endif #if MOBILEGL_BUILD_DISAGGREGATED @@ -543,7 +544,6 @@ namespace MobileGL::MG_Pipe { Bool Fatal = true; Bool InHook = false; // a re-read that re-enters an accessor is not re-verified Optional Corrupt; - String CorruptKnob; // the MOBILEGL_PIPE_VERIFY_CORRUPT value the last arm saw std::atomic Divergences{0}; Uint64 Summarised = 0; // how many of them the summary line has reported so far // THE SUMMARY IS WRITTEN FROM MobileGL::Destroy, NOT FROM HERE (V1 fix round 2). This @@ -568,24 +568,25 @@ namespace MobileGL::MG_Pipe { }; VerifyState g_verify; - // Armed on the first fill and re-armed when any of the three verify knobs' - // Features value (PipeVerify, PipeVerifyFatal, PipeVerifyCorrupt) differs from what - // the last arm latched (the same reason as ParsePoisonOmissionKnob: one arm per lane - // process, a fresh arm for a forked test child that turns a knob after its parent - // filled). Cost: two Bool compares and one String compare per fill, verify builds only. + // Armed on the first fill and re-armed when PipeVerify or PipeVerifyFatal differs from + // what the last arm latched: one arm per lane process, a fresh arm for a forked test + // child that turns a knob after its parent filled. Each arm that enables the comparator + // reads MOBILEGL_PIPE_VERIFY_CORRUPT the way the loader resolves every knob (the + // environment, then the system configuration), so the knob exists in this build only. + // Cost: two Bool compares per fill, verify builds only. void ArmVerify() { const auto& features = MG_Config::Features; if (g_verify.Parsed && g_verify.Enabled == features.PipeVerify && - g_verify.Fatal == features.PipeVerifyFatal && g_verify.CorruptKnob == features.PipeVerifyCorrupt) { + g_verify.Fatal == features.PipeVerifyFatal) { return; } g_verify.Parsed = true; g_verify.Enabled = features.PipeVerify; g_verify.Fatal = features.PipeVerifyFatal; - g_verify.CorruptKnob = features.PipeVerifyCorrupt; g_verify.Corrupt = Optional{}; if (!g_verify.Enabled) return; - const String& corrupt = g_verify.CorruptKnob; + String corrupt; + (void)MG_ConfigLoader::LookupSetting("MOBILEGL_PIPE_VERIFY_CORRUPT", corrupt); if (!corrupt.empty()) { const auto field = MGPipeFindInputField(corrupt.c_str()); if (!field) { diff --git a/MobileGL/MG_IntegrationTest/CMakeLists.txt b/MobileGL/MG_IntegrationTest/CMakeLists.txt index 9ef003cc..e1fd0061 100644 --- a/MobileGL/MG_IntegrationTest/CMakeLists.txt +++ b/MobileGL/MG_IntegrationTest/CMakeLists.txt @@ -1001,9 +1001,7 @@ mgl_itest_lane("DirectVulkan.PointSizeDemotion." "PointSizeDemotionScenario.*" # --- MOBILEGL_PIPE_VERIFY lanes (ARCHITECTURE.md §13.2), under `if (MOBILEGL_PIPE_VERIFY)` below # They exist only in a -DMOBILEGL_PIPE_VERIFY=ON build, so `ctest -L integration-verify -# --no-tests=error` fails when the option is lost. The ambient Verify. entries must not set -# MOBILEGL_PIPE_VERIFY_CORRUPT or MOBILEGL_PIPE_POISON_OMIT: CI's negative controls export them -# in the job environment, and a property of the same name would override them. +# --no-tests=error` fails when the option is lost. # --- handle ABA: one always-on monolith HandleRecycle.Handles lane per backend ------------- # MGITEST_HANDLE_ARM selects the arm (the scenario skips in the ambient entries). The lanes run # the default subsystem mask, so they measure the {slot, gen} key that ships. @@ -1293,9 +1291,9 @@ if (MOBILEGL_PIPE_VERIFY) ${MGL_ITEST_VULKAN_ENV}) # 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 CI step that exports - # the same corruption against the ambient lane, where FATAL keeps its default of 1, asserts - # the other half - that a divergence aborts and reds the entry. + # 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" @@ -3641,8 +3639,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED AND MOBILEGL_PIPE_VERIFY) # four entries with the split environment underneath and nothing else changed: each is # green when its control REPORTS, so each goes red the day the comparator or the poison # stops reaching the split arm. test.yml's integration-verify-split job requires each of - # them to pass by name, and control A's CI half - the same knob exported against the - # ambient VerifySplit. entries, where ctest has to FAIL - runs there too. + # them to pass by name. # # A (G4): the corruption perturbs the snapshot arm at the CLIENT's entry compare, so the # report lands in the client role's log, which is the half LibraryLog() reads. diff --git a/MobileGL/MG_IntegrationTest/Scenarios/PipeVerifyArmingScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/PipeVerifyArmingScenario.cpp index d1b20ea6..c61afa65 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/PipeVerifyArmingScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/PipeVerifyArmingScenario.cpp @@ -221,11 +221,6 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); } "set, so the library has nowhere to record that it armed; the Verify. " "ctest entries set both"; } - if (StringKnobIsSet("MOBILEGL_PIPE_VERIFY_CORRUPT")) { - GTEST_SKIP() << "MOBILEGL_PIPE_VERIFY_CORRUPT is armed in this process, so a divergence " - "is the EXPECTED outcome and asserting on its absence here would be " - "backwards; the VerifyCorrupted. lane owns that half"; - } const MGITest::PipeStatsWindow::LogMark before = LibraryLogMark(); ASSERT_NO_FATAL_FAILURE(DrawOneFrame()); @@ -257,9 +252,8 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); } // // It runs in its own lane (VerifyCorrupted.) because the knob is process-wide, and with // MOBILEGL_PIPE_VERIFY_FATAL=0 so the process survives its own divergence and this case can - // read the report back out of the log. The CI step that runs the SAME knob against the - // ambient lane - where FATAL keeps its default - asserts the other half: there, the - // divergence must abort and ctest must go red. + // read the report back out of the log. With FATAL at its default the divergence aborts + // instead, which PipeInputsTest.VerifyCorruptKnobNamesTheFieldAtEntry asserts. TEST_F(PipeVerifyArmingScenario, CorruptedFieldIsReported) { if (!Ready()) return; diff --git a/MobileGL/MG_Test/Pipe/PipeInputsTest.cpp b/MobileGL/MG_Test/Pipe/PipeInputsTest.cpp index 41ea85d1..ecaa280b 100644 --- a/MobileGL/MG_Test/Pipe/PipeInputsTest.cpp +++ b/MobileGL/MG_Test/Pipe/PipeInputsTest.cpp @@ -336,6 +336,50 @@ TEST_F(PipeInputsTest, CorruptedSnapshotFieldIsNamedWithItsSerial) { #endif } +// The same control for EVERY field, which is where P13 W3c moved the capability cross-check the +// residual block used to carry: two snapshots of one live context compare equal over all fields, +// and corrupting any one field that has storage makes the compare name exactly that field. The +// seven forwarded fields have no storage, so there is nothing to corrupt and nothing to compare. +TEST_F(PipeInputsTest, EveryCorruptedFieldIsNamedByTheComparator) { +#if !MOBILEGL_PIPE_VERIFY + GTEST_SKIP() << "verify not compiled in (MOBILEGL_PIPE_VERIFY=OFF)"; +#else + constexpr MGPipeFieldMask kEveryField{{~Uint64{0}, ~Uint64{0}}}; + const auto name = [](MGPipeInputField field) { + return field == MGPipeInputField::kFieldCount ? "" : kMGPipeInputFieldNames[static_cast(field)]; + }; + // A fresh block per snapshot: the snapshot copies only what the context holds (the binding + // points past the touched count keep their storage), so a reused block would carry one + // field's corruption into the next field's compare. + const auto snapshot = [&] { + auto block = MakeUnique(); + SnapshotFromGLContext(*block, kEveryField); + return block; + }; + const auto reference = snapshot(); + MGPipeInputField named = MGPipeInputField::kFieldCount; + ASSERT_TRUE(MGPipeVerifyInputs(*reference, *snapshot(), kEveryField, &named)) + << "two snapshots of one context differ at " << name(named); + + SizeT corruptedFields = 0; + for (SizeT f = 0; f < kMGPipeInputFieldCount; ++f) { + const auto field = static_cast(f); + const auto corrupted = snapshot(); + if (kMGPipeInputFieldSticky[f]) { + EXPECT_FALSE(MGPipeApplyVerifyCorruption(*corrupted, field)) << name(field) << " is forwarded"; + continue; + } + ASSERT_TRUE(MGPipeApplyVerifyCorruption(*corrupted, field)) << name(field) << " has no storage to corrupt"; + ++corruptedFields; + named = MGPipeInputField::kFieldCount; + EXPECT_FALSE(MGPipeVerifyInputs(*reference, *corrupted, kEveryField, &named)) + << "the comparator does not see a corrupted " << name(field); + EXPECT_EQ(named, field) << "a corrupted " << name(field) << " was reported as " << name(named); + } + EXPECT_EQ(corruptedFields, kMGPipeInputFieldCount - kMGPipeForwardedFieldCount); +#endif +} + // The compare-at-read arm (P1 brief D8, the arm that is real in P1): a value that changes in // the live context between the verb boundary and the read is Fatal{PipeVerifyDiffer, // "Field@Verb", verb=, where=read}; the same read before the mutation completes. @@ -370,9 +414,9 @@ TEST_F(PipeInputsTest, MutatedFieldIsNamedAtRead) { #endif } -// The MOBILEGL_PIPE_VERIFY_CORRUPT parser through Features: the fill after the child arms it -// logs both arming lines and the entry compare names the corrupted field at that fill's -// serial, where=entry. +// The MOBILEGL_PIPE_VERIFY_CORRUPT knob through the environment, where the lanes set it: the +// fill after the child sets it logs both arming lines and the entry compare names the +// corrupted field at that fill's serial, where=entry. TEST_F(PipeInputsTest, VerifyCorruptKnobNamesTheFieldAtEntry) { #if !MOBILEGL_PIPE_VERIFY GTEST_SKIP() << "verify not compiled in (MOBILEGL_PIPE_VERIFY=OFF)"; @@ -384,7 +428,7 @@ TEST_F(PipeInputsTest, VerifyCorruptKnobNamesTheFieldAtEntry) { const Uint64 serial = gPipeInputs->FilledState().CurrentVerbSerial + 1; const ChildResult r = RunInChild([] { MG_Config::Features.PipeVerify = true; - MG_Config::Features.PipeVerifyCorrupt = "GetRenderStateParameters"; + ::setenv("MOBILEGL_PIPE_VERIFY_CORRUPT", "GetRenderStateParameters", 1); MGPipeValidateForVerb(MGPipeVerb::DrawArrays); ::_exit(3); }); @@ -409,7 +453,7 @@ TEST_F(PipeInputsTest, BadVerifyCorruptKnobIsFatalNamingTheKnob) { ASSERT_FALSE(g_logPath.empty()) << "main() did not set MOBILEGL_LOG_FILE_PATH"; const ChildResult r = RunInChild([] { MG_Config::Features.PipeVerify = true; - MG_Config::Features.PipeVerifyCorrupt = "NoSuchField"; + ::setenv("MOBILEGL_PIPE_VERIFY_CORRUPT", "NoSuchField", 1); MGPipeValidateForVerb(MGPipeVerb::DrawArrays); ::_exit(3); }); @@ -439,7 +483,7 @@ TEST_F(PipeInputsTest, VerifyFatalOffLogsTheDivergenceAndContinues) { const ChildResult r = RunInChild([] { MG_Config::Features.PipeVerify = true; MG_Config::Features.PipeVerifyFatal = false; - MG_Config::Features.PipeVerifyCorrupt = "GetRenderStateParameters"; + ::setenv("MOBILEGL_PIPE_VERIFY_CORRUPT", "GetRenderStateParameters", 1); MGPipeValidateForVerb(MGPipeVerb::DrawArrays); MGPipeValidateForVerb(MGPipeVerb::DrawElements); std::exit(0); diff --git a/docs/Disaggregated/design/09-build-switches-counters.md b/docs/Disaggregated/design/09-build-switches-counters.md index fbc1cef6..2faed9b0 100644 --- a/docs/Disaggregated/design/09-build-switches-counters.md +++ b/docs/Disaggregated/design/09-build-switches-counters.md @@ -47,7 +47,7 @@ CMake:`MOBILEGL_BUILD_DISAGGREGATED`(OFF)、`MOBILEGL_BUILD_DISAGGREGATED_ | 变量 | 默认 | 说明 | |---|---|---| | `MOBILEGL_PIPE_PUSH` | `0x3fff` | 子系统位图(`MG_Pipe/MGPipe.h`,位永不复用);P13 起位 0–13 固定开,清任何一位都在启动时具名拒绝;只剩位 63("关 CSO 内容寻址"的行为对照)可设 | -| `MOBILEGL_PIPE_VERIFY` / `_VERIFY_FATAL` / `_VERIFY_CORRUPT` / `_POISON_OMIT` | 0 / 1 / 空 / 空 | 影子比对与两个阴性对照(verify 构建才有) | +| `MOBILEGL_PIPE_VERIFY` / `_VERIFY_FATAL` / `_VERIFY_CORRUPT` / `_POISON_OMIT` | 0 / 1 / 空 / 空 | 影子比对与两个阴性对照。比对器只编进 verify 构建,其他构建里 `MOBILEGL_PIPE_VERIFY=1` 只告警;`_VERIFY_CORRUPT` 不进 `Config.h`,只有 verify 构建在比对器武装时读它(`PipeFill.cpp` 的 `ArmVerify`,同样经环境与系统配置文件) | | `MOBILEGL_PIPE_STATS` / `_STATS_PERIOD` / `_STATS_FILE` | 0 / 120 / 空 | 边界计数器(附 B);dump 按角色写 `.client.json` / `.server.json` | | `MOBILEGL_PIPE_INDEX_MIRROR_MB` | 64 | 索引镜像预算(P8);`_LEGACY_MEMOS`、`_TEXEL_RETAIN_MB`、`_HANDLE_ABA_CONTROL` 随各自的臂在 P13 删除 | diff --git a/docs/ci/ci-cleanup-log.md b/docs/ci/ci-cleanup-log.md index 7475457e..96bf6daf 100644 --- a/docs/ci/ci-cleanup-log.md +++ b/docs/ci/ci-cleanup-log.md @@ -59,6 +59,9 @@ 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`) | ## Kept after investigation diff --git a/scripts/data/skip_census_baseline.json b/scripts/data/skip_census_baseline.json index 4994204e..ac8bddb7 100644 --- a/scripts/data/skip_census_baseline.json +++ b/scripts/data/skip_census_baseline.json @@ -9373,6 +9373,7 @@ "PipeInputsTest.AFrontendMutationInsideAVerbDoesNotDivergeAtRead", "PipeInputsTest.BadVerifyCorruptKnobIsFatalNamingTheKnob", "PipeInputsTest.CorruptedSnapshotFieldIsNamedWithItsSerial", + "PipeInputsTest.EveryCorruptedFieldIsNamedByTheComparator", "PipeInputsTest.MutatedFieldIsNamedAtRead", "PipeInputsTest.VerifyCorruptKnobNamesTheFieldAtEntry", "PipeInputsTest.VerifyFatalOffLogsTheDivergenceAndContinues", @@ -10505,6 +10506,7 @@ "PipeInputsTest.BadPoisonOmitKnobIsFatalNamingTheKnob", "PipeInputsTest.BadVerifyCorruptKnobIsFatalNamingTheKnob", "PipeInputsTest.CorruptedSnapshotFieldIsNamedWithItsSerial", + "PipeInputsTest.EveryCorruptedFieldIsNamedByTheComparator", "PipeInputsTest.EveryVerbFillsItsClassAndNothingElse", "PipeInputsTest.MutatedFieldIsNamedAtRead", "PipeInputsTest.OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale", @@ -14357,6 +14359,7 @@ "PipeInputsTest.AFrontendMutationInsideAVerbDoesNotDivergeAtRead", "PipeInputsTest.BadVerifyCorruptKnobIsFatalNamingTheKnob", "PipeInputsTest.CorruptedSnapshotFieldIsNamedWithItsSerial", + "PipeInputsTest.EveryCorruptedFieldIsNamedByTheComparator", "PipeInputsTest.MutatedFieldIsNamedAtRead", "PipeInputsTest.VerifyCorruptKnobNamesTheFieldAtEntry", "PipeInputsTest.VerifyFatalOffLogsTheDivergenceAndContinues", @@ -16801,6 +16804,7 @@ "PipeInputsTest.BadPoisonOmitKnobIsFatalNamingTheKnob", "PipeInputsTest.BadVerifyCorruptKnobIsFatalNamingTheKnob", "PipeInputsTest.CorruptedSnapshotFieldIsNamedWithItsSerial", + "PipeInputsTest.EveryCorruptedFieldIsNamedByTheComparator", "PipeInputsTest.EveryVerbFillsItsClassAndNothingElse", "PipeInputsTest.MutatedFieldIsNamedAtRead", "PipeInputsTest.OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale", @@ -19417,6 +19421,7 @@ "PipeInputsTest.AFrontendMutationInsideAVerbDoesNotDivergeAtRead", "PipeInputsTest.BadVerifyCorruptKnobIsFatalNamingTheKnob", "PipeInputsTest.CorruptedSnapshotFieldIsNamedWithItsSerial", + "PipeInputsTest.EveryCorruptedFieldIsNamedByTheComparator", "PipeInputsTest.MutatedFieldIsNamedAtRead", "PipeInputsTest.VerifyCorruptKnobNamesTheFieldAtEntry", "PipeInputsTest.VerifyFatalOffLogsTheDivergenceAndContinues",