From d8edc8baa2f1c389d1a5ed219718aaf2d421bf91 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Mon, 5 Oct 2026 08:45:28 -0400 Subject: [PATCH] [Fix] (MG_IntegrationTest): validate specialized controls and classify strict skips --- .github/workflows/test.yml | 48 ++++---- MobileGL/MG_IntegrationTest/CMakeLists.txt | 49 ++++++-- .../Harness/split_log_paths.py | 114 +++++++++++++++++- .../Harness/strict-known-limitations.md | 36 ++++++ .../Harness/test_split_log_paths.py | 37 ++++++ scripts/ci/spawn_lane_parity.py | 12 +- scripts/data/split_coverage_exemptions.txt | 7 ++ 7 files changed, 262 insertions(+), 41 deletions(-) create mode 100644 MobileGL/MG_IntegrationTest/Harness/strict-known-limitations.md diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b24db128..58662d33 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1690,27 +1690,11 @@ jobs: path: /tmp/core.* if-no-files-found: ignore - # P5c gt (CONTRACT-P5C §6): the strict twin of integration-split. The role guards (the two - # layers of Fatal{RoleViolation, ...}) are COMPILED into every split build - there is no - # knob to arm them - so "guards armed" below means MOBILEGL_IPC_STRICT_ERRORS=1, the knob - # that promotes a BARRIER-PULLED residual read from counted debt to a named abort. - # - # TWO ASSERTIONS, NOT ONE, because the two halves of "strict is armed" are different - # statements: - # - # * the unit label must stay GREEN under it. A unit case that newly aborts under strict - # is a value-class read that crept back in (P5c rv's exit line is value-class = 0), and - # this step is what says so. FieldOwnershipTest owns the strict arms' behaviour cases; - # - # * the integration-split scenarios must all ABORT under it, BY DESIGN: the remaining - # residual pulls are the pinned object-class rows (GetFramebufferBindingSlot & co., - # P3b/P4b/P7's), every drawing scenario reads one, and strict turns the first read into - # Fatal{UnmigratedPipeInput, ...} [BARRIER-PULLED, MOBILEGL_IPC_STRICT_ERRORS=1, ...]. - # A lane that "passed" a scenario here would mean strict never armed - the same - # two-state shape as the E1/E3(a) negative controls, so the step asserts the abort AND - # greps the entry's private split-log for the strict marker. This half flips meaning - # when the object-class rows retire (P7): the scenarios then PASS under strict and the - # step is rewritten as an ordinary green lane. + # Shares build-linux-split with the functional/transport job above. This job + # checks state ownership: strict residual-read rules, distinct client/server + # blocks, zero-residual assertions, and complete per-entry result accounting. + # Configured controls must pass without skips. Capability gaps and known + # limitations are reported separately and accepted only by exact name/reason. integration-split-strict: runs-on: ubuntu-latest timeout-minutes: 60 @@ -1918,6 +1902,22 @@ jobs: # ---- P5f (f1), P5F-WIRE-COMPLETENESS.md §4: THE DUAL-BLOCK CENSUS LANE --------------- # # The same integration-split entries (plus Magma's own lane), re-run under + # These entries own assertions removed from generic transport registrations. + # Discovery, execution and zero-skip accounting must agree on the exact set. + - name: Specialized configuration controls + working-directory: build-split + env: + MOBILEGL_ITEST_REQUIRE_GPU: "1" + run: | + helper="${GITHUB_WORKSPACE}/MobileGL/MG_IntegrationTest/Harness/split_log_paths.py" + ctest --show-only=json-v1 > "${RUNNER_TEMP}/specialized-all.json" + python3 "${helper}" select-specialized "${RUNNER_TEMP}/specialized-all.json" \ + "${RUNNER_TEMP}/specialized-selected.json" "${RUNNER_TEMP}/specialized-tests.txt" + ctest --tests-from-file "${RUNNER_TEMP}/specialized-tests.txt" --no-tests=error -j 4 \ + --output-on-failure --output-junit "${RUNNER_TEMP}/specialized.xml" + python3 "${helper}" specialized-results "${RUNNER_TEMP}/specialized-all.json" \ + "${RUNNER_TEMP}/specialized.xml" + # MOBILEGL_IPC_ROLE_SPLIT_STATE=1: the client's residual fill writes a client-role block # and the backend reads a server-role one, so every BARRIER-PULLED read aborts with a # named Fatal{UnmigratedPipeInput, "@"}. THE RED IS THE CENSUS - it is the @@ -2282,6 +2282,12 @@ jobs: path: | build-split/MobileGL/MG_IntegrationTest/*.log* build-split/MobileGL/MG_IntegrationTest/split-logs/*.log + ${{ runner.temp }}/specialized*.json + ${{ runner.temp }}/specialized-tests.txt + ${{ runner.temp }}/specialized.xml + ${{ runner.temp }}/dualblock-lane.json + ${{ runner.temp }}/dualblock.xml + ${{ runner.temp }}/dualblock-split.txt if-no-files-found: warn # MobileGL/MG_Remote/Protocol/generated/protocol_generated.h is COMMITTED, and diff --git a/MobileGL/MG_IntegrationTest/CMakeLists.txt b/MobileGL/MG_IntegrationTest/CMakeLists.txt index 8ce8a2df..5c20995c 100644 --- a/MobileGL/MG_IntegrationTest/CMakeLists.txt +++ b/MobileGL/MG_IntegrationTest/CMakeLists.txt @@ -2407,6 +2407,34 @@ if (MOBILEGL_BUILD_DISAGGREGATED) # # NO TcpDevice ARM ON MAGMA: the device endpoint is one phone, run serially, and doubling # its matrix before the host lane is even green buys nothing. Stated rather than implied. + # Generic transport entries exercise ordinary behavior. Configuration-specific + # assertions run only in their pinned entries below, where CI requires a pass. + function(mgl_itest_generic_split_filter input backend output) + set(excluded + LargeArenaAdoptionScenario.AnAdoptionCostsExactlyOneMapPersistentRoundtrip + LargeArenaAdoptionScenario.AStreamSessionRefusesTheAdoptTierOnceByName + LargeArenaAdoptionScenario.ASessionThatCannotRunT0FallsBackToT2OnceByName + FencePollScenario.PacedFrameFenceWaitsBench + GenerateMipmapServerScenario.Rgb16fDrawnBaseWithARefusedReadbackDeclinesByName + GenerateMipmapServerScenario.Rgb16fUploadedBaseWithARefusedReadbackIsFilteredFromTheStore + ViewportArrayScenario.WithoutTheEmulationEveryIndexCollapsesOntoViewportZero + UnlocatedIoBlockScenario.TheEmulationIsActuallyArmedWhenTheEnvironmentPinsItOn + PointSizeDemotionScenario.TheDemotionIsActuallyArmedWhenTheEnvironmentPinsItOn + WireMipShapeScenario.Depth1DChainWithoutANativeBlitDeclinesByName) + if(backend STREQUAL "DirectGLES") + list(APPEND excluded + PointSizeDemotionScenario.ACaptureSurvivesAStageThatOnlyReadsThePointSize + WireIndirectDrawScenario.ShaderWrittenCommandsWithoutAnApplicationBarrierAreOrdered + WireIndirectDispatchScenario.ShaderWrittenGroupCountsWithoutAnApplicationBarrierAreOrdered) + endif() + list(JOIN excluded ":" excluded) + if(input MATCHES "-") + set(${output} "${input}:${excluded}" PARENT_SCOPE) + else() + set(${output} "${input}-${excluded}" PARENT_SCOPE) + endif() + endfunction() + macro(mgl_itest_register_split_arms_for_backend mglItestBackend scenarioFilter prefixTail) # QUOTED DEREFERENCE, NOT A BARE NAME: a macro parameter is a textual substitution and # not a variable, so `if(mglItestBackend STREQUAL ...)` would compare the literal string @@ -2448,10 +2476,11 @@ if (MOBILEGL_BUILD_DISAGGREGATED) # # IT MATTERS MORE ON THE SPAWN ARM: there TWO PROCESSES append to the path, so two # entries sharing one file is not merely ambiguous, it interleaves two sessions. + mgl_itest_generic_split_filter("${scenarioFilter}" "${mglItestBackend}" mglItestGenericFilter) gtest_discover_tests(MobileGLIntegrationTest TEST_PREFIX "${mglItestBackend}.${mglItestArm}.${prefixTail}" TEST_LIST "MGL_SPLIT_ARM_TESTS_${MGL_SPLIT_ARM_INDEX}" - TEST_FILTER "${scenarioFilter}" + TEST_FILTER "${mglItestGenericFilter}" DISCOVERY_TIMEOUT 30 PROPERTIES LABELS "${mglItestArmLabels}" @@ -2486,7 +2515,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED) mgl_itest_register_split_arms("PersistentCoherentMapScenario.*" "") # P11 A2 (MG_Remote/CONTRACT-P11.md §2): the >= 16 MiB arena on all three arms. Its tier case # is declared emulated per entry by SplitLogPaths (T2: every acquisition declines); the mpr and - # adopt-tier-refusal cases skip here for want of their lane markers. The Magma arms are the + # adopt-tier assertions are registered only in their configured controls. The Magma arms are the # gated `Arena.` block beside `Pbo.` / `Fence.` below, plus the Buffers lane. mgl_itest_register_split_arms("LargeArenaAdoptionScenario.*" "") # P11 B2 addendum: A BUFFER DELETED WHILE BOUND TO AN INDEXED POINT (storage, uniform, atomic @@ -2529,7 +2558,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED) gtest_discover_tests(MobileGLIntegrationTest TEST_PREFIX "DirectGLES.Tcp.AdoptTier0." TEST_LIST "MGL_SPLIT_ARM_TESTS_${MGL_SPLIT_ARM_INDEX}" - TEST_FILTER "LargeArenaAdoptionScenario.*" + TEST_FILTER "LargeArenaAdoptionScenario.*-LargeArenaAdoptionScenario.AnAdoptionCostsExactlyOneMapPersistentRoundtrip:LargeArenaAdoptionScenario.ASessionThatCannotRunT0FallsBackToT2OnceByName" DISCOVERY_TIMEOUT 30 PROPERTIES LABELS "integration-gpu\;integration-tcp" @@ -2552,7 +2581,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED) gtest_discover_tests(MobileGLIntegrationTest TEST_PREFIX "DirectGLES.${mglT0Arm}.AdoptTier0." TEST_LIST "MGL_SPLIT_ARM_TESTS_${MGL_SPLIT_ARM_INDEX}" - TEST_FILTER "LargeArenaAdoptionScenario.*" + TEST_FILTER "LargeArenaAdoptionScenario.*-LargeArenaAdoptionScenario.AnAdoptionCostsExactlyOneMapPersistentRoundtrip:LargeArenaAdoptionScenario.AStreamSessionRefusesTheAdoptTierOnceByName" DISCOVERY_TIMEOUT 30 PROPERTIES LABELS "integration-gpu\;integration-${mglT0ArmLower}" @@ -4061,8 +4090,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED) # table's FenceSync / GetSyncStatus / ClientWaitSync), so the same three cases. foreach(fenceCase ZeroTimeoutPollLoopsEndInBoundedTime PollsOfASignaledFenceCrossNothing - AFenceThatFinishesWhileTheClientOnlyPollsIsReported - PacedFrameFenceWaitsBench) + AFenceThatFinishesWhileTheClientOnlyPollsIsReported) gtest_discover_tests(MobileGLIntegrationTest TEST_PREFIX "DirectVulkan.${mglItestArm}.Fence." TEST_FILTER "FencePollScenario.${fenceCase}" @@ -4118,15 +4146,16 @@ if (MOBILEGL_BUILD_DISAGGREGATED) # On tcp a stream refuses the tier by name at the client's handshake (A1, the stream case # reads the line); over shared segments (split, spawn) the knob ASKS T0, a host has no # AHardwareBuffer, and the session falls back to T2 by name (B2, the fallback case reads - # both lines). Each arm's marker makes the other arm's case skip, so the three arms carry - # the same names and spawn_lane_parity.py compares them like any other lane. + # both lines). Register only the assertion applicable to each transport; the + # parity check names these two transport-specific exceptions explicitly. if(mglItestArm STREQUAL "Tcp") set(mglItestAdoptTier0Env "MGITEST_ADOPT_TIER_REFUSAL=T0") + set(mglItestAdoptTier0Case AStreamSessionRefusesTheAdoptTierOnceByName) else() set(mglItestAdoptTier0Env "MGITEST_ADOPT_T0_FALLBACK=no AHardwareBuffer") + set(mglItestAdoptTier0Case ASessionThatCannotRunT0FallsBackToT2OnceByName) endif() - foreach(arenaCase IN LISTS MGL_ITEST_ARENA_CASES ITEMS AStreamSessionRefusesTheAdoptTierOnceByName - ASessionThatCannotRunT0FallsBackToT2OnceByName) + foreach(arenaCase IN LISTS MGL_ITEST_ARENA_CASES ITEMS ${mglItestAdoptTier0Case}) gtest_discover_tests(MobileGLIntegrationTest TEST_PREFIX "DirectVulkan.${mglItestArm}.AdoptTier0." TEST_FILTER "LargeArenaAdoptionScenario.${arenaCase}" diff --git a/MobileGL/MG_IntegrationTest/Harness/split_log_paths.py b/MobileGL/MG_IntegrationTest/Harness/split_log_paths.py index 628f4032..1302ba06 100644 --- a/MobileGL/MG_IntegrationTest/Harness/split_log_paths.py +++ b/MobileGL/MG_IntegrationTest/Harness/split_log_paths.py @@ -181,6 +181,81 @@ DUALBLOCK_ALLOWED_SKIPS = { } +# Capability gaps are not successful coverage. Keep them distinct from known +# implementation limitations, and print both categories in the strict report. +DUALBLOCK_CAPABILITY_SKIPS = { + "DirectGLES.Split.IterationRPFirstReductionScenario.SampledRgba32fFirstAverage": + "subgroup probe requires GL_KHR_shader_subgroup", + "DirectGLES.Split.IterationRPFirstReductionScenario.IndexedInputTopologyAndReduction": + "subgroup probe requires GL_KHR_shader_subgroup", + "DirectGLES.Split.IterationRPProgram203Scenario.FixedCompleteInputProducesFixedCompleteGoldenOutput": + "requires 512-invocation basic+arithmetic compute subgroups", + "DirectGLES.Split.IterationRPScratchFixScenario.FixtureShapedReductionSumsEveryInvocation": + "needs GL_KHR_shader_subgroup basic+arithmetic in compute", + "DirectGLES.Split.IterationRPScratchFixScenario.WideFixtureShapedReductionSumsEveryInvocation": + "needs GL_KHR_shader_subgroup basic+arithmetic in compute", +} + +# Outstanding behavior, not a claim that another backend covers these semantics. +# See strict-known-limitations.md for the missing assertions and their scope. +DUALBLOCK_KNOWN_LIMITATIONS = { + "DirectGLES.Split.ClipDistanceScenario.ADisabledClipDistanceRemovesNothing": + "clips by a DISABLED gl_ClipDistance", + "DirectGLES.Split.ClipDistanceScenario.TheEnablesAreIndependentPerDistance": + "clips by every declared gl_ClipDistance regardless of the enables", + "DirectVulkan.Split.GpuContent.PartialUploadOverGpuContentScenario.OneDimensionalArraySubImageKeepsTheGpuClearedLayer": + "a 1D array layer does not attach on backend DirectVulkan", + "DirectGLES.Split.SsboDeclarationFormScenario.PackedBlockWithAnUnsizedArrayBeforeAnotherMember": + "known: a non-trailing unsized array overlaps the member after it", + "DirectGLES.Split.SsboDeclarationFormScenario.TwoUnsizedArraysInOneBlock": + "known: two runtime arrays in one block overlap", +} +# The older Magma ClipDistance exceptions describe the same unresolved behavior. +for _case in ("ADisabledClipDistanceRemovesNothing", "TheEnablesAreIndependentPerDistance"): + _name = "DirectVulkan.Split.Fm.ClipDistanceScenario." + _case + DUALBLOCK_KNOWN_LIMITATIONS[_name] = DUALBLOCK_ALLOWED_SKIPS.pop(_name) + + +def specialized_required(): + """Exact configured entries replacing inapplicable generic copies; no skip allowed.""" + names = { + "DirectGLES.MapPersistentRoundtrips.LargeArenaAdoptionScenario.AnAdoptionCostsExactlyOneMapPersistentRoundtrip", + "DirectGLES.Spawn.AdoptT0Disallowed.LargeArenaAdoptionScenario.ASessionThatCannotRunT0FallsBackToT2OnceByName", + } + for backend in ("DirectGLES", "DirectVulkan"): + names.add(f"{backend}.Tcp.AdoptTier0.LargeArenaAdoptionScenario.AStreamSessionRefusesTheAdoptTierOnceByName") + for arm in ("Split", "Spawn"): + names.add(f"{backend}.{arm}.AdoptTier0.LargeArenaAdoptionScenario.ASessionThatCannotRunT0FallsBackToT2OnceByName") + for arm in ("Split", "Spawn"): + for case in ("Rgb16fDrawnBaseWithARefusedReadbackDeclinesByName", + "Rgb16fUploadedBaseWithARefusedReadbackIsFilteredFromTheStore"): + names.add(f"DirectGLES.{arm}.CpuMipRefused.GenerateMipmapServerScenario.{case}") + names.add(f"DirectVulkan.{arm}.ShaderMipDepth1D.WireMipShapeScenario.Depth1DChainWithoutANativeBlitDeclinesByName") + for arm in ("", "Split.", "Spawn.", "Tcp."): + for tail, case in ( + ("NoViewportArrayEmulation", "ViewportArrayScenario.WithoutTheEmulationEveryIndexCollapsesOntoViewportZero"), + ("UnlocatedIoBlocks", "UnlocatedIoBlockScenario.TheEmulationIsActuallyArmedWhenTheEnvironmentPinsItOn"), + ("PointSizeDemotion", "PointSizeDemotionScenario.ACaptureSurvivesAStageThatOnlyReadsThePointSize"), + ("PointSizeDemotion", "PointSizeDemotionScenario.TheDemotionIsActuallyArmedWhenTheEnvironmentPinsItOn"), + ): + names.add(f"DirectGLES.{arm}{tail}.{case}") + for arm in ("Split", "Spawn", "Tcp"): + names.add(f"DirectVulkan.{arm}.Indirect.WireIndirectDrawScenario.ShaderWrittenCommandsWithoutAnApplicationBarrierAreOrdered") + names.add(f"DirectVulkan.{arm}.IndirectDispatch.WireIndirectDispatchScenario.ShaderWrittenGroupCountsWithoutAnApplicationBarrierAreOrdered") + for case in ("ACaptureSurvivesAStageThatOnlyReadsThePointSize", "TheDemotionIsActuallyArmedWhenTheEnvironmentPinsItOn"): + names.add(f"DirectVulkan.PointSizeDemotion.PointSizeDemotionScenario.{case}") + return names + + +def select_specialized(document): + required = specialized_required() + tests = [t for t in document.get("tests", []) if t["name"] in required] + names = [t["name"] for t in tests] + if set(names) != required or len(names) != len(required): + raise ValueError(f"specialized controls missing or duplicated: missing={sorted(required - set(names))}") + return {"tests": tests} + + def complete_results(document, junit_path, allowed_skips=None): expected = [test["name"] for test in document.get("tests", [])] if not expected or len(expected) != len(set(expected)): @@ -196,14 +271,17 @@ def complete_results(document, junit_path, allowed_skips=None): if missing or extra: raise ValueError(f"result accounting: missing {sorted(missing)}, unexpected {sorted(extra)}") allowed_skips = allowed_skips or {} + problems = [] for name, case in by_name.items(): if case.find("skipped") is not None: reason = allowed_skips.get(name) text = " ".join(" ".join(case.itertext()).split()) if reason is None or reason not in text: - raise ValueError(f"result accounting: unexpected skip/reason for {name}") + problems.append(f"unexpected skip/reason for {name}") elif case.get("status") in ("notrun", "disabled"): - raise ValueError(f"result accounting: {name} did not execute ({case.get('status')})") + problems.append(f"{name} did not execute ({case.get('status')})") + if problems: + raise ValueError("result accounting: " + " | ".join(problems)) return by_name @@ -342,7 +420,14 @@ def expect_fatal(document, junit_path, expected_path): empty expected file IS the P5f exit state, which this mode then verifies rather than obstructs.""" logs = marker_log_paths(document) - by_name = complete_results(document, junit_path, DUALBLOCK_ALLOWED_SKIPS) + by_name = complete_results(document, junit_path, { + **DUALBLOCK_ALLOWED_SKIPS, **DUALBLOCK_CAPABILITY_SKIPS, **DUALBLOCK_KNOWN_LIMITATIONS}) + for category, entries in (("capability gaps (not covered)", DUALBLOCK_CAPABILITY_SKIPS), + ("known limitations (unresolved)", DUALBLOCK_KNOWN_LIMITATIONS)): + skipped_entries = sorted(n for n in entries if n in by_name and by_name[n].find("skipped") is not None) + print(f"SplitLogPaths {category}: {len(skipped_entries)}") + for name in skipped_entries: + print(f" {name}: {entries[name]}") problems = [] fatal, admitted, escalated = {}, {}, {} scanned = skipped = green = red = 0 @@ -475,6 +560,29 @@ def markers(document, selector, allowed_path, expected_path, require_no_fatal): def main(): mode = sys.argv[1] + if mode == "select-specialized": + selected = select_specialized(json.loads(Path(sys.argv[2]).read_text())) + Path(sys.argv[3]).write_text(json.dumps(selected)) + Path(sys.argv[4]).write_text("\n".join(t["name"] for t in selected["tests"]) + "\n") + return + if mode == "specialized-results": + document = json.loads(Path(sys.argv[2]).read_text()) + # CTest adds fixture setup/cleanup entries to JUnit; require those to pass too. + required = specialized_required() + fixture_names = set() + by_name = {t["name"]: t for t in document.get("tests", [])} + for test in select_specialized(document)["tests"]: + for prop in test.get("properties", []): + if prop["name"] == "FIXTURES_REQUIRED": + fixture_names.update(prop["value"]) + selected = [] + for test in by_name.values(): + is_fixture = any(p["name"] in ("FIXTURES_SETUP", "FIXTURES_CLEANUP") and + fixture_names.intersection(p["value"]) for p in test.get("properties", [])) + if test["name"] in required or is_fixture: + selected.append(test) + require_green({"tests": selected}, sys.argv[3], [t["name"] for t in selected]) + return if mode == "check": data = subprocess.check_output([sys.argv[2], "--test-dir", sys.argv[3], "--show-only=json-v1"], text=True) diff --git a/MobileGL/MG_IntegrationTest/Harness/strict-known-limitations.md b/MobileGL/MG_IntegrationTest/Harness/strict-known-limitations.md new file mode 100644 index 00000000..60b39ebe --- /dev/null +++ b/MobileGL/MG_IntegrationTest/Harness/strict-known-limitations.md @@ -0,0 +1,36 @@ +# Strict lane coverage limitations + +A skipped assertion is not a passing assertion. The strict result checker matches +both the exact entry and its documented reason; unknown skips still fail CI. +Configuration-specific controls are selected separately and must pass without skips. + +## Unresolved behavior + +- `ClipDistanceScenario.ADisabledClipDistanceRemovesNothing` and + `TheEnablesAreIndependentPerDistance`: per-distance enable semantics are missing + on the tested GLES and Vulkan paths. Neither backend supplies a passing control + on local llvmpipe/lavapipe. The strict list names the GLES and Magma Fm entries. +- `PartialUploadOverGpuContentScenario.OneDimensionalArraySubImageKeepsTheGpuClearedLayer`: + the Vulkan path cannot attach the 1D array layer. GLES passes, which does not + establish Vulkan coverage. +- `SsboDeclarationFormScenario.PackedBlockWithAnUnsizedArrayBeforeAnotherMember` + and `TwoUnsizedArraysInOneBlock`: the tests unconditionally skip before compiling + or drawing because the layout is known to overlap. No executing assertion exists + in either backend; resolving the intended shader/layout semantics is separate work. + +The checker reports these as **known limitations (unresolved)**. Removing a skip +must not require adding a new exception: an executing, passing case is accepted. + +## Driver capability gaps + +The five GLES IterationRP subgroup probes need compute basic/arithmetic subgroups. +The tested llvmpipe GLES driver lacks these. The two FirstReduction probes also +require subgroup width 16..256; local lavapipe exposes 8, so those two have no local +execution coverage. Program203 and ScratchFix have passing Vulkan entries, which +is not proof of the GLES path. The checker reports **capability gaps (not covered)**. + +## Optional measurement + +`FencePollScenario.PacedFrameFenceWaitsBench` is not part of generic split correctness +registrations. It remains available in ambient/full-suite entries for an explicit +`MGITEST_FENCE_PACE_BENCH=1` measurement. Ordinary fence polling assertions remain gated. diff --git a/MobileGL/MG_IntegrationTest/Harness/test_split_log_paths.py b/MobileGL/MG_IntegrationTest/Harness/test_split_log_paths.py index 50737372..bb831239 100644 --- a/MobileGL/MG_IntegrationTest/Harness/test_split_log_paths.py +++ b/MobileGL/MG_IntegrationTest/Harness/test_split_log_paths.py @@ -123,6 +123,43 @@ class ResultAccountingTest(unittest.TestCase): with self.assertRaisesRegex(ValueError, "unexpected skip"): self.census() + def test_all_unexpected_skips_are_reported_together(self): + self.results([(A, "notrun", "skipped"), (B, "notrun", "skipped")]) + with self.assertRaises(ValueError) as error: + self.census() + self.assertIn(A, str(error.exception)) + self.assertIn(B, str(error.exception)) + + def test_capability_and_known_limitation_reasons_are_not_interchangeable(self): + for table in (helper.DUALBLOCK_CAPABILITY_SKIPS, helper.DUALBLOCK_KNOWN_LIMITATIONS): + name, reason = next(iter(table.items())) + for actual, accepted in ((reason, True), ("preflight failed: no EGL display", False)): + with self.subTest(name=name, accepted=accepted): + self.document = self.discovery([name]) + self.results([(name, "notrun", "skipped")]) + tree = ET.parse(self.xml) + ET.SubElement(tree.getroot().find("testcase"), "system-out").text = actual + tree.write(self.xml) + if accepted: + self.census() + else: + with self.assertRaisesRegex(ValueError, "unexpected skip"): + self.census() + + def test_specialized_control_missing_or_skipped_is_rejected(self): + names = sorted(helper.specialized_required()) + document = self.discovery(names) + helper.select_specialized(document) + with self.assertRaisesRegex(ValueError, "missing or duplicated"): + helper.select_specialized({"tests": document["tests"][1:]}) + self.results([(n, "run", None) for n in names]) + with contextlib.redirect_stdout(io.StringIO()): + helper.require_green(document, self.xml, names) + self.results([(n, "notrun", "skipped") if i == 0 else (n, "run", None) + for i, n in enumerate(names)]) + with self.assertRaisesRegex(ValueError, "unexpected skip"): + helper.require_green(document, self.xml, names) + def test_admitted_or_fatal_on_passed_case_is_not_hidden(self): for marker in ['Admitted{UnmigratedPipeInput, "GetProgramObject@Clear"}', 'Fatal{UnmigratedPipeInput, "GetProgramObject@Clear"}']: diff --git a/scripts/ci/spawn_lane_parity.py b/scripts/ci/spawn_lane_parity.py index e1f4dc4e..57e96738 100644 --- a/scripts/ci/spawn_lane_parity.py +++ b/scripts/ci/spawn_lane_parity.py @@ -132,7 +132,7 @@ PRIMGEN_REROUTE_CASES = ("CountsADrawMadeWithNoCaptureSpan", "APausedSpanCountsATessellatedPatchExactlyOnce", "APausedSpanCountsAnInstancedDrawExactlyOnce", "TheRerouteIsActuallyArmedWhenTheEnvironmentPinsItOn") -MAGMA_SERVER_ENV_KNOB_NO_TCP = (".ShaderMip1.", ".ShaderMip2.", ".DepthMip.", +MAGMA_SERVER_ENV_KNOB_NO_TCP = (".AdoptTier0.LargeArenaAdoptionScenario.ASessionThatCannotRunT0FallsBackToT2OnceByName", ".ShaderMip1.", ".ShaderMip2.", ".DepthMip.", ".DefaultBlitShape1.", ".MsResolve1.", ".StaleSerial.", ".MsFlip1.", ".MsResolveBug.", ".MsFlipBug.", ".MsResolveElide.", ".ImageUnitPrivate.InvalidImageUnitScenario.AStoreThroughAnInvalidUnitIsLoadedThroughNeitherAnotherInvalidUnitNorItself", @@ -150,12 +150,10 @@ MAGMA_SERVER_ENV_KNOB_NO_TCP = (".ShaderMip1.", ".ShaderMip2.", ".DepthMip.", ".ShaderMipDepth1D.",) -# THE MIRROR SHAPE - entries that exist on tcp ALONE, by construction. Each tail must match at least -# one tcp entry, or the exception names nothing. P11 A1 put `.AdoptTier0.` here (over shared segments -# MOBILEGL_IPC_ADOPT_TIER=0 died at the handshake); P11 B2 made that knob an ASK that falls back to T2 -# by name, so the Magma `.AdoptTier0.` lane now runs on all three arms with one case set and is -# compared like any other. The mechanism stays for the next tcp-only lane. -MAGMA_TCP_ONLY = () +# The stream-refusal assertion is TCP-only. The shared-segment fallback assertion +# is required on split/spawn and listed in no_tcp above. Neither transport should +# register the other assertion merely to skip it. +MAGMA_TCP_ONLY = (".AdoptTier0.LargeArenaAdoptionScenario.AStreamSessionRefusesTheAdoptTierOnceByName",) def lane_names(build_dir, label): diff --git a/scripts/data/split_coverage_exemptions.txt b/scripts/data/split_coverage_exemptions.txt index 97df063a..69b23a46 100644 --- a/scripts/data/split_coverage_exemptions.txt +++ b/scripts/data/split_coverage_exemptions.txt @@ -63,3 +63,10 @@ ProgramPipelineScenario.AReservedNameTakesStateBeforeItIsAProgramPipeline no-rec # ---- pending-fix ------------------------------------------------------------------------------- # (none: P8-SE fixed the default-framebuffer depth/stencil case and registered it, notes/p8/SE.md) + +# These ambient copies cannot run their assertion; pinned controls are checked separately. +LargeArenaAdoptionScenario.AnAdoptionCostsExactlyOneMapPersistentRoundtrip mechanism synchronous monolith accounting; required DirectGLES.MapPersistentRoundtrips control +DirectGLES:FencePollScenario.PacedFrameFenceWaitsBench mechanism optional measurement requiring MGITEST_FENCE_PACE_BENCH=1, not a correctness gate +DirectGLES:WireIndirectDrawScenario.ShaderWrittenCommandsWithoutAnApplicationBarrierAreOrdered single-backend Vulkan implicit indirect-command barrier +DirectGLES:WireIndirectDispatchScenario.ShaderWrittenGroupCountsWithoutAnApplicationBarrierAreOrdered single-backend Vulkan implicit indirect-command barrier +DirectGLES:WireMipShapeScenario.Depth1DChainWithoutANativeBlitDeclinesByName single-backend Vulkan forced shader-mip decline; DirectGLES generates the chain