[MG_IntegrationTest, ci] (P7): the verify-split arm proof is a per-entry census, not a floor

- the landing shape was a count - at least 850 armed of at least 1000 client logs - which leaves the remaining 155 unnamed: the lane could lose fifty arms, gain fifty skips, and the floor would not move
- the rule is now per entry and two-sided, the one dualblock-expected-fatals.txt already rides: each client log must carry the arming line, or belong to an entry ctest reported SKIPPED, or be named in the new Harness/verify-split-unarmed-expected.txt
- a gtest skip is the recognised no-verb marker and is NOT named, because the skip set moves with the driver - naming it would ratchet on lavapipe rather than on the comparator
- both other directions fail: a listed entry that armed, and a listed name with no client log in this run
- split_log_paths.py gains the verify-split-arming mode; the lane step gains --output-junit so a skip can be told from a pass; test_split_log_paths.py gains six controls covering the four ways the census can be wrong
- measured on the host over 1064 private client logs: 909 armed, 99 unarmed and skipped, 56 unarmed and named in five classes - caps/limits/extension advertisement 22, shader and program object surfaces 16, object state written and read back or a call rejected before it becomes a verb 14, framebuffer completeness queries 2 (DirectGLES only; DirectVulkan skips both), the poison parent that only forks 2
This commit is contained in:
2026-09-22 21:35:06 -04:00
parent 2424c8209d
commit b4f830942b
5 changed files with 344 additions and 18 deletions
+21 -15
View File
@@ -995,18 +995,26 @@ jobs:
run: |
ulimit -c unlimited
sudo sysctl -w kernel.core_pattern='/tmp/core.%e.%p'
ctest --output-on-failure -L '^integration-verify-split$' --no-tests=error -j 4
# --output-junit, because the arm proof below has to tell a gtest SKIP from a pass: an
# entry that skipped reached no verb honestly, and the skip set moves with the driver.
ctest --output-on-failure -L '^integration-verify-split$' --no-tests=error -j 4 \
--output-junit "${RUNNER_TEMP}/verify-split.xml"
# THE ARM PROOF, per entry. Every ambient entry has a private log path (SplitLogPaths.cmake.in),
# so this grep - unlike integration-verify's, over a shared log - is a census:
# THE ARM PROOF, PER ENTRY AND BY NAME. Every ambient entry has a private log path
# (SplitLogPaths.cmake.in), so this - unlike integration-verify's grep over a shared log -
# is a census:
# * ConfigLoader's `MOBILEGL_TRANSPORT=inproc` line in EVERY client log: an entry that ran
# monolith would have passed every assertion above and fails here by name;
# * `MGPipe: verify armed` in both VerifySplitArming. logs, and in at least 850 per-entry
# client logs. The line is latched at a process's first fill, so the processes that never
# reach a verb (limits queries, CapsMirror answers, the poison parent) honestly lack it -
# 909 of 1064 carried it on the landing run - and a count that falls through the floor
# means the comparator stopped arming somewhere after bring-up.
- name: The verify-split entries ran inproc and armed the comparator
# * `MGPipe: verify armed` in both VerifySplitArming. logs;
# * and then the census proper. This used to be a FLOOR - "at least 850 armed of at least
# 1000 logs" - which leaves the remaining ~155 unnamed, so the lane could lose fifty
# arms, gain fifty skips, and the floor would not move. It is now the two-sided rule
# dualblock-expected-fatals.txt already rides: each client log must ARM, or belong to an
# entry ctest reported SKIPPED, or be NAMED in Harness/verify-split-unarmed-expected.txt
# (56 names in five classes, each class saying why it reaches no verb). A listed entry
# that arms, and a listed name with no log at all, both fail - an exception set that
# keeps rows nothing needs is how a proof rots green.
- name: The verify-split entries ran inproc, and every one armed, skipped or is named
working-directory: build-verify-split/MobileGL/MG_IntegrationTest
run: |
shopt -s nullglob
@@ -1027,12 +1035,10 @@ jobs:
exit 1
fi
done
armed=$(grep -l 'MGPipe: verify armed' "${logs[@]}" | wc -l)
if [ "${armed}" -lt 850 ]; then
echo "::error::only ${armed} of ${#logs[@]} verify-split entries armed the comparator (floor 850, 909 at landing)"
exit 1
fi
echo "${#logs[@]} entries resolved inproc; ${armed} of them armed the comparator"
python3 "${GITHUB_WORKSPACE}/MobileGL/MG_IntegrationTest/Harness/split_log_paths.py" \
verify-split-arming verify-split-logs "${RUNNER_TEMP}/verify-split.xml" \
"${GITHUB_WORKSPACE}/MobileGL/MG_IntegrationTest/Harness/verify-split-unarmed-expected.txt"
# 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
@@ -202,6 +202,105 @@ def require_green(document, junit_path, required):
print(f"SplitLogPaths require-green: {len(by_name)} PASS, 0 skip, 0 failed, complete discovery")
VERIFY_ARMED_LINE = "MGPipe: verify armed"
def verify_split_arming(logs_dir, junit_path, expected_path):
"""THE VERIFY-SPLIT LANE'S ARM PROOF, PER ENTRY (P7 wave 3, V1 fix round).
What this replaces: a COUNT. The landing shape of the lane's arm proof asserted that at
least 850 of at least 1000 per-entry client logs carried `MGPipe: verify armed`, which
leaves ~117 entries unnamed and therefore unaccounted - a lane can lose fifty arms and
gain fifty skips and the floor never moves. The census is the point (dualblock-expected-
fatals.txt's rule, ID-119), so this is the same two-sided ratchet applied to arming:
* an entry whose client log carries the arming line is ARMED and needs nothing else;
* an entry that ctest reported SKIPPED is recognised without a name - a gtest skip is a
process that brought a session up and left before any verb, which is honestly unarmed,
and the skip set is driver-dependent (a GitHub runner's lavapipe/llvmpipe skips more
or fewer than the host's), so naming those would ratchet on the driver;
* every OTHER unarmed entry must be named in the committed expected file, which says why
each class is there.
Two-sided, and both directions are failures:
* an unlisted, unskipped entry that did not arm - the comparator stopped reaching it, or
a case stopped issuing a verb, and either way the lane is quieter than it looks;
* a LISTED entry that armed - the name is stale and the file must lose the row in the
commit that made it arm, or the exception set rots into a permanent amnesty;
* a LISTED entry with no client log in this run at all - the name no longer matches an
entry (a rename, a filter change), which would otherwise be an invisible amnesty too.
A listed entry that SKIPPED on this run is neither: it is reported and allowed, because a
driver difference is exactly what the skip recognition above exists to absorb."""
directory = Path(logs_dir)
logs = sorted(directory.glob("*.client.log"))
if not logs:
raise ValueError(f"verify-split arming: no *.client.log under {directory} - the per-entry "
f"private log paths did not take, so nothing here is a per-entry proof")
status = {}
for case in ET.parse(junit_path).getroot().iter("testcase"):
name = case.get("name")
if case.find("skipped") is not None:
status[name] = "skipped"
elif (case.find("failure") is not None or case.find("error") is not None
or case.get("status") == "fail"):
status[name] = "failed"
else:
status[name] = "passed"
expected = read_pair_set(expected_path)
armed, skipped, named, unnamed, listed_armed, no_result = [], [], [], [], [], []
for path in logs:
entry = path.name[: -len(".client.log")]
if entry not in status:
no_result.append(entry)
continue
if VERIFY_ARMED_LINE in path.read_text(errors="replace"):
armed.append(entry)
if entry in expected:
listed_armed.append(entry)
elif status[entry] == "skipped":
skipped.append(entry)
elif entry in expected:
named.append(entry)
else:
unnamed.append(entry)
seen = set(armed) | set(skipped) | set(named) | set(unnamed)
stale = sorted(expected - seen)
listed_and_skipped = sorted(expected & set(skipped))
print(f"SplitLogPaths verify-split-arming: {len(logs)} per-entry client log(s) - "
f"{len(armed)} armed, {len(skipped)} unarmed and skipped, "
f"{len(named)} unarmed and named in {Path(expected_path).name}")
if listed_and_skipped:
print(f"SplitLogPaths verify-split-arming: {len(listed_and_skipped)} named entrie(s) "
f"skipped on this driver instead of running unarmed (allowed): "
f"{', '.join(listed_and_skipped)}")
problems = []
if no_result:
problems.append("%d private log(s) have no entry in the JUnit result: %s. The glob and "
"the run disagree about the lane's members, so neither side is a census."
% (len(no_result), ", ".join(sorted(no_result)[:20])))
if unnamed:
problems.append("%d entrie(s) ran, did not skip, and never armed the comparator: %s. Each "
"is a process that reached no verb - add it to %s with the class it "
"belongs to, or find out why the fill stopped happening."
% (len(unnamed), ", ".join(sorted(unnamed)[:20]), Path(expected_path).name))
if listed_armed:
problems.append("%d entrie(s) are named in %s but DID arm: %s. Remove the row in the "
"commit that made them arm - an exception set that keeps rows nothing "
"needs any more is how this proof rots."
% (len(listed_armed), Path(expected_path).name,
", ".join(sorted(listed_armed)[:20])))
if stale:
problems.append("%d name(s) in %s match no per-entry client log in this run: %s. The "
"entry was renamed or filtered out; drop the row in the same commit."
% (len(stale), Path(expected_path).name, ", ".join(stale[:20])))
if problems:
raise ValueError("the verify-split arm proof: " + " | ".join(problems))
print(f"SplitLogPaths verify-split-arming: per-entry arm proof OK - every log armed, skipped "
f"or named ({len(expected)} name(s) in the expected set)")
def expect_fatal(document, junit_path, expected_path):
"""THE DUAL-BLOCK LANE'S CENSUS AND TWO-SIDED RATCHET (P5f f1, P5F §4/§6).
@@ -372,6 +471,13 @@ def main():
if mode == "expect-fatal":
expect_fatal(json.loads(Path(sys.argv[2]).read_text()), sys.argv[3], sys.argv[4])
return
if mode == "verify-split-arming":
# <logs dir> <junit xml> <expected names>. It takes the LOG DIRECTORY rather than the
# discovery JSON on purpose: the claim is about the files a run left on disk, and a
# helper that rebuilt the set from discovery could not notice a log that was never
# written.
verify_split_arming(sys.argv[2], sys.argv[3], sys.argv[4])
return
if mode == "require-green":
require_green(json.loads(Path(sys.argv[2]).read_text()), sys.argv[3], sys.argv[4:])
return
@@ -117,5 +117,79 @@ class ResultAccountingTest(unittest.TestCase):
helper.require_green(self.document, self.xml, [A, B])
class VerifySplitArmProofTest(unittest.TestCase):
"""The per-entry arm proof, BOTH SIDES (P7 wave 3, V1 fix round).
The thing it replaced was a count, and a count cannot be tested for the failure that
matters - "fifty arms became fifty skips" leaves it unmoved. These four cases are the four
ways the census can be wrong, and each one has to raise."""
ARMED = "DirectGLES.VerifySplit.TriangleScenario.DrawsATriangle"
QUIET = "DirectGLES.VerifySplit.AdvertisedLimitsScenario.EveryGL45CoreMinimumIsMet"
SKIPPED = "DirectVulkan.VerifySplit.F1WireScenario.SomethingLavapipeCannotDo"
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.root = Path(self.temp.name)
self.logs = self.root / "verify-split-logs"
self.logs.mkdir()
self.xml = self.root / "run.xml"
self.expected = self.root / "expected.txt"
self.expected.write_text("# the quiet one\n" + self.QUIET + "\n")
self.write_log(self.ARMED, helper.VERIFY_ARMED_LINE)
self.write_log(self.QUIET, "no fill here")
self.write_log(self.SKIPPED, "no fill here either")
self.results([(self.ARMED, "run", None), (self.QUIET, "run", None),
(self.SKIPPED, "notrun", "skipped")])
def tearDown(self):
self.temp.cleanup()
def write_log(self, entry, text):
(self.logs / (entry + ".client.log")).write_text(text + "\n")
def results(self, rows):
suite = ET.Element("testsuite")
for name, status, kind in rows:
case = ET.SubElement(suite, "testcase", name=name, status=status)
if kind:
ET.SubElement(case, kind)
ET.ElementTree(suite).write(self.xml)
def proof(self):
with contextlib.redirect_stdout(io.StringIO()):
helper.verify_split_arming(self.logs, self.xml, self.expected)
def test_armed_skipped_or_named_is_the_whole_rule(self):
self.proof()
def test_an_unlisted_quiet_entry_fails(self):
self.expected.write_text("# nobody\n")
with self.assertRaisesRegex(ValueError, "never armed"):
self.proof()
def test_a_listed_entry_that_armed_fails(self):
self.write_log(self.QUIET, helper.VERIFY_ARMED_LINE)
with self.assertRaisesRegex(ValueError, "DID arm"):
self.proof()
def test_a_listed_name_with_no_log_fails(self):
(self.logs / (self.QUIET + ".client.log")).unlink()
self.results([(self.ARMED, "run", None), (self.SKIPPED, "notrun", "skipped")])
with self.assertRaisesRegex(ValueError, "match no per-entry client log"):
self.proof()
def test_a_log_with_no_result_fails(self):
self.results([(self.ARMED, "run", None), (self.QUIET, "run", None)])
with self.assertRaisesRegex(ValueError, "no entry in the JUnit result"):
self.proof()
def test_an_empty_log_directory_is_not_a_green(self):
for path in self.logs.glob("*.client.log"):
path.unlink()
with self.assertRaisesRegex(ValueError, "did not take"):
self.proof()
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,129 @@
# THE VERIFY-SPLIT ENTRIES THAT HONESTLY NEVER ARM THE COMPARATOR, BY NAME.
#
# `MGPipe: verify armed` is latched at a process's FIRST FILL, so a process that reaches no verb
# never prints it. The lane's arm proof (.github/workflows/test.yml, integration-verify-split)
# used to allow that with a COUNT - "at least 850 of at least 1000 client logs carry the line" -
# which leaves the remainder unnamed: the lane could lose fifty arms, gain fifty skips, and the
# floor would not move. This file is the census instead, and split_log_paths.py's
# `verify-split-arming` mode is what reads it.
#
# THE RULE, PER ENTRY. Every verify-split-logs/*.client.log must either
# (a) carry the arming line, or
# (b) belong to an entry ctest reported SKIPPED - a gtest skip is a process that brought a
# session up and left before any verb, and the skip set is DRIVER-DEPENDENT (a GitHub
# runner's lavapipe/llvmpipe skips more or fewer cases than the host's), so naming those
# would ratchet on the driver rather than on the comparator, or
# (c) be named below.
#
# TWO-SIDED, the rule dualblock-expected-fatals.txt already rides (ID-119):
# * an unlisted, unskipped entry that did not arm FAILS - a case stopped issuing a verb, or
# the comparator stopped reaching it, and both are quieter lanes than they look;
# * a LISTED entry that ARMED fails - remove the row in the commit that made it arm;
# * a LISTED name with no client log in this run fails - the entry was renamed or filtered
# out, and the row is an amnesty nobody asked for.
# A listed entry that SKIPS on some driver is allowed and reported: that is (b) doing its job.
#
# HOW TO REFRESH (never by hand from a guess):
# ctest --test-dir build-split -L '^integration-verify-split$' -j 6 \
# --output-junit /tmp/verify-split.xml
# python3 MobileGL/MG_IntegrationTest/Harness/split_log_paths.py verify-split-arming \
# build-split/MobileGL/MG_IntegrationTest/verify-split-logs /tmp/verify-split.xml this-file
# and put the names it prints here, under the class they belong to.
#
# MEASURED ON THE HOST (WSL Arch + lavapipe, build-split, 1064 entries with a private log):
# 909 armed, 99 unarmed and skipped, 56 unarmed and named below.
# --------------------------------------------------------------------------------------------
# CAPS, LIMITS AND EXTENSION ADVERTISEMENT - answered from the client's CapsMirror, no verb (22)
# These call glGetIntegerv / glGetString / glGetInternalformativ and compare the answer
# against a floor or against the other width of the same query. Nothing in them reaches a fill
# point, so the arming line - latched at the process's FIRST fill - is honestly absent. They are
# the same 13 entries per backend that tier 3 measured as reds under MGITEST_SPLIT_LANE=1, for
# the same reason (a query answered without moving the encoder's record ordinal).
# --------------------------------------------------------------------------------------------
DirectGLES.VerifySplit.AdvertisedLimitsScenario.ComputeLocalSizeComesFromTheLinkedProgram
DirectGLES.VerifySplit.AdvertisedLimitsScenario.ComputeUniformBlocksIsAnAmountAnApplicationCouldActuallyAllocate
DirectGLES.VerifySplit.AdvertisedLimitsScenario.ComputeWorkGroupLimitsAreTheCapsBlocksAnswer
DirectGLES.VerifySplit.AdvertisedLimitsScenario.EveryBufferLimitIsWithinItsAdvertisedRange
DirectGLES.VerifySplit.AdvertisedLimitsScenario.EveryGL45CoreMinimumIsMet
DirectGLES.VerifySplit.AdvertisedLimitsScenario.IndexedBufferBindingsAreReportedVerbatimOnBothWidths
DirectGLES.VerifySplit.AdvertisedLimitsScenario.IndexedTargetSizeSurvivesDivisionByItsBindingCount
DirectGLES.VerifySplit.AdvertisedLimitsScenario.PerStageBlockCountsFitInTheirBindingPoints
DirectGLES.VerifySplit.AdvertisedLimitsScenario.ViewportArrayLimitsMeetTheirGL43Floors
DirectGLES.VerifySplit.DoublePrecisionScenario.TheFp64ExtensionIsNotAdvertised
DirectGLES.VerifySplit.SpirvShaderBinaryScenario.ShaderBinaryFormatIsAdvertisedExactlyOnce
DirectVulkan.VerifySplit.AdvertisedLimitsScenario.ComputeLocalSizeComesFromTheLinkedProgram
DirectVulkan.VerifySplit.AdvertisedLimitsScenario.ComputeUniformBlocksIsAnAmountAnApplicationCouldActuallyAllocate
DirectVulkan.VerifySplit.AdvertisedLimitsScenario.ComputeWorkGroupLimitsAreTheCapsBlocksAnswer
DirectVulkan.VerifySplit.AdvertisedLimitsScenario.EveryBufferLimitIsWithinItsAdvertisedRange
DirectVulkan.VerifySplit.AdvertisedLimitsScenario.EveryGL45CoreMinimumIsMet
DirectVulkan.VerifySplit.AdvertisedLimitsScenario.IndexedBufferBindingsAreReportedVerbatimOnBothWidths
DirectVulkan.VerifySplit.AdvertisedLimitsScenario.IndexedTargetSizeSurvivesDivisionByItsBindingCount
DirectVulkan.VerifySplit.AdvertisedLimitsScenario.PerStageBlockCountsFitInTheirBindingPoints
DirectVulkan.VerifySplit.AdvertisedLimitsScenario.ViewportArrayLimitsMeetTheirGL43Floors
DirectVulkan.VerifySplit.DoublePrecisionScenario.TheFp64ExtensionIsNotAdvertised
DirectVulkan.VerifySplit.SpirvShaderBinaryScenario.ShaderBinaryFormatIsAdvertisedExactlyOnce
# --------------------------------------------------------------------------------------------
# SHADER AND PROGRAM OBJECT SURFACES - compile, link, specialize, query, glGetError (16)
# glShaderBinary / glSpecializeShader / glCompileShader / glGetUniformdv / the compiler
# thread pool's settle-and-join, and the reserved-name pipeline case. Every one of them asserts on
# an error code or a program query; none issues a draw, a dispatch, a clear or a readback.
# --------------------------------------------------------------------------------------------
DirectGLES.VerifySplit.AsyncCompileScenario.CompletionStatusPollingThenForcedJoin
DirectGLES.VerifySplit.AsyncCompileScenario.ZeroCompilerThreadsSettlesEverythingImmediately
DirectGLES.VerifySplit.DoublePrecisionScenario.GetUniformdvReadsBackWhatWasStored
DirectGLES.VerifySplit.DoublePrecisionScenario.TheUniformIsStillReportedAsADouble
DirectGLES.VerifySplit.ProgramPipelineScenario.AReservedNameTakesStateBeforeItIsAProgramPipeline
DirectGLES.VerifySplit.SpirvShaderBinaryScenario.AnUnsupportedBinaryFormatIsRejectedInsteadOfSilentlyAccepted
DirectGLES.VerifySplit.SpirvShaderBinaryScenario.CompileShaderOnASpirvShaderIsInvalidOperationAndShaderSourceTakesItBack
DirectGLES.VerifySplit.SpirvShaderBinaryScenario.SpecializeShaderErrorSurfaceMatchesTheExtension
DirectVulkan.VerifySplit.AsyncCompileScenario.CompletionStatusPollingThenForcedJoin
DirectVulkan.VerifySplit.AsyncCompileScenario.ZeroCompilerThreadsSettlesEverythingImmediately
DirectVulkan.VerifySplit.DoublePrecisionScenario.GetUniformdvReadsBackWhatWasStored
DirectVulkan.VerifySplit.DoublePrecisionScenario.TheUniformIsStillReportedAsADouble
DirectVulkan.VerifySplit.ProgramPipelineScenario.AReservedNameTakesStateBeforeItIsAProgramPipeline
DirectVulkan.VerifySplit.SpirvShaderBinaryScenario.AnUnsupportedBinaryFormatIsRejectedInsteadOfSilentlyAccepted
DirectVulkan.VerifySplit.SpirvShaderBinaryScenario.CompileShaderOnASpirvShaderIsInvalidOperationAndShaderSourceTakesItBack
DirectVulkan.VerifySplit.SpirvShaderBinaryScenario.SpecializeShaderErrorSurfaceMatchesTheExtension
# --------------------------------------------------------------------------------------------
# OBJECT STATE WRITTEN AND READ BACK, OR A CALL REJECTED BEFORE IT BECOMES A VERB (14)
# glVertexAttribLFormat then glGetVertexAttribiv (the 64-bit format case does NOT draw - the
# comment above it names the Adreno null-deref that is why), glTexBufferRange then the level
# queries, glEnable then glIsEnabled, glGetIntegerv(GL_SAMPLE_MASK_WORDS) then glSampleMaski, the
# two glCopyImageSubData level-range REJECTIONS (the call errors out, so no verb is filled), and
# the arena readback, which is glBufferSubData + glGetBufferSubData and not a pipe verb.
# --------------------------------------------------------------------------------------------
DirectGLES.VerifySplit.BufferTextureScenario.LevelQueriesDescribeTheAttachedBufferRange
DirectGLES.VerifySplit.ClipDistanceScenario.EnableIsObservableThroughIsEnabled
DirectGLES.VerifySplit.CopyImageLevelRangeScenario.LevelOneOfASingleLevelTextureIsRejected
DirectGLES.VerifySplit.CopyImageLevelRangeScenario.LevelTwoOfATwoLevelTextureIsRejected
DirectGLES.VerifySplit.DoublePrecisionScenario.A64BitVertexFormatIsRecordedAndItsArrayIsDroppedAtDraw
DirectGLES.VerifySplit.LargeArenaAdoptionScenario.ReadbackSeesTheLatestCpuWrite
DirectGLES.VerifySplit.SampleMaskScopeScenario.TheAdvertisedSampleMaskWordCountMatchesWhatSampleMaskiAccepts
DirectVulkan.VerifySplit.BufferTextureScenario.LevelQueriesDescribeTheAttachedBufferRange
DirectVulkan.VerifySplit.ClipDistanceScenario.EnableIsObservableThroughIsEnabled
DirectVulkan.VerifySplit.CopyImageLevelRangeScenario.LevelOneOfASingleLevelTextureIsRejected
DirectVulkan.VerifySplit.CopyImageLevelRangeScenario.LevelTwoOfATwoLevelTextureIsRejected
DirectVulkan.VerifySplit.DoublePrecisionScenario.A64BitVertexFormatIsRecordedAndItsArrayIsDroppedAtDraw
DirectVulkan.VerifySplit.LargeArenaAdoptionScenario.ReadbackSeesTheLatestCpuWrite
DirectVulkan.VerifySplit.SampleMaskScopeScenario.TheAdvertisedSampleMaskWordCountMatchesWhatSampleMaskiAccepts
# --------------------------------------------------------------------------------------------
# FRAMEBUFFER COMPLETENESS QUERIES - attach and ask, never render (2)
# glCheckFramebufferStatus on an attachment of that format. Only the DirectGLES half is
# here: on DirectVulkan both cases SKIP (the format is not renderable on lavapipe), and a skip is
# recognised without a name.
# --------------------------------------------------------------------------------------------
DirectGLES.VerifySplit.SnormAttachmentScenario.SignedNormalizedColorAttachmentsReportComplete
DirectGLES.VerifySplit.ThreeChannelAttachmentScenario.ThreeChannelColorAttachmentsReportComplete
# --------------------------------------------------------------------------------------------
# THE POISON PARENT THAT ONLY FORKS (2)
# PoisonOmissionScenario.WithoutOmissionCompletes re-executes the binary and waits; the
# frame is drawn in the CHILD, which writes its own log (<base>.poison-child). The parent process
# brings a session up and forks, so it reaches no verb of its own.
# --------------------------------------------------------------------------------------------
DirectGLES.VerifySplit.PoisonOmissionScenario.WithoutOmissionCompletes
DirectVulkan.VerifySplit.PoisonOmissionScenario.WithoutOmissionCompletes
+14 -3
View File
@@ -96,9 +96,20 @@ upload,所以描述符宣告 `(target, level)` 而调用递空指针,会走
集合是错的(tier 3 实测 13 红全是 CapsMirror 回答的查询)。取而代之的是**逐条目私有日志**
(`SplitLogPaths.cmake.in` 的 `MGL_VERIFY_SPLIT_TEST_LISTS`)和 CI 的逐条目臂证明:每个 client 日志都必须有
ConfigLoader 的 `MOBILEGL_TRANSPORT=inproc` 行;`MGPipe: verify armed` 是进程首次 fill 时才打的,所以从不到达
动词的进程(limits 查询、CapsMirror 回答、只 fork 的 poison 父进程)诚实地没有它——落地这一轮 1064 个里
909 个有、155 个没有(其中 99 个是 skip)——CI 要求两个 `VerifySplitArming.` 日志都有,并给逐条目计数设下限
850。
动词的进程诚实地没有它。
- **臂证明是逐条目的普查,不是计数(V1 修复轮改)。** 落地形状是「≥1000 个日志里 ≥850 个 armed」,剩下的
~155 条无名——车道掉 50 个 arm、多 50 个 skip,这个下限一动不动。现在用 `dualblock-expected-fatals.txt` 那条
两侧规则(ID-119):每个 client 日志要么 **armed**,要么属于 **ctest 报 SKIPPED 的条目**(gtest skip = 起了会话
没到动词,而且 skip 集合随驱动变,点名会变成对驱动上棘轮),要么**按名**列在
`MG_IntegrationTest/Harness/verify-split-unarmed-expected.txt` 里。两侧都红:列了却 armed、列了却本轮没有日志、
没列却既没 armed 也没 skip。读表的是 `split_log_paths.py` 新增的 `verify-split-arming` 模式(`test_split_log_paths.py`
里六条控制用例覆盖四种错法);车道步骤为此加 `--output-junit`。
- **本机实测(1064 个私有 client 日志):909 armed、99 unarmed 且 skip、56 unarmed 且按名列出。** 五类,逐类原因
写在文件里:caps/limits/扩展广告(22,`glGet*` 由 client 的 CapsMirror 回答,不经填充点);shader / program 对象
与其错误面(16,编译、链接、specialize、uniform 回读、编译线程池 settle,都不画);对象状态写了再查回、或调用在
成为动词之前就被拒(14,含两条 `glCopyImageSubData` level 拒绝与 arena 的 `glGetBufferSubData` 回读);framebuffer
完整性查询(2,只有 DirectGLES 半边——DirectVulkan 上这两条 skip,由 skip 规则吸收);只 fork 的 poison 父进程(2,
帧画在子进程里、写子进程自己的日志)。
### 2.2 发现 A(真实分歧,已定位;改的是预言,不是规则)