mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[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
This commit is contained in:
+6
-101
@@ -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
|
||||
|
||||
+4
-11
@@ -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: <Verb>:<FieldName>; 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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 <MG_State/GLState/Core.h>
|
||||
#include <MG_State/GLState/BufferState/BufferState.h>
|
||||
// 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 <MG_Impl/Pipe/Verb/VerbPort.h>
|
||||
#include <MG_Impl/Pipe/Verb/GpuWriteSet.h>
|
||||
// MG_ConfigLoader::LookupSetting: ArmVerify reads MOBILEGL_PIPE_VERIFY_CORRUPT itself.
|
||||
#include <Init.h>
|
||||
#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<MGPipeInputField> Corrupt;
|
||||
String CorruptKnob; // the MOBILEGL_PIPE_VERIFY_CORRUPT value the last arm saw
|
||||
std::atomic<Uint64> 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<MGPipeInputField>{};
|
||||
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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 ? "<none>" : kMGPipeInputFieldNames[static_cast<SizeT>(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<PipeInputs>();
|
||||
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<MGPipeInputField>(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=<serial>, 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);
|
||||
|
||||
@@ -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 按角色写 `<base>.client.json` / `<base>.server.json` |
|
||||
| `MOBILEGL_PIPE_INDEX_MIRROR_MB` | 64 | 索引镜像预算(P8);`_LEGACY_MEMOS`、`_TEXEL_RETAIN_MB`、`_HANDLE_ABA_CONTROL` 随各自的臂在 P13 删除 |
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user