From 5f8e8db1b96d1663bfe67155efb5a0c0c370b6dc Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 6 Sep 2026 01:59:39 -0400 Subject: [PATCH] [Test] (Retrace): make a retrace under MOBILEGL_PIPE_VERIFY prove it armed, and give the lane a label - A retrace that exports MOBILEGL_PIPE_VERIFY=1 at a library configured without -DMOBILEGL_PIPE_VERIFY=ON is a no-op: the frames still match their goldens and the case reports green having compared nothing. run_trace_case.cmake now demands the evidence whenever the variable is set to anything but 0/false - mobilegl.log must exist, must carry "MGPipe: verify armed", and must carry neither Fatal{PipeVerifyDiffer nor Fatal{UnmigratedPipeInput. With the variable unset the script is byte-for-byte the old one. - The Fatal scan is not redundant with the replay's exit status: MOBILEGL_PIPE_VERIFY_FATAL=0 is the supported triage configuration, and there a divergence is logged and counted rather than aborted, so the run would finish 0 with its own report sitting unread in the log. - LABELS retrace on both registrations: the lane was selectable only by regex, so `ctest -L retrace --no-tests=error` - the spelling that reds a lane which registered nothing - could not be written at all. - "verify": true on eight cases (the five 180s cases plus minecraft-1.21.4-in-world, minecraft-1.21.4-fabric-sodium-in-world and improved-transparency-minecraft-26.3), and --format github-verify-matrix over that subset: 16 entries, against the full lane's 77. The verify build compares at every verb boundary and again at every accessor read, which the design budgets at 5-10x, so the per-push job runs the subset and the full sweep is a phase-exit / workflow_dispatch run. - The flag is validated in the manifest loader, not at the matrix, so a non-boolean or a verify case excluded from CI is a loud error in every consumer instead of a subset that is quietly one case short. --- tools/trace_replay/CMakeLists.txt | 6 ++- tools/trace_replay/run_trace_case.cmake | 57 +++++++++++++++++++++++++ tools/trace_replay/trace_cases.json | 8 ++++ tools/trace_replay/trace_cases.py | 27 ++++++++++++ 4 files changed, 96 insertions(+), 2 deletions(-) diff --git a/tools/trace_replay/CMakeLists.txt b/tools/trace_replay/CMakeLists.txt index c52ca374..ae1ed03b 100644 --- a/tools/trace_replay/CMakeLists.txt +++ b/tools/trace_replay/CMakeLists.txt @@ -372,10 +372,12 @@ function(add_trace_replay_test CASE_NAME BACKEND) -P ${MOBILEGL_TRACE_ROOT}/run_trace_case.cmake) if(BACKEND STREQUAL "DirectGLES") set_tests_properties(MobileGLTraceReplay.${CASE_NAME}.${BACKEND} PROPERTIES - ENVIRONMENT "EGL_PLATFORM=surfaceless;LIBGL_ALWAYS_SOFTWARE=1;MESA_GL_VERSION_OVERRIDE=3.3;MESA_GLSL_VERSION_OVERRIDE=330") + ENVIRONMENT "EGL_PLATFORM=surfaceless;LIBGL_ALWAYS_SOFTWARE=1;MESA_GL_VERSION_OVERRIDE=3.3;MESA_GLSL_VERSION_OVERRIDE=330" + LABELS retrace) else() set_tests_properties(MobileGLTraceReplay.${CASE_NAME}.${BACKEND} PROPERTIES - ENVIRONMENT "LIBGL_ALWAYS_SOFTWARE=1;MESA_GL_VERSION_OVERRIDE=3.3;MESA_GLSL_VERSION_OVERRIDE=330") + ENVIRONMENT "LIBGL_ALWAYS_SOFTWARE=1;MESA_GL_VERSION_OVERRIDE=3.3;MESA_GLSL_VERSION_OVERRIDE=330" + LABELS retrace) endif() endfunction() diff --git a/tools/trace_replay/run_trace_case.cmake b/tools/trace_replay/run_trace_case.cmake index ce389b47..1d76d704 100644 --- a/tools/trace_replay/run_trace_case.cmake +++ b/tools/trace_replay/run_trace_case.cmake @@ -127,3 +127,60 @@ endif() if(NOT replay_result EQUAL 0) message(FATAL_ERROR "${TRACE_CASE_NAME} ${TRACE_BACKEND} trace replay failed with status ${replay_result}") endif() + +# --- MOBILEGL_PIPE_VERIFY: the third CI mode's own assertions (gates G3 and G8) -------------- +# +# A retrace that exported MOBILEGL_PIPE_VERIFY=1 at a library which was never configured with +# -DMOBILEGL_PIPE_VERIFY=ON is a no-op that looks exactly like a clean pass: the variable steers +# nothing, the frames still match their goldens, and the case reports green having verified +# nothing at all. The mode therefore has to prove it ran, and the only channel a `cmake -P` script +# has for that is the library's own log. +# +# Three demands, all of them silent when MOBILEGL_PIPE_VERIFY is unset or "0", so an ordinary +# retrace is untouched: +# * mobilegl.log exists - the replay wrote one, so the library was loaded and logging; +# * it carries "MGPipe: verify armed" - the comparator armed in THIS process; +# * it carries neither Fatal{PipeVerifyDiffer (a push/pull divergence, the thing the mode +# exists to find) nor Fatal{UnmigratedPipeInput (a backend read of a field the verb's fill +# table does not list - fixed by adding the row to MG_Pipe/FillPoints.def, never by marking +# the field sticky). +# The Fatal check is not redundant with the replay's exit status: MOBILEGL_PIPE_VERIFY_FATAL=0 is +# the supported triage configuration, and there the divergence is logged and counted rather than +# aborted, so the run would otherwise finish 0 with its own report in the log. +if(DEFINED ENV{MOBILEGL_PIPE_VERIFY} AND NOT "$ENV{MOBILEGL_PIPE_VERIFY}" STREQUAL "") + set(pipe_verify_case "${TRACE_CASE_NAME} ${TRACE_BACKEND}") + if("$ENV{MOBILEGL_PIPE_VERIFY}" STREQUAL "0" OR "$ENV{MOBILEGL_PIPE_VERIFY}" STREQUAL "false") + message(STATUS "MGPipe verify: MOBILEGL_PIPE_VERIFY=$ENV{MOBILEGL_PIPE_VERIFY}, no verify assertions for ${pipe_verify_case}") + elseif(NOT EXISTS "${mobilegl_log}") + message(FATAL_ERROR + "MOBILEGL_PIPE_VERIFY is set for ${pipe_verify_case} but the run wrote no ${mobilegl_log}, " + "so there is no evidence the comparator ever armed. A verify retrace with no library log " + "cannot be counted as a verify retrace.") + else() + file(READ "${mobilegl_log}" pipe_verify_log) + string(FIND "${pipe_verify_log}" "MGPipe: verify armed" pipe_verify_armed_at) + if(pipe_verify_armed_at EQUAL -1) + message(FATAL_ERROR + "MOBILEGL_PIPE_VERIFY is set for ${pipe_verify_case} and the library never reported " + "\"MGPipe: verify armed\". Either this libMobileGL.so was not built with " + "-DMOBILEGL_PIPE_VERIFY=ON - in which case the whole verify retrace lane is comparing " + "nothing - or the runtime knob never reached the process. Check that the VERIFY " + "runtime artifact is the one unpacked at ${MOBILEGL_LIBRARY}.") + endif() + file(STRINGS "${mobilegl_log}" pipe_verify_fatals + REGEX "Fatal\\{(PipeVerifyDiffer|UnmigratedPipeInput)") + if(pipe_verify_fatals) + foreach(line IN LISTS pipe_verify_fatals) + message(STATUS "${line}") + endforeach() + list(LENGTH pipe_verify_fatals pipe_verify_fatal_count) + message(FATAL_ERROR + "${pipe_verify_case}: ${pipe_verify_fatal_count} MGPipe Fatal(s) under " + "MOBILEGL_PIPE_VERIFY. Fatal{PipeVerifyDiffer, \"@\"} is a real push/pull " + "divergence and is recorded, not silenced; Fatal{UnmigratedPipeInput, \"@\"} " + "is a missing row in MG_Pipe/FillPoints.def's class table - add it, regenerate, rerun " + "(never mark the field sticky).") + endif() + message(STATUS "MGPipe verify: ${pipe_verify_case} armed, zero divergences, zero unmigrated reads") + endif() +endif() diff --git a/tools/trace_replay/trace_cases.json b/tools/trace_replay/trace_cases.json index 914b9f98..03ef2c0d 100644 --- a/tools/trace_replay/trace_cases.json +++ b/tools/trace_replay/trace_cases.json @@ -13,6 +13,7 @@ "cases": [ { "name": "OpenRA", + "verify": true, "trace_archive": "openra.tgz", "trace_file": "openra.trace", "golden": "openra.0000031249.png", @@ -27,6 +28,7 @@ }, { "name": "minecraft-1.21.4-startup", + "verify": true, "trace_archive": "minecraft-1.21.4-startup.tgz", "golden": "minecraft-1.21.4-startup.0000092195.png", "target_call": 92195, @@ -34,6 +36,7 @@ }, { "name": "minecraft-1.21.4-main-menu", + "verify": true, "trace_archive": "minecraft-1.21.4-main-menu.tgz", "golden": "minecraft-1.21.4-main-menu.0000481787.png", "alternate_golden": "minecraft-1.21.4-main-menu.0000481787-mali.png", @@ -42,6 +45,7 @@ }, { "name": "minecraft-1.21.11-main-menu", + "verify": true, "trace_archive": "minecraft-1.21.11-main-menu.tgz", "golden": "minecraft-1.21.11-main-menu.0000205347.png", "target_call": 205347, @@ -49,6 +53,7 @@ }, { "name": "minecraft-1.17-main-menu-854", + "verify": true, "trace_archive": "minecraft-1.17-main-menu-854.tgz", "golden": "minecraft-1.17-main-menu-854.0000117757.png", "target_call": 117757, @@ -56,6 +61,7 @@ }, { "name": "minecraft-1.21.4-in-world", + "verify": true, "trace_archive": "minecraft-1.21.4-in-world.tgz", "golden": "minecraft-1.21.4-in-world.0000280000.png", "target_call": 280000 @@ -70,6 +76,7 @@ }, { "name": "minecraft-1.21.4-fabric-sodium-in-world", + "verify": true, "trace_archive": "minecraft-1.21.4-fabric-sodium-in-world.tgz", "golden": "minecraft-1.21.4-fabric-sodium-in-world.0000923340.png", "target_call": 923340, @@ -277,6 +284,7 @@ }, { "name": "improved-transparency-minecraft-26.3", + "verify": true, "trace_archive": "improved-transparency-minecraft-26.3.tgz", "golden": "improved-transparency-minecraft-26.3.0002667619.png", "target_call": 2667619, diff --git a/tools/trace_replay/trace_cases.py b/tools/trace_replay/trace_cases.py index 3cdf0ac1..8203eac5 100644 --- a/tools/trace_replay/trace_cases.py +++ b/tools/trace_replay/trace_cases.py @@ -26,6 +26,16 @@ def load_trace_case_manifest(path=TRACE_CASES_JSON): for key in ("trace_archive", "trace_file", "golden", "target_call", "width", "height"): if key not in merged: raise ValueError(f"{key} is required for {name}") + # "verify" opts a case into the MOBILEGL_PIPE_VERIFY retrace subset. It is checked here + # rather than where the matrix is built so that a typo is a loud manifest error in every + # consumer (the cmake emitter included) instead of a subset that is quietly one case short. + verify = merged.get("verify", False) + if not isinstance(verify, bool): + raise ValueError(f"verify must be true or false for {name}") + if verify and not merged.get("ci", True): + raise ValueError( + f"{name} is marked verify but excluded from CI, so the verify matrix would drop it" + ) cases.append(merged) return {"defaults": defaults, "cases": cases} @@ -72,6 +82,16 @@ def ci_trace_cases(cases): return [case for case in cases if case.get("ci", True)] +def verify_trace_cases(cases): + """The subset the third CI mode retraces. + + The verify build compares two state models at every verb boundary and again at every accessor + read, which the design budgets at 5-10x, so the per-push lane runs a named subset and the full + 79-case sweep happens at the phase exit and on workflow_dispatch. + """ + return [case for case in cases if case.get("verify", False)] + + def ci_backends(case): backends = case.get("ci_backends") if backends is None: @@ -98,6 +118,10 @@ def github_test_matrix(cases): } +def github_verify_matrix(cases): + return github_test_matrix(verify_trace_cases(cases)) + + def github_apk_matrix(cases): backends = { "DirectGLES": {"name": "DirectGLES", "gpu": "software"}, @@ -158,6 +182,7 @@ def parse_args(): choices=( "names", "github-test-matrix", + "github-verify-matrix", "github-apk", "github-apk-matrix", "fixture-files", @@ -177,6 +202,8 @@ def main(): print(json.dumps([case["name"] for case in cases], separators=(",", ":"))) elif args.format == "github-test-matrix": print(json.dumps(github_test_matrix(cases), separators=(",", ":"))) + elif args.format == "github-verify-matrix": + print(json.dumps(github_verify_matrix(cases), separators=(",", ":"))) elif args.format == "github-apk": print(json.dumps([github_apk_case(case) for case in cases], separators=(",", ":"))) elif args.format == "github-apk-matrix":