mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[CI] (.github/workflows, docs/ci, docs/Disaggregated, MG_IntegrationTest, MG_IntegrationTest/Harness, scripts/ci, scripts/data): retire the split barrier and map-push negative controls
- skip census: lane junit-integration-split/control retired with its step
This commit is contained in:
@@ -1,143 +0,0 @@
|
||||
#!/bin/bash
|
||||
# MobileGL - scripts/ci/control_smoke_test.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# A smoke test for the CI negative controls themselves.
|
||||
#
|
||||
# A negative control is a gate, and a workflow `run:` block only executes on a runner, so this
|
||||
# file runs the real control script - the same file .github/workflows/test.yml invokes, not a copy
|
||||
# of it - against a stubbed ctest, and checks that it passes exactly when it should.
|
||||
#
|
||||
# The case that matters is the first one. A stubbed ctest reports a non-empty selection and then
|
||||
# fails with UNRELATED_CONTROL_FAILURE: a reason that has nothing to do with the knob the control
|
||||
# turns. The control must report FAILED on it, and PASSED only on its scenarios' own diagnostic.
|
||||
#
|
||||
# usage: control_smoke_test.sh
|
||||
set -u
|
||||
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
WORK="$(mktemp -d)" || exit 1
|
||||
trap 'rm -rf "${WORK}"' EXIT
|
||||
|
||||
STUB_DIR="${WORK}/stub"
|
||||
mkdir -p "${STUB_DIR}"
|
||||
cp "${HERE}/testdata/stub_ctest.sh" "${STUB_DIR}/ctest"
|
||||
chmod +x "${STUB_DIR}/ctest"
|
||||
|
||||
passes=0
|
||||
failures=0
|
||||
|
||||
# expect <expected: PASSED|FAILED> <label> -- <command...>
|
||||
expect() {
|
||||
want="$1"; label="$2"; shift 3 # shift past the literal "--"
|
||||
outfile="${WORK}/run.out"
|
||||
"$@" > "${outfile}" 2>&1
|
||||
rc=$?
|
||||
if [ "${rc}" -eq 0 ]; then got="PASSED"; else got="FAILED"; fi
|
||||
if [ "${got}" = "${want}" ]; then
|
||||
passes=$((passes + 1))
|
||||
printf 'ok %-58s %s (rc=%d)\n' "${label}" "${got}" "${rc}"
|
||||
else
|
||||
failures=$((failures + 1))
|
||||
printf 'NOT OK %-58s expected %s, got %s (rc=%d)\n' "${label}" "${want}" "${got}" "${rc}"
|
||||
sed 's/^/ | /' "${outfile}"
|
||||
fi
|
||||
}
|
||||
|
||||
# The lane step's own baseline, as the workflow leaves it: the stub's JUnit for the whole label and
|
||||
# its exit code beside it, in <base>.xml and <base>.rc.
|
||||
lane_baseline() { # $1 = STUB_MODE, $2 = base path
|
||||
env -i PATH="${STUB_DIR}:/usr/bin:/bin" STUB_MODE="$1" CONTROL_TMPDIR="${WORK}/tmp-$1" \
|
||||
ctest -L integration-split --no-tests=error --output-junit "$2.xml" > /dev/null 2>&1
|
||||
echo "$?" > "$2.rc"
|
||||
}
|
||||
|
||||
# run_split <STUB_MODE> [baseline path]: the controls, tallying the lane step's baseline. A path
|
||||
# with no files behind it is the job whose lane step never ran.
|
||||
run_split() {
|
||||
local base="${2:-${WORK}/base-$1}"
|
||||
[ -n "${2:-}" ] || lane_baseline "$1" "${base}"
|
||||
env -i PATH="${STUB_DIR}:/usr/bin:/bin" STUB_MODE="$1" \
|
||||
CTEST=ctest CONTROL_TMPDIR="${WORK}/tmp-$1" SPLIT_BASELINE_JUNIT="${base}.xml" \
|
||||
bash "${HERE}/split_negative_controls.sh"
|
||||
}
|
||||
|
||||
# run_split_saying <sentence> <args for run_split...>: run_split, and its output must carry the
|
||||
# sentence that says which baseline it used.
|
||||
run_split_saying() {
|
||||
local sentence="$1"
|
||||
shift
|
||||
local out
|
||||
out=$(run_split "$@" 2>&1)
|
||||
local rc=$?
|
||||
printf '%s\n' "${out}"
|
||||
printf '%s' "${out}" | grep -qF "${sentence}" || { echo "missing: ${sentence}"; return 1; }
|
||||
return "${rc}"
|
||||
}
|
||||
|
||||
# The lane step's JUnit says green but its exit code says red: ctest's "Not Run" is a skip in
|
||||
# JUnit, so the code is part of the baseline and must be honoured.
|
||||
run_split_red_code() {
|
||||
lane_baseline evidence "${WORK}/redcode"
|
||||
echo 8 > "${WORK}/redcode.rc"
|
||||
run_split evidence "${WORK}/redcode"
|
||||
}
|
||||
|
||||
# The lane step never ran: the controls rerun the baseline, and the rerun's JUnit stays out of
|
||||
# CONTROL_TMPDIR/*.xml, which the job uploads for the skip census.
|
||||
run_split_rerun() {
|
||||
rm -f "${WORK}/tmp-evidence/isplit.xml"
|
||||
run_split_saying "reran the DirectGLES.Split. baseline" evidence "${WORK}/absent" || return 1
|
||||
if [ -e "${WORK}/tmp-evidence/isplit.xml" ]; then
|
||||
echo "the rerun's JUnit landed in CONTROL_TMPDIR/*.xml"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# The lane step ran nothing of the DirectGLES.Split. arm (only other entries of the label).
|
||||
run_split_no_arm() {
|
||||
printf '%s\n' '<testsuite><testcase name="SplitLogPaths.PrivateAndDistinct" status="run"/></testsuite>' \
|
||||
> "${WORK}/noarm.xml"
|
||||
echo 0 > "${WORK}/noarm.rc"
|
||||
run_split evidence "${WORK}/noarm"
|
||||
}
|
||||
|
||||
echo "=== the split lane's E1 / E3(a) controls (scripts/ci/split_negative_controls.sh)"
|
||||
# THE FINDING, REPRODUCED. Non-empty selection, green baseline, and a red that is not the knob's.
|
||||
expect FAILED "unrelated failure with a non-empty selection" -- run_split unrelated
|
||||
# ... and the same control on the same stub, failing for its own reason: it must PASS.
|
||||
expect PASSED "the scenarios' own diagnostic" -- run_split evidence
|
||||
# The pre-existing half of the control, which was never broken: a knob that reds nothing.
|
||||
expect FAILED "the knob leaves the selection green" -- run_split green
|
||||
# The arming counter's half of the finding: a baseline that is already red cannot arm anything.
|
||||
expect FAILED "the baseline is already red" -- run_split red-baseline
|
||||
# P5 is complete: losing the runtime implementation must no longer disarm the gate.
|
||||
expect FAILED "every split entry skipped (implementation lost)" -- run_split all-skipped
|
||||
expect FAILED "E1 missing boundary probes" -- run_split e1-empty
|
||||
expect FAILED "E1 own baseline is already red" -- run_split e1-red-baseline
|
||||
expect FAILED "E1 restored boundary remains red" -- run_split e1-restore-red
|
||||
expect FAILED "E1 record never reaches the peer" -- run_split e1-no-peer
|
||||
expect FAILED "E1 observed no real emission" -- run_split e1-no-emit
|
||||
expect FAILED "E1 incorrectly waits every record" -- run_split e1-waitall
|
||||
# The baseline is the lane step's own run; the controls rerun it only when that step never ran.
|
||||
expect PASSED "the lane step's baseline is tallied, not rerun" -- run_split_saying "no rerun" evidence
|
||||
expect PASSED "no lane JUnit: the controls rerun the baseline" -- run_split_rerun
|
||||
expect FAILED "the lane step's exit code is red, its JUnit green" -- run_split_red_code
|
||||
expect FAILED "the lane JUnit has no DirectGLES.Split. entry" -- run_split_no_arm
|
||||
|
||||
echo
|
||||
echo "smoke test: ${passes} passed, ${failures} failed"
|
||||
# Keep the private-file cases on the entry point used by CI and the local gate.
|
||||
if ! bash "${HERE}/testdata/split_private_log_smoke.sh"; then
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
if [ "${failures}" -gt 0 ]; then
|
||||
echo "CONTROL_SMOKE_TEST_FAILED"
|
||||
exit 1
|
||||
fi
|
||||
echo "CONTROL_SMOKE_TEST_OK"
|
||||
@@ -10,8 +10,7 @@
|
||||
# P5f (f1)'s NEGATIVE CONTROL (P5F-WIRE-COMPLETENESS.md §6): the dual-block rehearsal must be
|
||||
# the thing the dual-block lane measures.
|
||||
#
|
||||
# The contract is split_negative_controls.sh's, shrunk to one control because there is one
|
||||
# knob:
|
||||
# Its contract has two rules:
|
||||
#
|
||||
# 1. THE BASELINE MUST BE GREEN. DirectGLES.Split.DualBlock.* runs with
|
||||
# MOBILEGL_IPC_ROLE_SPLIT_STATE=1 exported (the entry deliberately does NOT pin the knob in
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
#!/bin/bash
|
||||
# MobileGL - scripts/ci/split_negative_controls.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# EXIT GATE E1's NEGATIVE CONTROL and EXIT GATE E3(a)'s.
|
||||
#
|
||||
# The body of test.yml's "Negative controls (barrier, map push)" step, which requires the verb
|
||||
# barrier and the persistent-map push to be load-bearing. It is a file, not an inline `run:` block,
|
||||
# so scripts/ci/control_smoke_test.sh runs these same lines off a runner against a stubbed ctest.
|
||||
#
|
||||
# WHAT THE REVIEW FOUND (ID-46 finding 8, CONFIRMED by execution; ID-48 assigns it here).
|
||||
# The previous version accepted ANY non-zero ctest exit as "the knob is load-bearing". A timeout, a
|
||||
# setup abort, an unrelated assertion, a harness that died before it read the knob at all - every
|
||||
# one of them printed "turned N selected entries red, as it must" and the step went green. The
|
||||
# verifier demonstrated it: a stubbed ctest with a NON-EMPTY selection that failed with
|
||||
# `UNRELATED_CONTROL_FAILURE` produced both controls' success messages and HARNESS_EXIT=0.
|
||||
#
|
||||
# So each control now has to say WHY the red is its own:
|
||||
#
|
||||
# 1. THE BASELINE MUST BE GREEN. The arming run below used to end in `|| true` and count every
|
||||
# case that was not <skipped/> as "ran" - so a case that RAN AND FAILED armed the controls,
|
||||
# and a control that turns an already-red entry red proves nothing at all. It now counts
|
||||
# PASSED cases, and a baseline with any failure in it is a hard error rather than an arming
|
||||
# signal.
|
||||
# 2. THE RED MUST CARRY THE SELECTED CASE'S OWN FAILURE TEXT. Each control names a regex of the
|
||||
# diagnostics its scenarios emit when that knob is off, and the red is refused if the output
|
||||
# carries none of them.
|
||||
#
|
||||
# WHY THE EVIDENCE IS THE SCENARIO'S ASSERTION TEXT AND NOT THE KNOB'S OWN LOG LINE.
|
||||
# ConfigLoader logs a named line for both knobs ("is the R-1 NEGATIVE
|
||||
# CONTROL", "is the E3(a) NEGATIVE CONTROL"), and it is tempting to grep for that. It is not
|
||||
# evidence: it is written at config load, by every process in the run, whatever happens next. A
|
||||
# setup abort would carry it too. It proves the knob was READ, never that the knob caused the red.
|
||||
# Only the failing case's own diagnostic does that. E1 now observes deterministic transport
|
||||
# waits in dedicated CPU tests; E3(a) retains its scenario assertion and private library log.
|
||||
# P5e ID-122: ordinary run-ahead draws need not fail with VERB_BARRIER=0, and the old
|
||||
# overlap Fatal cannot fire with the default BATCH_WAITS=1. Pixel failures are not E1 evidence.
|
||||
#
|
||||
# Usage: split_negative_controls.sh [--self-test]
|
||||
# CTEST ctest binary (default: ctest)
|
||||
# CONTROL_TMPDIR scratch dir for the junit + output (default: ${RUNNER_TEMP:-/tmp})
|
||||
# SPLIT_BASELINE_JUNIT the JUnit an earlier `ctest -L integration-split` wrote; its exit code is
|
||||
# read from the file of the same name ending in .rc. When both exist the
|
||||
# baseline is tallied from them instead of re-run.
|
||||
set -u
|
||||
|
||||
if [ "${1:-}" = "--self-test" ]; then
|
||||
bash "$(dirname "$0")/control_smoke_test.sh"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
CTEST="${CTEST:-ctest}"
|
||||
CONTROL_TMPDIR="${CONTROL_TMPDIR:-${RUNNER_TEMP:-/tmp}}"
|
||||
mkdir -p "${CONTROL_TMPDIR}"
|
||||
|
||||
log_helper="$(dirname "$0")/../../MobileGL/MG_IntegrationTest/Harness/split_log_paths.py"
|
||||
|
||||
# Check ownership even while the runtime lane is disarmed and will skip.
|
||||
python3 "${log_helper}" check "${CTEST}" "$PWD" || exit 1
|
||||
|
||||
# ---- the baseline ---------------------------------------------------------------------------
|
||||
#
|
||||
# THE ARMED STATE IS DERIVED FROM BEHAVIOUR, not from a marker string in the generated ctest files.
|
||||
# The first version read MGITEST_REMOTE_CLIENT_PRESENT out of *_tests.cmake, which was a
|
||||
# restatement of the CMake source probe review finding M-1 falsified; the arming condition is a
|
||||
# runtime fact inside each test process (MG_Config::Transport, ClientSession::Active(),
|
||||
# ImplementedVerbCount(), read by Harness/SplitRuntimePeek), so the only honest way to ask it from a
|
||||
# shell is to look at what the entries DID.
|
||||
# The metadata ownership test shares the label but cannot arm runtime controls.
|
||||
#
|
||||
# The lane step before this one already ran every integration-split entry on this runner and
|
||||
# library, and left its JUnit and exit code. Both are read when they exist; only the
|
||||
# DirectGLES.Split. entries count, as in the rerun. The rerun stays for a job whose lane step
|
||||
# never ran (an earlier gate failed), where this baseline is the only run of those entries.
|
||||
baseline_rc_file="${SPLIT_BASELINE_JUNIT:-}"
|
||||
baseline_rc_file="${baseline_rc_file%.xml}.rc"
|
||||
if [ -n "${SPLIT_BASELINE_JUNIT:-}" ] && [ -f "${SPLIT_BASELINE_JUNIT}" ] && [ -f "${baseline_rc_file}" ]; then
|
||||
junit="${SPLIT_BASELINE_JUNIT}"
|
||||
baseline_rc=$(tr -dc '0-9' < "${baseline_rc_file}")
|
||||
baseline_rc="${baseline_rc:-1}"
|
||||
echo "baseline tallied from ${junit} (the lane step's own run, ctest exit ${baseline_rc}); no rerun"
|
||||
else
|
||||
# A directory of its own: the job uploads CONTROL_TMPDIR/*.xml for the skip census, which has
|
||||
# no row for this rerun.
|
||||
mkdir -p "${CONTROL_TMPDIR}/split-controls"
|
||||
junit="${CONTROL_TMPDIR}/split-controls/isplit.xml"
|
||||
rm -f "${junit}"
|
||||
echo "::notice::no baseline JUnit and exit code from the lane step (SPLIT_BASELINE_JUNIT='${SPLIT_BASELINE_JUNIT:-}'), so the controls reran the DirectGLES.Split. baseline"
|
||||
"${CTEST}" -L integration-split -R '^DirectGLES\.Split\.' -j 4 --no-tests=error --output-junit "${junit}"
|
||||
baseline_rc=$?
|
||||
fi
|
||||
|
||||
if [ ! -f "${junit}" ]; then
|
||||
echo "::error::the baseline run wrote no ${junit} (ctest exit ${baseline_rc}), so nothing below can tell an armed lane from a broken one"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tally=$(python3 "$(dirname "$0")/junit_tally.py" "${junit}" --require-split-ran)
|
||||
if [ -z "${tally}" ]; then
|
||||
echo "::error::could not tally ${junit}; a run whose result cannot be read is not an arming signal"
|
||||
exit 1
|
||||
fi
|
||||
baseline_passed=$(echo "${tally}" | cut -d' ' -f1)
|
||||
baseline_failed=$(echo "${tally}" | cut -d' ' -f2)
|
||||
baseline_skipped=$(echo "${tally}" | cut -d' ' -f3)
|
||||
|
||||
echo "split entries - passed: ${baseline_passed}, failed: ${baseline_failed}, skipped: ${baseline_skipped} (ctest exit ${baseline_rc})"
|
||||
|
||||
# A RED BASELINE DISARMS THE CONTROLS RATHER THAN ARMING THEM (review finding 8, second half).
|
||||
# `|| true` plus a "not skipped" counter used to treat a case that ran and FAILED as evidence the
|
||||
# lane was live. Turning an already-red entry red is not a measurement.
|
||||
if [ "${baseline_failed}" -gt 0 ] || [ "${baseline_rc}" -ne 0 ]; then
|
||||
echo "::error::${baseline_failed} DirectGLES.Split. entries are ALREADY RED with both knobs at their defaults, so neither negative control below can attribute its red to the knob it turns. Fix the lane first; a control measured against a red baseline is not a control. (This used to be swallowed by an unconditional '|| true' and counted as 'the lane is armed'.)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${baseline_passed}" -lt 1 ]; then
|
||||
echo "::error::split baseline FAILED: every DirectGLES.Split. entry SKIPPED; the split implementation did not execute"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---- the controls ---------------------------------------------------------------------------
|
||||
#
|
||||
# run_control <name> <filter> <ctest evidence regex> <private-log evidence regex|""> <VAR=VALUE>...
|
||||
#
|
||||
# THE FOURTH ARGUMENT IS NEW AND E3(a) IS WHY. The header above argues that the knob's own
|
||||
# ConfigLoader line is not evidence, and that is still true: it is written at config load, by
|
||||
# every process in the run, whatever happens next. What IS evidence is a line the knob's
|
||||
# BEHAVIOUR emits at the site that changed - and until now E3(a) had none, because
|
||||
# PersistentMapTracker::PushBlocksFor simply `return`ed at blockBytes == 0. The joint gate
|
||||
# recorded exactly that ("There is no Fatal for block size zero ... No nonexistent private-file
|
||||
# Fatal is quoted", joint-v1.md 3) and ID-65 assigned the missing line here. With it, E3(a) no
|
||||
# longer rests on a pixel assertion alone: the red must carry the scenario's own diagnostic AND
|
||||
# the library's own statement that the push was disabled, from the entry's private file.
|
||||
# The optional 5th positional is a ctest -E EXCLUSION, applied to every ctest invocation this
|
||||
# function makes. It exists because ctest -R is POSIX ERE: there is no negative lookahead, so a
|
||||
# selection that must say "these, except that one" cannot say it in -R alone. E1 below is the
|
||||
# case that forced it (ID-122).
|
||||
run_control() {
|
||||
name="$1"; filter="$2"; evidence="$3"; private_evidence="$4"; exclude="${5:-}"; shift 5
|
||||
if [ -n "${exclude}" ]; then EX=(-E "${exclude}"); else EX=(); fi
|
||||
export SPLIT_LOG_EXCLUDE="${exclude}"
|
||||
|
||||
matched=$("${CTEST}" -N -L integration-split -R "${filter}" "${EX[@]}" | grep -cE '^ *Test *#[0-9]+:')
|
||||
if [ "${matched}" -lt 1 ]; then
|
||||
echo "::error::${name} selected ${matched} tests; its filter no longer matches anything"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
manifest="${CONTROL_TMPDIR}/split-tests.json"
|
||||
"${CTEST}" --show-only=json-v1 > "${manifest}" || exit 1
|
||||
# Remove selected files first: a previous Fatal must never arm a new red.
|
||||
python3 "${log_helper}" reset "${manifest}" "${filter}" || exit 1
|
||||
|
||||
out="${CONTROL_TMPDIR}/control-output.txt"
|
||||
result="${CONTROL_TMPDIR}/control.xml"
|
||||
rm -f "${result}"
|
||||
env "$@" "${CTEST}" --output-on-failure -L integration-split -R "${filter}" "${EX[@]}" --no-tests=error --output-junit "${result}" > "${out}" 2>&1
|
||||
control_rc=$?
|
||||
cat "${out}"
|
||||
|
||||
# Inspect JUnit before exit status or private Fatal: a skipped pre-flight can carry both.
|
||||
python3 "${log_helper}" results "${manifest}" "${filter}" "${result}" 'E3(a)' || exit 1
|
||||
|
||||
if [ "${control_rc}" -eq 0 ]; then
|
||||
echo "::error::${name} left ${matched} split entries GREEN, so the knob it turns is not load-bearing and the gate it controls proves nothing."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! python3 "${log_helper}" assertion "${manifest}" "${filter}" "${result}" "${evidence}"; then
|
||||
echo "::error::${name} FAILED: red lacks its persistent-map push diagnostic. Required: ${evidence}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ... and, where the knob has one, the LIBRARY's own line as well, out of the entry's private
|
||||
# file. Both halves are required: the scenario assertion says the pixels were wrong, and this
|
||||
# says the code path the knob turns off is the one that stopped running. A red that has only
|
||||
# the first half is consistent with any other defect in the same scenario.
|
||||
if [ -n "${private_evidence}" ]; then
|
||||
python3 "${log_helper}" evidence "${manifest}" "${filter}" "${private_evidence}" "${name}" || exit 1
|
||||
fi
|
||||
|
||||
echo "${name} turned ${matched} selected entries red, and the red carries the scenario's own diagnostic, as it must"
|
||||
}
|
||||
|
||||
# E1: the default arm must actually park for a kWaitApplied record and for a
|
||||
# kWaitNone record whose server has no run-ahead cap. Removing the verb barrier
|
||||
# must fail those exact return-before-apply assertions. A third positive case
|
||||
# proves that a cap-authorized kWaitNone record does not wait. The peer releases
|
||||
# on an observed Park or on the client's return, not on a sleep or pixel race.
|
||||
# The helper demands green baseline, two named reds, and green restoration;
|
||||
# skipped/missing cases, timeouts and unrelated failures never count as evidence.
|
||||
python3 "$(dirname "$0")/wait_boundary_negative_control.py" \
|
||||
--ctest "${CTEST}" --out "${CONTROL_TMPDIR}/e1" || exit 1
|
||||
|
||||
# E3(a): PersistentMapTracker::PushBlocksFor stops at blockBytes == 0 - deliberately, because 0
|
||||
# is the negative control and not "unlimited". Two independent halves are now required:
|
||||
# * the SCENARIO's own assertion in ctest's output. PersistentCoherentMapScenario.cpp:414-417 /
|
||||
# 442-443 name the missing second write; a generic source-line Failure is not accepted,
|
||||
# because an unrelated assertion in the same case is not this red;
|
||||
# * the LIBRARY's own line in the entry's private file, saying the push was disabled by this
|
||||
# knob. It did not exist until ID-65 assigned it (joint-v1.md 3), which is why this control
|
||||
# used to rest on the pixels alone.
|
||||
# TheMapLandsInTheArmItsLaneDeclares skips by design outside PersistentMapArm.
|
||||
# Select only the pixel cases; a pre-flight skip in either remains a hard failure.
|
||||
run_control "negative control E3(a) (MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0)" \
|
||||
'DirectGLES\.Split\.(SmallRing\.)?PersistentCoherentMapScenario\.(TwoWritesThroughTheCoherentPointerEachReachTheirOwnDraw|AWriteAfterAFrameBoundaryReachesTheNextFramesDraw)$' \
|
||||
"the SECOND write through the same mapping, announced by nothing|frame 1's write through the SAME mapping, after a Present" \
|
||||
'MGPipe: persistent-map push disabled - MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0' \
|
||||
'' \
|
||||
MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
#!/bin/bash
|
||||
# MobileGL - scripts/ci/testdata/split_private_log_smoke.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# Exercise E1's per-test boundary evidence and E3(a)'s private-file read (R-16).
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
WORK=$(mktemp -d "${TMPDIR:-/tmp}/split-private-log.XXXXXX")
|
||||
trap 'rm -rf "${WORK}"' EXIT
|
||||
cp "${HERE}/testdata/stub_ctest.sh" "${WORK}/ctest"
|
||||
chmod +x "${WORK}/ctest"
|
||||
passes=0
|
||||
for mode in missing-fatal stdout-fatal stale-fatal evidence e3-unrelated e3-no-private skipped-selection e3-skipped-selection notrun-selection missing-selection partial-fatal wrong-fatal; do
|
||||
mkdir -p "${WORK}/${mode}"
|
||||
rc=0
|
||||
STUB_MODE="${mode}" CTEST="${WORK}/ctest" CONTROL_TMPDIR="${WORK}/${mode}" \
|
||||
bash "${HERE}/split_negative_controls.sh" > "${WORK}/${mode}.out" 2>&1 || rc=$?
|
||||
if [ "${mode}" = evidence ]; then
|
||||
[ "${rc}" = 0 ] && grep -q "negative control E3(a).*scenario's own diagnostic" "${WORK}/${mode}.out" || {
|
||||
cat "${WORK}/${mode}.out"; echo "NOT OK own boundary/private-file evidence must PASS"; exit 1;
|
||||
}
|
||||
grep -qFx "private-log evidence: DirectGLES.Split.PersistentCoherentMapScenario.TwoWritesThroughTheCoherentPointerEachReachTheirOwnDraw: ${WORK}/${mode}/pmap.log" "${WORK}/${mode}.out" || {
|
||||
cat "${WORK}/${mode}.out"; echo "NOT OK private-file evidence line must name the selected entry and path"; exit 1;
|
||||
}
|
||||
else
|
||||
message='red lacks its own observed wait-boundary failure'
|
||||
[ "${mode}" != e3-unrelated ] || message='FAILED: red lacks its persistent-map push diagnostic'
|
||||
case "${mode}" in
|
||||
e3-no-private) message='no selected private log carries /MGPipe: persistent-map push disabled' ;;
|
||||
skipped-selection|notrun-selection) message='selected testcase skipped or did not run' ;;
|
||||
e3-skipped-selection) message='SplitLogPaths FAILED: E3(a) control: the knob killed the pre-flight, not the entry - 1 selected entries skipped' ;;
|
||||
missing-selection) message='selected testcase names are missing, duplicated or unexpected' ;;
|
||||
esac
|
||||
match=(-qF)
|
||||
[ "${rc}" != 0 ] && grep "${match[@]}" "${message}" "${WORK}/${mode}.out" || {
|
||||
cat "${WORK}/${mode}.out"; echo "NOT OK ${mode}: control must report FAILED for its own reason"; exit 1;
|
||||
}
|
||||
fi
|
||||
echo "ok private-log ${mode} (control rc=${rc})"
|
||||
passes=$((passes + 1))
|
||||
done
|
||||
echo "private-log smoke test: ${passes} passed, 0 failed"
|
||||
Vendored
-269
@@ -1,269 +0,0 @@
|
||||
#!/bin/bash
|
||||
# MobileGL - scripts/ci/testdata/stub_ctest.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# A stubbed `ctest` for scripts/ci/control_smoke_test.sh.
|
||||
#
|
||||
# Descended from the verification agent's stub (wave1-codex-verify.md 8, ~/w7/p5-verify-f8-stub/ctest),
|
||||
# which is what CONFIRMED that the negative controls accepted an unrelated failure. Every mode below
|
||||
# is deliberately the BEST case for the control under test: the selection is never empty except in
|
||||
# the mode that exists to test the empty-selection guard, and the baseline is green except in the
|
||||
# mode that exists to test the red-baseline guard. If a control passes here it is because the
|
||||
# control's logic is wrong, not because the stub starved it.
|
||||
#
|
||||
# STUB_MODE:
|
||||
# unrelated baseline green; the control's own run fails with UNRELATED_CONTROL_FAILURE
|
||||
# evidence baseline green; the control's own run fails with the scenarios' own wording
|
||||
# missing-fatal private file exists but lacks Fatal (stdout status still fails)
|
||||
# stdout-fatal Fatal exists only on stdout, never in the private file
|
||||
# stale-fatal Fatal exists before reset, never from this control run
|
||||
# e3-unrelated E1 has its private Fatal; E3(a) fails for an unrelated reason
|
||||
# skipped-selection knob kills pre-flight; private Fatal exists, entry skips, ctest exits 0
|
||||
# notrun-selection selected entry was not run, despite a private Fatal
|
||||
# missing-selection selected entry is absent from the result XML
|
||||
# partial-fatal two entries fail, but only the first has its expected private Fatal
|
||||
# wrong-fatal entry fails with a different Fatal in its private file
|
||||
# e3-no-private E1 has its private Fatal; E3(a) has its PIXEL assertion but the library
|
||||
# never says the push was disabled (the half ID-65 added)
|
||||
# green baseline green; the control's own run PASSES (the knob is not load-bearing)
|
||||
# red-baseline the baseline itself has a failed entry
|
||||
# all-skipped the baseline is entirely skipped (lost implementation, a hard failure)
|
||||
set -u
|
||||
|
||||
mode="${STUB_MODE:?STUB_MODE must be set}"
|
||||
|
||||
listing=1
|
||||
junit=""
|
||||
prev=""
|
||||
for a in "$@"; do
|
||||
[ "$a" = "--show-only=json-v1" ] && json_requested=1
|
||||
[ "$a" = "-N" ] && listing_requested=1
|
||||
if [ "$prev" = "--output-junit" ]; then junit="$a"; fi
|
||||
if [ "$prev" = "-R" ]; then selector="$a"; fi
|
||||
prev="$a"
|
||||
done
|
||||
listing_requested="${listing_requested:-0}"
|
||||
|
||||
# E1's CPU-only boundary probes use their own testcase results. Keep observations
|
||||
# inside each testcase so stdout-only and stale evidence remain rejected.
|
||||
if [[ "${selector:-}" == *RemoteWaitBoundaryControl* ]]; then
|
||||
python3 - "${mode}" "${junit}" "${selector}" <<'PY'
|
||||
import os, re, sys
|
||||
import xml.etree.ElementTree as ET
|
||||
mode, path, selector = sys.argv[1:]
|
||||
barrier = int(os.environ['MOBILEGL_IPC_VERB_BARRIER'])
|
||||
negative = barrier == 0
|
||||
prefix = 'RemoteWaitBoundaryControl.'
|
||||
specs = [
|
||||
('AppliedClassWaitsWithRunAheadCap', 'ApplierReset', 1, True,
|
||||
'E1 wait boundary: ApplierReset returned before apply'),
|
||||
('MissingServerCapKeepsClearLockstep', 'Clear', 0, True,
|
||||
'E1 wait boundary: Clear without server cap returned before apply'),
|
||||
('ServerCapAllowsClearToRunAhead', 'Clear', 1, False, ''),
|
||||
]
|
||||
root = ET.Element('testsuite')
|
||||
failures = 0
|
||||
for index, (short, op, cap, waits, diagnostic) in enumerate(specs):
|
||||
name = prefix + short
|
||||
if not re.search(selector, name):
|
||||
continue
|
||||
if mode == 'e1-empty' or (negative and mode == 'missing-selection' and index == 1):
|
||||
continue
|
||||
case = ET.SubElement(root, 'testcase', name=name, status='run')
|
||||
waited = int(waits and barrier == 1)
|
||||
observation = (f'E1 observation: op={op} cap={cap} barrier={barrier} parked={waited} '
|
||||
f'applied_before_return={waited} emitted=1 peer_seen=1')
|
||||
failed = negative and mode != 'green'
|
||||
if mode == 'e1-red-baseline' and 'baseline' in path:
|
||||
failed = True
|
||||
if mode == 'e1-restore-red' and 'restored' in path:
|
||||
failed = True
|
||||
if mode == 'e1-waitall' and not waits:
|
||||
failed = True
|
||||
observation = observation.replace('parked=0', 'parked=1').replace('applied_before_return=0', 'applied_before_return=1')
|
||||
if negative and mode == 'skipped-selection':
|
||||
ET.SubElement(case, 'skipped')
|
||||
failed = False
|
||||
if negative and mode == 'notrun-selection':
|
||||
case.set('status', 'notrun')
|
||||
failed = False
|
||||
if failed:
|
||||
ET.SubElement(case, 'failure', message='control red')
|
||||
failures += 1
|
||||
text = observation + '\n' + (diagnostic if negative else '')
|
||||
if negative:
|
||||
if mode == 'unrelated' or mode == 'wrong-fatal':
|
||||
text = observation + '\nUNRELATED_CONTROL_FAILURE'
|
||||
elif mode == 'missing-fatal' or (mode == 'partial-fatal' and index == 1):
|
||||
text = diagnostic
|
||||
elif mode == 'stdout-fatal':
|
||||
print(text) # Must not qualify as this testcase's own evidence.
|
||||
text = 'UNRELATED_CONTROL_FAILURE'
|
||||
elif mode == 'stale-fatal':
|
||||
text = text.replace('barrier=0', 'barrier=1')
|
||||
elif mode == 'e1-no-peer':
|
||||
text = text.replace('peer_seen=1', 'peer_seen=0')
|
||||
elif mode == 'e1-no-emit':
|
||||
text = text.replace('emitted=1', 'emitted=0')
|
||||
ET.SubElement(case, 'system-out').text = text
|
||||
ET.ElementTree(root).write(path, encoding='utf-8', xml_declaration=True)
|
||||
print(f'E1 stub: {len(root)} selected, {failures} failed')
|
||||
sys.exit(8 if failures else 0)
|
||||
PY
|
||||
exit $?
|
||||
fi
|
||||
|
||||
emit_listing() {
|
||||
echo "Test project /stub"
|
||||
echo " Test #1: DirectGLES.Split.ClearThenReadPixelsScenario.ClearWithNoDrawIsVisibleToDefaultFramebufferReadPixels"
|
||||
if [ "${mode}" = partial-fatal ]; then
|
||||
echo " Test #2: DirectGLES.Split.TriangleScenario.SecondEntry"
|
||||
echo "Total Tests: 2"
|
||||
else
|
||||
echo "Total Tests: 1"
|
||||
fi
|
||||
}
|
||||
|
||||
write_junit() {
|
||||
if [ "${MOBILEGL_IPC_VERB_BARRIER:-1}" = 0 ] || [ "${MOBILEGL_IPC_PERSISTENT_BLOCK_KB:-64}" = 0 ]; then
|
||||
entry=DirectGLES.Split.ClearThenReadPixelsScenario.ClearWithNoDrawIsVisibleToDefaultFramebufferReadPixels
|
||||
[ "${MOBILEGL_IPC_PERSISTENT_BLOCK_KB:-64}" != 0 ] || entry=DirectGLES.Split.PersistentCoherentMapScenario.TwoWritesThroughTheCoherentPointerEachReachTheirOwnDraw
|
||||
body="<testcase name=\"${entry}\" status=\"fail\"><failure message=\"control red\"/></testcase>"
|
||||
if [ "${MOBILEGL_IPC_PERSISTENT_BLOCK_KB:-64}" = 0 ]; then
|
||||
case "${mode}" in
|
||||
evidence|e3-no-private)
|
||||
body="<testcase name=\"${entry}\" status=\"fail\"><failure/><system-out>the SECOND write through the same mapping, announced by nothing</system-out></testcase>" ;;
|
||||
esac
|
||||
fi
|
||||
if [ "${mode}" = partial-fatal ] && [ "${MOBILEGL_IPC_VERB_BARRIER:-1}" = 0 ]; then
|
||||
body="${body}<testcase name=\"DirectGLES.Split.TriangleScenario.SecondEntry\" status=\"fail\"><failure/></testcase>"
|
||||
fi
|
||||
case "${mode}" in
|
||||
e3-skipped-selection)
|
||||
if [ "${MOBILEGL_IPC_PERSISTENT_BLOCK_KB:-64}" = 0 ]; then
|
||||
body="<testcase name=\"${entry}\" status=\"notrun\"><skipped/></testcase>"
|
||||
fi ;;
|
||||
skipped-selection) body="<testcase name=\"${entry}\" status=\"notrun\"><skipped/></testcase>" ;;
|
||||
notrun-selection) body="<testcase name=\"${entry}\" status=\"notrun\"/>" ;;
|
||||
missing-selection) body='' ;;
|
||||
green) body="<testcase name=\"${entry}\" status=\"run\"/>" ;;
|
||||
esac
|
||||
printf '%s\n' "<testsuite>${body}</testsuite>" > "$1"
|
||||
return
|
||||
fi
|
||||
case "${mode}" in
|
||||
red-baseline)
|
||||
body='<testcase name="DirectGLES.Split.TriangleScenario.AVboBackedTriangleReachesReadPixels" status="failed"><failure message="already red"/></testcase>'
|
||||
;;
|
||||
all-skipped)
|
||||
body='<testcase name="DirectGLES.Split.TriangleScenario.AVboBackedTriangleReachesReadPixels" status="notrun"><skipped/></testcase>'
|
||||
;;
|
||||
*)
|
||||
body='<testcase name="DirectGLES.Split.TriangleScenario.AVboBackedTriangleReachesReadPixels" status="run" time="0.3"/>'
|
||||
;;
|
||||
esac
|
||||
printf '%s\n' '<?xml version="1.0" encoding="UTF-8"?>' "<testsuite name=\"stub\">" " ${body}" '</testsuite>' > "$1"
|
||||
}
|
||||
|
||||
# Model the library file sink separately from ctest stdout (ID-53).
|
||||
#
|
||||
# P6: the real sink writes one file per ROLE, `<base>.client.log` / `<base>.server.log`, and the
|
||||
# validator derives those from the ENV base. So the manifest below keeps the base names (what the
|
||||
# lane sets) while the on-disk writes here land in the CLIENT file - which is the role that raises
|
||||
# these markers in a real run.
|
||||
log="${CONTROL_TMPDIR}/entry.client.log"
|
||||
if [ "${json_requested:-0}" = 1 ]; then
|
||||
python3 -c 'import json, os; p=os.environ["CONTROL_TMPDIR"]; entries=[("ClearThenReadPixelsScenario.ClearWithNoDrawIsVisibleToDefaultFramebufferReadPixels", "entry.log"), ("PersistentCoherentMapScenario.TwoWritesThroughTheCoherentPointerEachReachTheirOwnDraw", "pmap.log")]; entries += [("TriangleScenario.SecondEntry", "second.log")] if os.environ["STUB_MODE"] == "partial-fatal" else []; print(json.dumps({"tests": [{"name": "DirectGLES.Split."+n, "properties": [{"name": "LABELS", "value": ["integration-split"]}, {"name": "ENVIRONMENT", "value": ["MOBILEGL_LOG_FILE_PATH="+p+"/"+f]}]} for n,f in entries]}))'
|
||||
if [ "${mode}" = stale-fatal ]; then
|
||||
echo 'Fatal{BarrierViolation, "DrawVbo"}' > "${log}"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "${listing_requested}" = "1" ]; then
|
||||
emit_listing
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -n "${junit}" ]; then
|
||||
write_junit "${junit}"
|
||||
if [ "${MOBILEGL_IPC_VERB_BARRIER:-1}" != 0 ] && [ "${MOBILEGL_IPC_PERSISTENT_BLOCK_KB:-64}" != 0 ]; then
|
||||
case "${mode}" in
|
||||
red-baseline) echo "1/1 Test #1: ... ***Failed"; exit 8 ;;
|
||||
*) echo "100% tests passed, 0 tests failed out of 1"; exit 0 ;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
|
||||
# The control's own run.
|
||||
if [ "${MOBILEGL_IPC_VERB_BARRIER:-1}" = 0 ]; then
|
||||
case "${mode}" in
|
||||
evidence|e3-unrelated|e3-no-private|skipped-selection|e3-skipped-selection|notrun-selection|missing-selection|partial-fatal) echo 'Fatal{BarrierViolation, "DrawVbo"}' > "${log}" ;;
|
||||
wrong-fatal) echo 'Fatal{ReplyMissing, "DrawVbo"}' > "${log}" ;;
|
||||
missing-fatal) echo "library setup only; no fatal" > "${log}" ;;
|
||||
stdout-fatal) echo 'Fatal{BarrierViolation, "DrawVbo"}' ;;
|
||||
esac
|
||||
fi
|
||||
# E3(a)'s library line, in the PERSISTENT-MAP entry's own private file - a different file from
|
||||
# E1's, exactly as the manifest above declares. `e3-no-private` is the mode that leaves it out:
|
||||
# the pixel assertion arrives, the library says nothing, and the control must refuse the red.
|
||||
if [ "${MOBILEGL_IPC_PERSISTENT_BLOCK_KB:-64}" = 0 ] && [ "${mode}" = evidence ]; then
|
||||
echo 'MGPipe: persistent-map push disabled - MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0 is exit gate E3(a)'"'"'s NEGATIVE CONTROL' \
|
||||
> "${CONTROL_TMPDIR}/pmap.client.log"
|
||||
fi
|
||||
case "${mode}" in
|
||||
e3-skipped-selection)
|
||||
if [ "${MOBILEGL_IPC_PERSISTENT_BLOCK_KB:-64}" = 0 ]; then
|
||||
echo 'selected E3 entry ... ***Skipped'
|
||||
exit 0
|
||||
fi
|
||||
exit 8
|
||||
;;
|
||||
skipped-selection|notrun-selection|missing-selection)
|
||||
echo '1/1 Test #1: selected entry ... ***Skipped'
|
||||
echo '100% tests passed, 0 tests failed out of 1'
|
||||
exit 0
|
||||
;;
|
||||
e3-no-private)
|
||||
# E1's half passes (its private Fatal is written above); E3(a)'s pixel assertion arrives on
|
||||
# stdout and its private line does not, so the control must stop at the second half.
|
||||
echo "1/1 Test #1: DirectGLES.Split.ClearThenReadPixelsScenario.ClearWithNoDrawIsVisibleToDefaultFramebufferReadPixels ...***Failed"
|
||||
if [ "${MOBILEGL_IPC_PERSISTENT_BLOCK_KB:-64}" = 0 ]; then
|
||||
echo "../MobileGL/MG_IntegrationTest/Scenarios/PersistentCoherentMapScenario.cpp:414: Failure"
|
||||
echo "the SECOND write through the same mapping, announced by nothing: this is exit gate E3(b)"
|
||||
fi
|
||||
exit 8
|
||||
;;
|
||||
unrelated|missing-fatal|stdout-fatal|stale-fatal|e3-unrelated|partial-fatal|wrong-fatal)
|
||||
echo "1/1 Test #1: DirectGLES.Split.ClearThenReadPixelsScenario.ClearWithNoDrawIsVisibleToDefaultFramebufferReadPixels ...***Failed"
|
||||
echo "UNRELATED_CONTROL_FAILURE: the harness aborted in setup before the knob was read"
|
||||
if [ "${MOBILEGL_IPC_PERSISTENT_BLOCK_KB:-64}" = 0 ]; then
|
||||
case "${mode}" in
|
||||
missing-fatal|stdout-fatal|stale-fatal)
|
||||
echo "the SECOND write through the same mapping, announced by nothing" ;;
|
||||
esac
|
||||
fi
|
||||
exit 8
|
||||
;;
|
||||
evidence)
|
||||
# E1 is file-only above; E3(a) emits its scenario assertion to ctest.
|
||||
echo "1/1 Test #1: DirectGLES.Split.ClearThenReadPixelsScenario.ClearWithNoDrawIsVisibleToDefaultFramebufferReadPixels ...***Failed"
|
||||
echo "../MobileGL/MG_IntegrationTest/Scenarios/ClearThenReadPixelsScenario.cpp:290: Failure"
|
||||
echo "Expected: (bottom.r) > (200), actual: '\\0' vs 200"
|
||||
echo "the SECOND write through the same mapping, announced by nothing: this is exit gate E3(b)"
|
||||
exit 8
|
||||
;;
|
||||
green)
|
||||
echo "100% tests passed, 0 tests failed out of 1"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "stub_ctest: unknown STUB_MODE '${mode}'" >&2
|
||||
exit 127
|
||||
;;
|
||||
esac
|
||||
@@ -1,105 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# MobileGL - scripts/ci/wait_boundary_negative_control.py
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
"""E1: green/red/restored-green proof of real ClientSession wait boundaries.
|
||||
|
||||
The peer holds apply until the actual transport parks or the emit call returns.
|
||||
VERB_BARRIER=0 must fail only the two owed waits; ordinary run-ahead draws need
|
||||
not fail. Each arm is checked from its own CTest JUnit testcase, never a global
|
||||
grep, stale library log, arbitrary nonzero exit, or timing-dependent pixel.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
PREFIX = "RemoteWaitBoundaryControl."
|
||||
CASES = {
|
||||
"AppliedClassWaitsWithRunAheadCap": ("ApplierReset", 1, True,
|
||||
"E1 wait boundary: ApplierReset returned before apply"),
|
||||
"MissingServerCapKeepsClearLockstep": ("Clear", 0, True,
|
||||
"E1 wait boundary: Clear without server cap returned before apply"),
|
||||
"ServerCapAllowsClearToRunAhead": ("Clear", 1, False, ""),
|
||||
}
|
||||
|
||||
|
||||
def own_negative(text, expected_observation, diagnostic):
|
||||
return expected_observation in text and diagnostic in text
|
||||
|
||||
|
||||
def inspect(path, selected, barrier, negative, rc):
|
||||
root = ET.parse(path).getroot()
|
||||
cases = root.findall(".//testcase")
|
||||
names = [case.get("name") for case in cases]
|
||||
if len(names) != len(set(names)) or set(names) != set(selected):
|
||||
raise ValueError("selected testcase names are missing, duplicated or unexpected")
|
||||
for case in cases:
|
||||
name = case.get("name")
|
||||
if case.find("skipped") is not None or case.get("status") in {"notrun", "disabled"}:
|
||||
raise ValueError(f"{name}: selected testcase skipped or did not run")
|
||||
failed = case.find("failure") is not None or case.find("error") is not None
|
||||
op, cap, waits, diagnostic = CASES[name.removeprefix(PREFIX)]
|
||||
waited = int(waits and barrier == 1)
|
||||
observation = (f"E1 observation: op={op} cap={cap} barrier={barrier} parked={waited} "
|
||||
f"applied_before_return={waited} emitted=1 peer_seen=1")
|
||||
text = "\n".join(case.itertext())
|
||||
if negative:
|
||||
if not failed or rc == 0:
|
||||
raise ValueError(f"{name}: disabled barrier did not fail the owed wait")
|
||||
if not own_negative(text, observation, diagnostic):
|
||||
raise ValueError(f"{name}: red lacks its own observed wait-boundary failure")
|
||||
elif failed or rc != 0 or observation not in text:
|
||||
raise ValueError(f"{name}: baseline/restored arm did not execute its green boundary proof")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--ctest", default=os.environ.get("CTEST", "ctest"))
|
||||
parser.add_argument("--out", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
args.out.mkdir(parents=True, exist_ok=True)
|
||||
all_cases = [PREFIX + name for name in CASES]
|
||||
negative_cases = [PREFIX + name for name, (_, _, waits, _) in CASES.items() if waits]
|
||||
ok = True
|
||||
for arm, barrier, negative, selected in (
|
||||
("baseline", 1, False, all_cases),
|
||||
("control", 0, True, negative_cases),
|
||||
("restored", 1, False, all_cases),
|
||||
):
|
||||
# Never run a negative arm against a failed baseline. A failed negative
|
||||
# still proceeds through restoration and must not consume stale XML.
|
||||
if arm == "control" and not ok:
|
||||
continue
|
||||
result = args.out / f"e1-{arm}.xml"
|
||||
result.unlink(missing_ok=True)
|
||||
env = os.environ.copy()
|
||||
env["MOBILEGL_IPC_VERB_BARRIER"] = str(barrier)
|
||||
selector = "^(" + "|".join(re.escape(name) for name in selected) + ")$"
|
||||
command = [args.ctest, "-L", "unit", "-R", selector, "--no-tests=error",
|
||||
"--output-on-failure", "--output-junit", str(result)]
|
||||
try:
|
||||
run = subprocess.run(command, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
text=True, timeout=90)
|
||||
(args.out / f"e1-{arm}.out").write_text(run.stdout, encoding="utf-8")
|
||||
print(run.stdout, end="", flush=True)
|
||||
inspect(result, selected, barrier, negative, run.returncode)
|
||||
print(f"E1 {arm}: {'EXPECTED_RED' if negative else 'GREEN'} ({len(selected)} cases)", flush=True)
|
||||
except (OSError, ValueError, ET.ParseError, subprocess.TimeoutExpired) as exc:
|
||||
print(f"E1 FAILED: {arm}: {exc}", file=sys.stderr, flush=True)
|
||||
ok = False
|
||||
if ok:
|
||||
print("negative control E1: owed waits failed by their own observed boundary assertions; baseline and restoration green")
|
||||
return 0 if ok else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -12983,15 +12983,6 @@
|
||||
"names_from": "junit-integration-monolith-control-monolith/integration",
|
||||
"optional": "runs only when build-linux-split builds its own library, or the split lane's logs do not prove the IPC defaults"
|
||||
},
|
||||
"junit-integration-split/control": {
|
||||
"skipped": [],
|
||||
"names": [
|
||||
"DirectGLES.Split.PersistentCoherentMapScenario.AWriteAfterAFrameBoundaryReachesTheNextFramesDraw",
|
||||
"DirectGLES.Split.PersistentCoherentMapScenario.TwoWritesThroughTheCoherentPointerEachReachTheirOwnDraw",
|
||||
"DirectGLES.Split.SmallRing.PersistentCoherentMapScenario.AWriteAfterAFrameBoundaryReachesTheNextFramesDraw",
|
||||
"DirectGLES.Split.SmallRing.PersistentCoherentMapScenario.TwoWritesThroughTheCoherentPointerEachReachTheirOwnDraw"
|
||||
]
|
||||
},
|
||||
"junit-integration-split/spawn-baseline": {
|
||||
"skipped": [
|
||||
"DirectGLES.Spawn.ClipDistanceScenario.ADisabledClipDistanceRemovesNothing",
|
||||
|
||||
Reference in New Issue
Block a user