mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (MG_IntegrationTest): validate specialized controls and classify strict skips
This commit is contained in:
+27
-21
@@ -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, "<field>@<verb>"}. 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
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
@@ -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"}']:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user