From b4f830942b0bc9cdc27ed78cf9b2490870c2f686 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 22 Sep 2026 21:19:46 -0400 Subject: [PATCH] [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 --- .github/workflows/test.yml | 36 +++-- .../Harness/split_log_paths.py | 106 ++++++++++++++ .../Harness/test_split_log_paths.py | 74 ++++++++++ .../Harness/verify-split-unarmed-expected.txt | 129 ++++++++++++++++++ docs/Disaggregated/notes/p7/verify-split.md | 17 ++- 5 files changed, 344 insertions(+), 18 deletions(-) create mode 100644 MobileGL/MG_IntegrationTest/Harness/verify-split-unarmed-expected.txt diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 33a69729..01e523da 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/MobileGL/MG_IntegrationTest/Harness/split_log_paths.py b/MobileGL/MG_IntegrationTest/Harness/split_log_paths.py index 63155489..a7cdcaee 100644 --- a/MobileGL/MG_IntegrationTest/Harness/split_log_paths.py +++ b/MobileGL/MG_IntegrationTest/Harness/split_log_paths.py @@ -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": + # . 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 diff --git a/MobileGL/MG_IntegrationTest/Harness/test_split_log_paths.py b/MobileGL/MG_IntegrationTest/Harness/test_split_log_paths.py index b416685c..80e7a092 100644 --- a/MobileGL/MG_IntegrationTest/Harness/test_split_log_paths.py +++ b/MobileGL/MG_IntegrationTest/Harness/test_split_log_paths.py @@ -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() diff --git a/MobileGL/MG_IntegrationTest/Harness/verify-split-unarmed-expected.txt b/MobileGL/MG_IntegrationTest/Harness/verify-split-unarmed-expected.txt new file mode 100644 index 00000000..5c6bc713 --- /dev/null +++ b/MobileGL/MG_IntegrationTest/Harness/verify-split-unarmed-expected.txt @@ -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 (.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 diff --git a/docs/Disaggregated/notes/p7/verify-split.md b/docs/Disaggregated/notes/p7/verify-split.md index f5401963..f886531d 100644 --- a/docs/Disaggregated/notes/p7/verify-split.md +++ b/docs/Disaggregated/notes/p7/verify-split.md @@ -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(真实分歧,已定位;改的是预言,不是规则)