mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-17 08:38:30 +09:00
Merge branch 'feat/disaggregated' into p5/c1
# Conflicts: # MobileGL/MG_Remote/Client/ClientSession.cpp # MobileGL/MG_Test/Wire/CMakeLists.txt
This commit is contained in:
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/bin/bash
|
||||
# R-16 FOR THE CI NEGATIVE CONTROLS THEMSELVES: a control-run smoke test.
|
||||
#
|
||||
# BRIEF-P5 13 (R-16) says a negative control must assert its own failure reason and that every gate
|
||||
# carries a line saying "I made it red once, by doing X". The two controls this file exercises ARE
|
||||
# gates, and until ID-46 finding 8 nobody could make either of them red, because a workflow `run:`
|
||||
# block only executes on a runner. The wave-1 verification agent had to hand-copy the blocks into
|
||||
# throwaway harnesses to show they were broken (wave1-codex-verify.md 8). This file is that
|
||||
# experiment, kept: it runs the REAL control scripts - the same files .github/workflows/test.yml
|
||||
# invokes, not copies of them - against a stubbed ctest, and checks that each one 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. Before ID-48's fix both controls printed their success message and the step exited 0. They
|
||||
# must now report FAILED.
|
||||
#
|
||||
# usage: control_smoke_test.sh
|
||||
set -u
|
||||
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
WORK="$(mktemp -d)"
|
||||
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
|
||||
}
|
||||
|
||||
run_split() { # $1 = STUB_MODE
|
||||
env -i PATH="${STUB_DIR}:/usr/bin:/bin" STUB_MODE="$1" \
|
||||
CTEST=ctest CONTROL_TMPDIR="${WORK}/tmp-$1" \
|
||||
bash "${HERE}/split_negative_controls.sh"
|
||||
}
|
||||
|
||||
run_retrace() { # $1 = STUB_MODE
|
||||
cd "${WORK}" || return 127
|
||||
mkdir -p "${WORK}/OpenRA"
|
||||
env -i PATH="${STUB_DIR}:/usr/bin:/bin" STUB_MODE="$1" \
|
||||
CTEST=ctest CONTROL_TMPDIR="${WORK}/tmp-$1" \
|
||||
PULL_LIBRARY="${WORK}/pull.so" FROZEN_LIBRARY="${WORK}/frozen.so" \
|
||||
bash "${HERE}/retrace_pull_library_control.sh" OpenRA DirectGLES
|
||||
}
|
||||
|
||||
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
|
||||
# The disarmed lane, which is a legitimate exit 0 while c1/s1/v1 are landing.
|
||||
expect PASSED "every split entry skipped (lane not armed)" -- run_split all-skipped
|
||||
|
||||
echo
|
||||
echo "=== the retrace lane's pull-library control (scripts/ci/retrace_pull_library_control.sh)"
|
||||
# A pull-shaped library the nm identity check accepts: a real ELF .so defining no MG_Remote symbol.
|
||||
if command -v cc > /dev/null 2>&1; then
|
||||
printf '%s\n' 'int mobilegl_pull_only(void) { return 1; }' > "${WORK}/pull.c"
|
||||
cc -shared -fPIC -o "${WORK}/pull.so" "${WORK}/pull.c" || { echo "cannot build the stand-in library"; exit 1; }
|
||||
else
|
||||
echo "no cc available; the retrace half of this smoke test needs one" >&2
|
||||
exit 1
|
||||
fi
|
||||
: > "${WORK}/frozen.so"
|
||||
|
||||
# THE FINDING, part (b): a regex matching no tests. --no-tests=error exits non-zero and the old
|
||||
# control read that as "the pull library turned it red".
|
||||
expect FAILED "empty selection (--no-tests=error exit)" -- run_retrace retrace-noselect
|
||||
# A red that never names the transport: a fixture failure, a loader failure, a timeout.
|
||||
expect FAILED "red without the transport-resolution message" -- run_retrace retrace-unrelated
|
||||
# The real thing - and note the stub emits it CMake-wrapped across two lines, which a line-oriented
|
||||
# grep for the literal sentence would miss.
|
||||
expect PASSED "run_trace_case.cmake's own sentence, wrapped" -- run_retrace retrace-evidence
|
||||
# The pull library replaying green is the failure this control exists to catch.
|
||||
expect FAILED "a pull library passed the split retrace" -- run_retrace retrace-green
|
||||
|
||||
echo
|
||||
echo "smoke test: ${passes} passed, ${failures} failed"
|
||||
if [ "${failures}" -gt 0 ]; then
|
||||
echo "CONTROL_SMOKE_TEST_FAILED"
|
||||
exit 1
|
||||
fi
|
||||
echo "CONTROL_SMOKE_TEST_OK"
|
||||
Executable
+49
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Tally a ctest --output-junit file as "passed failed skipped".
|
||||
|
||||
Split out of .github/workflows/test.yml's negative-control step so that the workflow, the local
|
||||
gate and the control smoke test all count a run the same way.
|
||||
|
||||
WHY THIS EXISTS AT ALL (ID-46 finding 8, second half). The counter this replaces lived inline in
|
||||
the workflow and counted a case as having "run" when it was merely not <skipped/>:
|
||||
|
||||
if case.find('skipped') is None and case.get('status') not in ('notrun', 'disabled'):
|
||||
ran += 1
|
||||
|
||||
so a case that RAN AND FAILED armed the negative controls below it. Combined with the `|| true`
|
||||
that hid the baseline's exit code, a lane in which every split entry was already red reported
|
||||
itself armed, and a control that turns an already-red entry red then "passed". Passed, failed and
|
||||
skipped are three different answers and the caller needs all three.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def tally(path):
|
||||
passed = failed = skipped = 0
|
||||
for case in ET.parse(path).getroot().iter('testcase'):
|
||||
if case.find('failure') is not None or case.find('error') is not None:
|
||||
failed += 1
|
||||
elif case.find('skipped') is not None or case.get('status') in ('notrun', 'disabled'):
|
||||
skipped += 1
|
||||
else:
|
||||
passed += 1
|
||||
return passed, failed, skipped
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: junit_tally.py <junit.xml>", file=sys.stderr)
|
||||
return 2
|
||||
try:
|
||||
passed, failed, skipped = tally(sys.argv[1])
|
||||
except Exception as exc: # a malformed file is not "zero of everything"
|
||||
print(f"junit_tally: cannot parse {sys.argv[1]}: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
print(f"{passed} {failed} {skipped}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
Executable
+71
@@ -0,0 +1,71 @@
|
||||
#!/bin/bash
|
||||
# R-16's "I made it red once, by doing X" for scripts/ci/control_smoke_test.sh, mechanised so the
|
||||
# claim can be re-checked rather than believed.
|
||||
#
|
||||
# X = revert the message check in each control, i.e. put the controls back in the state ID-46
|
||||
# finding 8 found them in: a non-zero ctest exit is accepted whatever the failure was.
|
||||
#
|
||||
# The smoke test must then FAIL, and it must fail on the two cases that exist for this defect -
|
||||
# "unrelated failure with a non-empty selection" and "red without the transport-resolution
|
||||
# message" - and not merely somewhere. A smoke test that goes red for any other reason when the
|
||||
# evidence check is removed would not be pinning the evidence check.
|
||||
set -u
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
WORK="$(mktemp -d)"
|
||||
trap 'cp "${WORK}/split.orig" "${HERE}/split_negative_controls.sh"; cp "${WORK}/retrace.orig" "${HERE}/retrace_pull_library_control.sh"; rm -rf "${WORK}"' EXIT
|
||||
|
||||
cp "${HERE}/split_negative_controls.sh" "${WORK}/split.orig"
|
||||
cp "${HERE}/retrace_pull_library_control.sh" "${WORK}/retrace.orig"
|
||||
|
||||
echo "=== baseline: the smoke test must be GREEN before anything is perturbed"
|
||||
if ! bash "${HERE}/control_smoke_test.sh" > "${WORK}/before.log" 2>&1; then
|
||||
echo "the smoke test is ALREADY RED; the red-check below would prove nothing"
|
||||
cat "${WORK}/before.log"
|
||||
exit 1
|
||||
fi
|
||||
tail -1 "${WORK}/before.log"
|
||||
|
||||
echo
|
||||
echo "=== perturbation: remove the evidence check from both controls"
|
||||
python3 - "${HERE}/split_negative_controls.sh" "${HERE}/retrace_pull_library_control.sh" <<'PY'
|
||||
import sys
|
||||
split, retrace = sys.argv[1], sys.argv[2]
|
||||
for path, needle in ((split, 'grep -qE "${evidence}"'), (retrace, 'grep -qF "${EVIDENCE}"')):
|
||||
text = open(path).read()
|
||||
out, hit = [], 0
|
||||
for line in text.splitlines(keepends=True):
|
||||
if needle in line and line.lstrip().startswith('if ! '):
|
||||
indent = line[:len(line) - len(line.lstrip())]
|
||||
out.append(f"{indent}if false; then\n")
|
||||
hit += 1
|
||||
else:
|
||||
out.append(line)
|
||||
if hit != 1:
|
||||
raise SystemExit(f"expected exactly one evidence check in {path}, found {hit}")
|
||||
open(path, 'w').write(''.join(out))
|
||||
print("both evidence checks reverted to 'any non-zero ctest exit is accepted'")
|
||||
PY
|
||||
|
||||
echo
|
||||
echo "=== the smoke test on the reverted controls (it MUST be red, on those two cases)"
|
||||
bash "${HERE}/control_smoke_test.sh" > "${WORK}/after.log" 2>&1
|
||||
rc=$?
|
||||
cat "${WORK}/after.log"
|
||||
|
||||
if [ "${rc}" -eq 0 ]; then
|
||||
echo
|
||||
echo "RED-CHECK FAILED: the controls accept an unrelated failure again and the smoke test still passed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
missed=0
|
||||
grep -q "NOT OK unrelated failure with a non-empty selection" "${WORK}/after.log" || missed=1
|
||||
grep -q "NOT OK red without the transport-resolution message" "${WORK}/after.log" || missed=1
|
||||
if [ "${missed}" -ne 0 ]; then
|
||||
echo
|
||||
echo "RED-CHECK FAILED: the smoke test went red, but not on the two cases the evidence check exists for."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "P5_T1_CONTROL_SMOKE_REDCHECK_OK - removing the evidence check reds exactly the two cases that pin it"
|
||||
@@ -0,0 +1,117 @@
|
||||
#!/bin/bash
|
||||
# THE RETRACE-SPLIT LANE'S NEGATIVE CONTROL: a PULL library must red this split retrace.
|
||||
#
|
||||
# This file is the body of .github/workflows/test.yml's "Negative control - the PULL library must
|
||||
# red this split retrace" step, extracted for the reason given at the top of
|
||||
# scripts/ci/split_negative_controls.sh: a `run:` block is unreviewable and untestable off a
|
||||
# runner, and scripts/ci/control_smoke_test.sh now runs THIS file rather than a hand-made copy.
|
||||
#
|
||||
# WHAT THE CONTROL IS FOR. The retrace-split job replays a trace against the SPLIT runtime under
|
||||
# MOBILEGL_TRANSPORT=inproc. OpenRA scores ssim 1.000000 against a MONOLITH library too - measured -
|
||||
# so the picture is not and cannot be this lane's gate. What stands between the job and a green that
|
||||
# ran monolith end to end is run_trace_case.cmake's transport-resolution assertion
|
||||
# (run_trace_case.cmake:265-289): the library must have logged
|
||||
# "MOBILEGL_TRANSPORT=inproc - the MGPipe record stream", which exists only in
|
||||
# ConfigLoader::InitTransport's InProcess arm, which exists only under MOBILEGL_BUILD_DISAGGREGATED.
|
||||
# This control swaps the pull library over the frozen path and requires the same replay to fail FOR
|
||||
# THAT REASON.
|
||||
#
|
||||
# WHAT THE REVIEW FOUND (ID-46 finding 8 part (b), CONFIRMED by execution against the REAL ctest in
|
||||
# a REAL build tree; ID-48 assigns it here). Two holes, both of which let the control pass while
|
||||
# asserting nothing:
|
||||
#
|
||||
# 1. NO SELECTION GUARD AT ALL - unlike the split lane's run_control, which has had one since
|
||||
# review finding M-4. With a case/backend regex matching no tests, `--no-tests=error` exits 8,
|
||||
# and the old `if [ "${control_rc}" -eq 0 ]` accepted 8 as "the pull library turned it red".
|
||||
# The verifier measured exactly that: "real ctest exit for a regex matching NO tests: 8",
|
||||
# HARNESS_EXIT=0. An empty selection was the one thing this control could not tell apart from
|
||||
# a working transport-identity assertion.
|
||||
# 2. ONLY "non-zero ctest" WAS CHECKED after the nm identity check. The nm check establishes that
|
||||
# the library IS a pull build; it says nothing about why the replay failed. A loader failure, a
|
||||
# missing fixture or a timeout all passed the control.
|
||||
#
|
||||
# Both are closed below: the selection is counted before the run, and the red must carry
|
||||
# run_trace_case.cmake's own words.
|
||||
#
|
||||
# Usage: retrace_pull_library_control.sh <case> <backend>
|
||||
# CTEST ctest binary (default: ctest)
|
||||
# CONTROL_TMPDIR scratch dir (default: ${RUNNER_TEMP:-/tmp})
|
||||
# PULL_LIBRARY the pull libMobileGL.so to swap in
|
||||
# FROZEN_LIBRARY the path every case has baked in, which PULL_LIBRARY is copied over
|
||||
set -u
|
||||
|
||||
CASE="${1:?usage: retrace_pull_library_control.sh <case> <backend>}"
|
||||
BACKEND="${2:?usage: retrace_pull_library_control.sh <case> <backend>}"
|
||||
|
||||
CTEST="${CTEST:-ctest}"
|
||||
CONTROL_TMPDIR="${CONTROL_TMPDIR:-${RUNNER_TEMP:-/tmp}}"
|
||||
PULL_LIBRARY="${PULL_LIBRARY:?PULL_LIBRARY must name the pull build libMobileGL.so}"
|
||||
FROZEN_LIBRARY="${FROZEN_LIBRARY:?FROZEN_LIBRARY must name the path the cases have baked in}"
|
||||
mkdir -p "${CONTROL_TMPDIR}"
|
||||
|
||||
# run_trace_case.cmake's own sentence for "this library never resolved the transport". Anchored on
|
||||
# the distinctive clause rather than on the whole paragraph, which carries substituted paths.
|
||||
EVIDENCE='never reported resolving it'
|
||||
|
||||
selector="^MobileGLTraceReplay\.${CASE}\.${BACKEND}$"
|
||||
|
||||
# The rerun replays into the same case directory, so the good run's images are put aside and
|
||||
# restored whichever way the control goes; "Upload actual image" runs `if: always()` and would
|
||||
# otherwise ship the deliberately-wrong run's output under the good run's name.
|
||||
GOOD_OUTPUT="${CONTROL_TMPDIR}/split-verified-output"
|
||||
rm -rf "${GOOD_OUTPUT}"
|
||||
if [ -d "${CASE}" ]; then cp -a "${CASE}" "${GOOD_OUTPUT}"; fi
|
||||
|
||||
restore_good_output() {
|
||||
if [ -d "${GOOD_OUTPUT}" ]; then
|
||||
rm -rf "${CASE}"; mv "${GOOD_OUTPUT}" "${CASE}"
|
||||
echo "restored the verified run's output over the control's"
|
||||
fi
|
||||
}
|
||||
|
||||
# HOLE 1: COUNT THE SELECTION FIRST. `--no-tests=error` turns an empty selection into a non-zero
|
||||
# exit, which is indistinguishable from a working control unless the selection is counted.
|
||||
matched=$("${CTEST}" -N -R "${selector}" | grep -cE '^ *Test *#[0-9]+:')
|
||||
if [ "${matched}" -lt 1 ]; then
|
||||
restore_good_output
|
||||
echo "::error::the control selected ${matched} tests with -R '${selector}', so there is nothing for the pull library to red. --no-tests=error would have exited non-zero on the empty selection and this control used to read that as success (ID-46 finding 8b, measured: ctest exit 8, step green)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The pull library, unpacked from build-linux's artifact, over the frozen path every case has baked
|
||||
# in. It defines no MG_Remote symbol, so ConfigLoader has no transport parser and
|
||||
# MOBILEGL_TRANSPORT=inproc is accepted and ignored - the exact shape of "the split lane ran
|
||||
# monolith".
|
||||
cp "${PULL_LIBRARY}" "${FROZEN_LIBRARY}"
|
||||
if nm --defined-only "${FROZEN_LIBRARY}" | grep -q -i MG_Remote; then
|
||||
restore_good_output
|
||||
echo "::error::the control's own library defines MG_Remote symbols, so it is not a pull build and this control would prove nothing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
out="${CONTROL_TMPDIR}/retrace-control-output.txt"
|
||||
export MOBILEGL_TRANSPORT=inproc
|
||||
"${CTEST}" -V --no-tests=error --timeout 10800 -R "${selector}" > "${out}" 2>&1
|
||||
control_rc=$?
|
||||
cat "${out}"
|
||||
|
||||
restore_good_output
|
||||
|
||||
if [ "${control_rc}" -eq 0 ]; then
|
||||
echo "::error::a PULL library passed the split retrace. OpenRA scores ssim 1.000000 under a monolith library too (measured), so the picture is not and cannot be this lane's gate - run_trace_case.cmake's transport-resolution assertion is, and it has stopped working. Every green in this job is then a monolith run under a name that says split."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# HOLE 2: THE RED MUST BE THIS CONTROL'S RED.
|
||||
#
|
||||
# Whitespace is normalised across the WHOLE file before the match, newlines included, because the
|
||||
# sentence is emitted by CMake's message(FATAL_ERROR ...) and CMake re-wraps that text to its own
|
||||
# width: "never reported resolving it" arrives split over two lines with a two-space continuation
|
||||
# indent, and a line-oriented grep for the literal finds nothing. That is not hypothetical - it is
|
||||
# the shape the stub reproduces in scripts/ci/testdata/stub_ctest.sh.
|
||||
if ! tr -s '[:space:]' ' ' < "${out}" | grep -qF "${EVIDENCE}"; then
|
||||
echo "::error::the split retrace went red (ctest exit ${control_rc}) with the pull library in place, but the failure never says the library did not resolve the transport - run_trace_case.cmake's \"${EVIDENCE}\" is absent from the output. A loader failure, a missing fixture, a timeout or an SSIM drop all land here, and none of them establishes that the transport-identity assertion is what caught the pull library. Only 'non-zero ctest' used to be checked (ID-46 finding 8b)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "the pull library turned the split retrace red for its own reason (ctest exit ${control_rc}): ${matched} selected case(s) named the transport, not the picture"
|
||||
Executable
+151
@@ -0,0 +1,151 @@
|
||||
#!/bin/bash
|
||||
# EXIT GATE E1's NEGATIVE CONTROL and EXIT GATE E3(a)'s.
|
||||
#
|
||||
# This file is the body of .github/workflows/test.yml's "Negative controls - the verb barrier and
|
||||
# the persistent-map push must be load-bearing" step. It lives in the repository rather than inline
|
||||
# in the workflow for one reason: a workflow `run:` block cannot be executed anywhere except on a
|
||||
# runner, so the logic below was unreviewable and untestable until it ran in CI - and when the
|
||||
# wave-1 cross-family review claimed it was broken, confirming the claim needed a hand-made copy of
|
||||
# these lines with their inputs stubbed (wave1-codex-verify.md 8). A copy is not the thing. The
|
||||
# smoke test at scripts/ci/control_smoke_test.sh now runs THIS file, so the lines CI executes and
|
||||
# the lines the smoke test proves are the same lines.
|
||||
#
|
||||
# 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 (ConfigLoader.cpp:385-393, "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. (It is also unreachable from here: the three
|
||||
# DirectGLES.Split. lanes set no MOBILEGL_LOG_FILE_PATH, and the library's console sink is compiled
|
||||
# out of this configuration, so no MGLOG_ output of any level reaches ctest's transcript. Measured:
|
||||
# ~/w7/p5-v1-joint-isplit-barrier0.log, 18 aborted entries, zero occurrences of the string "Fatal".)
|
||||
#
|
||||
# Usage: split_negative_controls.sh
|
||||
# CTEST ctest binary (default: ctest)
|
||||
# CONTROL_TMPDIR scratch dir for the junit + output (default: ${RUNNER_TEMP:-/tmp})
|
||||
set -u
|
||||
|
||||
CTEST="${CTEST:-ctest}"
|
||||
CONTROL_TMPDIR="${CONTROL_TMPDIR:-${RUNNER_TEMP:-/tmp}}"
|
||||
mkdir -p "${CONTROL_TMPDIR}"
|
||||
|
||||
junit="${CONTROL_TMPDIR}/isplit.xml"
|
||||
|
||||
# ---- 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.
|
||||
"${CTEST}" -L integration-split -j 4 --no-tests=error --output-junit "${junit}"
|
||||
baseline_rc=$?
|
||||
|
||||
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}")
|
||||
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 ]; 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 "::warning::every DirectGLES.Split. entry SKIPPED, so neither negative control can fire. The arming condition is a runtime fact - MG_Config::Transport, ClientSession::Active() and ImplementedVerbCount(), read by Harness/SplitRuntimePeek - and it becomes true on the commit that lands the last of c1/s1/v1. This step becomes a gate then, with no edit; it is not a green that asserted anything today."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ---- the controls ---------------------------------------------------------------------------
|
||||
#
|
||||
# run_control <name> <filter> <evidence regex> <VAR=VALUE>...
|
||||
run_control() {
|
||||
name="$1"; filter="$2"; evidence="$3"; shift 3
|
||||
|
||||
matched=$("${CTEST}" -N -L integration-split -R "${filter}" | 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
|
||||
|
||||
out="${CONTROL_TMPDIR}/control-output.txt"
|
||||
env "$@" "${CTEST}" --output-on-failure -L integration-split -R "${filter}" --no-tests=error > "${out}" 2>&1
|
||||
control_rc=$?
|
||||
cat "${out}"
|
||||
|
||||
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
|
||||
|
||||
# THE HALF THAT WAS MISSING. A non-zero exit is necessary and nowhere near sufficient.
|
||||
# Whitespace is normalised across the whole file first, for the reason given in
|
||||
# retrace_pull_library_control.sh: a diagnostic that arrives wrapped is still the diagnostic.
|
||||
if ! tr -s '[:space:]' ' ' < "${out}" | grep -qE "${evidence}"; then
|
||||
echo "::error::${name} turned ${matched} selected entries red, but the red carries NONE of the diagnostics those scenarios emit when this knob is off, so it is not this control's red. Required one of: ${evidence}. A timeout, a setup abort, a harness that died before it read the knob, or any unrelated assertion lands here - and every one of them used to print the success message below and leave this step green (ID-46 finding 8). If the entries aborted with no output at all, that is the barrier path having no named diagnostic of its own: see t1-v2.md, it is a debt on the server package, not a reason to accept the red."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "${name} turned ${matched} selected entries red, and the red carries the scenario's own diagnostic, as it must"
|
||||
}
|
||||
|
||||
# E1: R-1's lockstep verb barrier. Without it the client keeps pulling fields from a live GLContext
|
||||
# while the server runs ahead, so the server reads future values.
|
||||
#
|
||||
# THE SELECTION INCLUDES THE SmallRing LANE, and that is not cosmetic. Measured on v1's joint tree
|
||||
# (~/w7/p5-v1-joint-isplit-barrier0.log): with the barrier off, every entry the OLD filter selected
|
||||
# aborted with no output whatsoever, and the one entry in the whole run that failed with a readable
|
||||
# assertion - ClearThenReadPixelsScenario.cpp:290/:295, reading back 0 where >200 was cleared - was
|
||||
# a DirectGLES.Split.SmallRing. entry, which the old filter excluded. A control whose selection
|
||||
# contains no case able to say why it failed cannot assert its own failure reason. The SmallRing
|
||||
# entries are the same two scenarios under the same transport with SEG_CMD/SEG_STAGE at their floor,
|
||||
# so including them widens E1's selection strictly within E1's charter.
|
||||
run_control "negative control E1 (MOBILEGL_IPC_VERB_BARRIER=0)" \
|
||||
'DirectGLES\.Split\.(SmallRing\.)?(Triangle|ClearThenReadPixels)' \
|
||||
'ClearThenReadPixelsScenario\.cpp:(290|295)|the bottom band should be red after the resolve|the top band should be blue after the resolve|TriangleScenario\.cpp:[0-9]+: Failure' \
|
||||
MOBILEGL_IPC_VERB_BARRIER=0
|
||||
|
||||
# E3(a): the persistent-map push. 0 is admitted by ConfigLoader on purpose and is documented there
|
||||
# as this control. The evidence is the scenario's own wording for "the second write never arrived":
|
||||
# with the push off, the write that no GL call announces cannot reach its draw, which is exactly
|
||||
# what TwoWritesThroughTheCoherentPointerEachReachTheirOwnDraw and
|
||||
# AWriteAfterAFrameBoundaryReachesTheNextFramesDraw read back
|
||||
# (PersistentCoherentMapScenario.cpp:414-417, :442-443). The counting case's pmap= assertion
|
||||
# (:531-541) is listed too, for the lane where it is the one that runs.
|
||||
run_control "negative control E3(a) (MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0)" \
|
||||
'DirectGLES\.Split\.(SmallRing\.)?PersistentCoherentMapScenario' \
|
||||
"the SECOND write through the same mapping, announced by nothing|frame 1's write through the SAME mapping|cannot have pushed|PersistentCoherentMapScenario\.cpp:[0-9]+: Failure" \
|
||||
MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
#!/bin/bash
|
||||
# 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
|
||||
# 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 (the disarmed lane, a legitimate exit 0)
|
||||
# retrace-noselect `ctest -N` matches nothing; the run exits 8 the way --no-tests=error does
|
||||
# retrace-unrelated one match; the run fails without naming the transport
|
||||
# retrace-evidence one match; the run fails with run_trace_case.cmake's own sentence
|
||||
# retrace-green one match; the run PASSES
|
||||
set -u
|
||||
|
||||
mode="${STUB_MODE:?STUB_MODE must be set}"
|
||||
|
||||
listing=1
|
||||
junit=""
|
||||
prev=""
|
||||
for a in "$@"; do
|
||||
[ "$a" = "-N" ] && listing_requested=1
|
||||
if [ "$prev" = "--output-junit" ]; then junit="$a"; fi
|
||||
prev="$a"
|
||||
done
|
||||
listing_requested="${listing_requested:-0}"
|
||||
|
||||
emit_listing() {
|
||||
echo "Test project /stub"
|
||||
if [ "${mode}" = "retrace-noselect" ]; then
|
||||
echo "Total Tests: 0"
|
||||
return
|
||||
fi
|
||||
echo " Test #1: DirectGLES.Split.ClearThenReadPixelsScenario.ClearWithNoDrawIsVisibleToDefaultFramebufferReadPixels"
|
||||
echo "Total Tests: 1"
|
||||
}
|
||||
|
||||
write_junit() {
|
||||
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"
|
||||
}
|
||||
|
||||
if [ "${listing_requested}" = "1" ]; then
|
||||
emit_listing
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -n "${junit}" ]; then
|
||||
write_junit "${junit}"
|
||||
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
|
||||
|
||||
# The control's own run.
|
||||
case "${mode}" in
|
||||
unrelated)
|
||||
echo "1/1 Test #1: DirectGLES.Split.ClearThenReadPixelsScenario.ClearWithNoDrawIsVisibleToDefaultFramebufferReadPixels ...***Failed"
|
||||
echo "UNRELATED_CONTROL_FAILURE: the harness aborted in setup before the knob was read"
|
||||
exit 8
|
||||
;;
|
||||
evidence)
|
||||
# Both controls' required wording, so one stub serves E1 and E3(a). Copied from the real
|
||||
# diagnostics: ~/w7/p5-v1-joint-isplit-barrier0.log for the first, and
|
||||
# PersistentCoherentMapScenario.cpp:414-417 for the second.
|
||||
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
|
||||
;;
|
||||
retrace-noselect)
|
||||
echo "No tests were found!!!"
|
||||
exit 8
|
||||
;;
|
||||
retrace-unrelated)
|
||||
echo "1/1 Test #1: MobileGLTraceReplay.OpenRA.DirectGLES ...***Failed"
|
||||
echo "CMake Error: the fixture could not be unpacked"
|
||||
exit 8
|
||||
;;
|
||||
retrace-evidence)
|
||||
echo "1/1 Test #1: MobileGLTraceReplay.OpenRA.DirectGLES ...***Failed"
|
||||
echo "CMake Error at run_trace_case.cmake:279 (message):"
|
||||
echo " MOBILEGL_TRANSPORT=inproc is set for OpenRA DirectGLES and the library never"
|
||||
echo " reported resolving it: mobilegl.log carries no"
|
||||
echo ' "MOBILEGL_TRANSPORT=inproc - the MGPipe record stream".'
|
||||
exit 8
|
||||
;;
|
||||
retrace-green)
|
||||
echo "100% tests passed, 0 tests failed out of 1"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "stub_ctest: unknown STUB_MODE '${mode}'" >&2
|
||||
exit 127
|
||||
;;
|
||||
esac
|
||||
+70
-12
@@ -1389,27 +1389,54 @@ def write(path, text, check, changed):
|
||||
handle.write(text)
|
||||
|
||||
|
||||
def expect_trip(name, fn):
|
||||
"""Runs one negative control; a gate that lets it through is the failure."""
|
||||
def expect_trip(name, because, fn, quiet=False):
|
||||
"""Runs one negative control; a gate that lets it through, or trips for a reason that is
|
||||
not ITS OWN, is the failure.
|
||||
|
||||
The first version of this function caught any SystemExit - including an unrelated
|
||||
diagnostic, and even a CLEAN sys.exit(0) - and asked nothing about which one, so the
|
||||
codex cross-family review's finding 9 could replace a control's callback with either and
|
||||
the "nine negative controls all trip" line stayed true while the guard it named never
|
||||
fired. gen_pipe_field_ownership.py's M-1 was the same defect in the sibling generator;
|
||||
this mirrors that fix.
|
||||
|
||||
`because` is a substring the control's OWN message must contain, and an exit code of 0
|
||||
can never count as a trip."""
|
||||
try:
|
||||
fn()
|
||||
except SystemExit as trip:
|
||||
print("gen_pipe: self-test %s: tripped as expected (%s)" % (name, str(trip).splitlines()[0][:100]))
|
||||
return 1
|
||||
print("gen_pipe: self-test %s: DID NOT TRIP" % name, file=sys.stderr)
|
||||
code = trip.code
|
||||
message = str(code) if code is not None else ""
|
||||
if code != 0 and because in message:
|
||||
if not quiet:
|
||||
print("gen_pipe: self-test %s: tripped as expected (%s)"
|
||||
% (name, message.splitlines()[0][:100]))
|
||||
return 1
|
||||
if not quiet:
|
||||
print("gen_pipe: self-test %s: tripped for SOMEONE ELSE'S reason:\n"
|
||||
" expected to contain: %s\n"
|
||||
" actually said (code=%r): %s"
|
||||
% (name, because, code, message.splitlines()[0][:200] if message else "<empty>"),
|
||||
file=sys.stderr)
|
||||
return 0
|
||||
if not quiet:
|
||||
print("gen_pipe: self-test %s: DID NOT TRIP" % name, file=sys.stderr)
|
||||
return 0
|
||||
|
||||
|
||||
def self_test(accessors):
|
||||
"""The negative controls (check_include_closure.py's shape): each gate must go red for
|
||||
its reason, and zero trips is itself an error."""
|
||||
its OWN reason, and zero trips is itself an error."""
|
||||
canned_struct = "struct Canned {\n Uint32 A;\n Uint32 B, C;\n Uint8 Pad0[3];\n void F() { return; }\n};\n"
|
||||
controls = [
|
||||
("struct member without F(...)",
|
||||
"member(s) with no F(...) in PipeFields.def",
|
||||
lambda: check_field_lists_cover_struct_members({"Canned": ["A", "B"]}, ["Canned"], [canned_struct])),
|
||||
("F(...) that is not a member",
|
||||
"F(...) name(s) that are not members",
|
||||
lambda: check_field_lists_cover_struct_members({"Canned": ["A", "B", "C", "D"]}, ["Canned"], [canned_struct])),
|
||||
("payload with no struct",
|
||||
"struct not found",
|
||||
lambda: check_field_lists_cover_struct_members({"Nowhere": ["A"]}, ["Nowhere"], [canned_struct])),
|
||||
]
|
||||
fill_text = read(os.path.join(PIPE_DIR, "FillPoints.def"))
|
||||
@@ -1417,16 +1444,24 @@ def self_test(accessors):
|
||||
field_row = re.compile(r"X\(\s*kDraw\s*,\s*GetBoundVertexArray\s*\)")
|
||||
if not verb_row.search(fill_text) or not field_row.search(fill_text):
|
||||
sys.exit("gen_pipe: self-test: FillPoints.def lost the rows the controls edit")
|
||||
controls.append(("verb missing from FillPoints.def", lambda: parse_fill_points(
|
||||
controls.append(("verb missing from FillPoints.def",
|
||||
"GLFunctionsTable member(s) without a verb row",
|
||||
lambda: parse_fill_points(
|
||||
accessors, text=verb_row.sub("", fill_text, count=1))))
|
||||
controls.append(("verb that is not a GLFunctionsTable member", lambda: parse_fill_points(
|
||||
controls.append(("verb that is not a GLFunctionsTable member",
|
||||
"verb(s) that are not GLFunctionsTable members",
|
||||
lambda: parse_fill_points(
|
||||
accessors, text=verb_row.sub("X(DrawArrays, kDraw) X(NotAVerb, kDraw)", fill_text, count=1))))
|
||||
controls.append(("field row naming a non-accessor", lambda: parse_fill_points(
|
||||
controls.append(("field row naming a non-accessor",
|
||||
"is not an accessor in Coverage.def",
|
||||
lambda: parse_fill_points(
|
||||
accessors, text=field_row.sub("X(kDraw, NotAnAccessor)", fill_text, count=1))))
|
||||
# The EMITTED list's own gate: a row naming a call that is not in PipeCalls.def would
|
||||
# generate an enumerator nothing can dispatch on.
|
||||
calls_for_control = parse_calls()
|
||||
controls.append(("emitted row naming a call that does not exist", lambda: gen_emitted_by(
|
||||
controls.append(("emitted row naming a call that does not exist",
|
||||
"which is not a call in PipeCalls.def",
|
||||
lambda: gen_emitted_by(
|
||||
[("GetViewport", "SetDynamicState")], calls_for_control, [("GetViewport", "NotACall")])))
|
||||
# P5 R-13.4's gate. The flags are now a GENERATED TABLE six packages read instead of six
|
||||
# hard-coded copies, so a token that is not an MGPipeCallFlags enumerator has to stop the
|
||||
@@ -1434,13 +1469,36 @@ def self_test(accessors):
|
||||
# kNone, the empty set, may not be OR'd with a real flag and quietly read as one.
|
||||
flag_typo = Call(1, "Canned", "MGPHandleOnly", "kScreen", ["kHasBlobb"])
|
||||
flag_kNone = Call(1, "Canned", "MGPHandleOnly", "kScreen", ["kNone", "kHasBlob"])
|
||||
flag_typo_because = "which is not an MGPipeCallFlags enumerator"
|
||||
controls.append(("call flag that is not an MGPipeCallFlags enumerator",
|
||||
flag_typo_because,
|
||||
lambda: check_call_flags_are_known([flag_typo])))
|
||||
controls.append(("kNone combined with a real flag",
|
||||
"combines kNone with",
|
||||
lambda: check_call_flags_are_known([flag_kNone])))
|
||||
|
||||
# R-16 meta-control (codex review finding 9; verified by execution in
|
||||
# p5-results/wave1-codex-verify.md §9). The exact perturbation there replaced the
|
||||
# flag-typo control's callback with `sys.exit("unrelated parser failure")` and,
|
||||
# separately, with `sys.exit(0)`; the OLD expect_trip counted both as "tripped as
|
||||
# expected" because it never looked at the message or the code. Both must be REJECTED
|
||||
# here, under the real control's own `because`. This is a quiet, unlisted assertion (not
|
||||
# one of the nine controls below) so it cannot itself inflate the trip count.
|
||||
# "Red once by doing X" = reverting expect_trip to `except SystemExit as trip: return 1`
|
||||
# (accept any SystemExit) - that alone turns each of these two checks from a silent pass
|
||||
# into the sys.exit below.
|
||||
if expect_trip("(R-16 meta-control) unrelated diagnostic standing in for the flag-typo guard",
|
||||
flag_typo_because, lambda: sys.exit("unrelated parser failure"), quiet=True) != 0:
|
||||
sys.exit("gen_pipe: self-test: expect_trip counted an UNRELATED SystemExit as the "
|
||||
"flag-typo guard's own trip - finding 9 / R-16 is back")
|
||||
if expect_trip("(R-16 meta-control) sys.exit(0) standing in for the flag-typo guard",
|
||||
flag_typo_because, lambda: sys.exit(0), quiet=True) != 0:
|
||||
sys.exit("gen_pipe: self-test: expect_trip counted sys.exit(0) as a trip - "
|
||||
"finding 9 / R-16 is back")
|
||||
|
||||
trips = 0
|
||||
for name, fn in controls:
|
||||
trips += expect_trip(name, fn)
|
||||
for name, because, fn in controls:
|
||||
trips += expect_trip(name, because, fn)
|
||||
# The positive control: the canned struct's exact list passes, and the parser sees the
|
||||
# padding member as padding and the function as not a member.
|
||||
check_field_lists_cover_struct_members({"Canned": ["A", "B", "C"]}, ["Canned"], [canned_struct])
|
||||
|
||||
@@ -37,7 +37,20 @@
|
||||
# FlushPendingRangesNow at all, so a gate that hashed only that name protected text the shipping
|
||||
# build never sees, and a tier-threshold edit made in the ladder that DOES ship would pass it. So
|
||||
# both names are hashed. The pull build's ladder is compared against the base ref; the push build's
|
||||
# is compared against a sha pinned at the commit where it was reviewed - see PINNED_FUNCTIONS.
|
||||
# is compared, ALWAYS AND WHETHER OR NOT <ref-a> DEFINES IT, against a sha pinned at the commit
|
||||
# where that body was reviewed - see PINNED_FUNCTIONS.
|
||||
#
|
||||
# "ALWAYS" IS INTEGRATOR DECISION ID-41 AND IT IS A CHANGE. Until P5 this text said "pinned" while
|
||||
# the implementation consulted the pin only as a FALLBACK, when <ref-a> did not define the function
|
||||
# at all. At P3a's own base ref it does not, so the two readings agreed and nobody noticed; from
|
||||
# ff2994d9 onward it does, so the fallback stopped firing and the row silently went back to being
|
||||
# ref-a-vs-ref-b. ID-41 rules that the pin is the baseline, because the pin is the REVIEWED text:
|
||||
# P5 (b1) gave this body two defaulted parameters (hostBaseFrom/hostBaseTo), a
|
||||
# MOBILEGL_PIPE_VERIFY-only StageSnapshotTooNarrow log and a tier-1 access computation moved into
|
||||
# InvalidateFlushAccessFor, the pull arm (FlushPendingRangesNow) is byte-identical across that
|
||||
# change and G1 reports .text +0, so it is the intended change to the eleventh row rather than
|
||||
# drift - and the answer is to RE-PIN it, not to revert it and not to let the row stop being
|
||||
# compared. The OTHER TEN stay ref-a-vs-ref-b: nothing about them moved.
|
||||
#
|
||||
# THE TENTH IS HERE BY INTEGRATOR DECISION ID-11, resolving a contradiction inside the brief.
|
||||
# D-F's "Decision: nine functions" table omits FlushPendingRangesNow, but BRIEF-P3A.md:420 calls it
|
||||
@@ -88,12 +101,22 @@ set -u -o pipefail
|
||||
SOURCE_PATH=MobileGL/MG_Backend/DirectGLES/Managers.cpp
|
||||
# The ten that exist at the P3a base ref, so their baseline is read out of <ref-a>.
|
||||
FUNCTIONS="IsPoolable EnrollIntoPool AcquireFromPool TrimBufferPool ClearBufferPool ProcessDeferredBufferReleases CreateRingStorage RingAvailable RingAllocate FlushPendingRangesNow"
|
||||
# The ELEVENTH (ID-15), and it is a different kind of row: it was BORN in P3a, so there is no
|
||||
# body at the base ref to compare it with and its baseline is PINNED below, captured at
|
||||
# 3e298c9a - the commit at which the two-arm shape was reviewed and accepted.
|
||||
# The ELEVENTH (ID-15), and it is a different kind of row: it was BORN in P3a, so there was no
|
||||
# body at the base ref to compare it with, and its baseline is PINNED below and consulted
|
||||
# UNCONDITIONALLY (ID-41 - see the long note at the top of this file).
|
||||
#
|
||||
# THE PIN, AND WHAT RE-PINS IT. Whoever moves this body deliberately replaces BOTH lines and
|
||||
# writes the decision beside them; a pin with no commit and no decision next to it is a number
|
||||
# nobody can audit.
|
||||
# 3e298c9a 37fc94ff... ID-15, P3a: the two-arm shape, reviewed and accepted
|
||||
# 3dadd4c1 172b0222... ID-41, P5 (b1): [Fix] (DirectGLES): make the extent hostBase is good
|
||||
# for a parameter of the flush ladder, so tier 1's widening refusal is
|
||||
# live code the moment a SEG_STAGE snapshot is narrower than the queued
|
||||
# range. <- CURRENT
|
||||
PINNED_FUNCTIONS="FlushPendingRangesFrom"
|
||||
PINNED_BASELINE_REF=3e298c9a
|
||||
PINNED_SHA_FlushPendingRangesFrom=37fc94ffc5991923d222d585daa3af6511d2352d255623026ce35a3b6963c4a6
|
||||
PINNED_BASELINE_REF=3dadd4c1
|
||||
PINNED_BASELINE_DECISION=ID-41
|
||||
PINNED_SHA_FlushPendingRangesFrom=172b022273db01b16e772d15b269ffcd797fe38c767f7354d83ce113a66040d0
|
||||
ALL_FUNCTIONS="$FUNCTIONS $PINNED_FUNCTIONS"
|
||||
EXPECTED_FUNCTION_COUNT=11
|
||||
# The functions the self-test perturbs, one control each. ClearBufferPool is small, has no forward
|
||||
@@ -247,7 +270,7 @@ def extract(path, names):
|
||||
return rows, problems
|
||||
|
||||
|
||||
def perturb(src, dst, names, target):
|
||||
def perturb(src, dst, names, target, one_token=False):
|
||||
text = open(src, encoding='utf-8', newline='').read()
|
||||
masked = mask(text)
|
||||
hits = find_definition(text, masked, target)
|
||||
@@ -260,9 +283,17 @@ def perturb(src, dst, names, target):
|
||||
# perturbation somewhere that is not the body, and the control would be proving the wrong
|
||||
# thing. Offsets are identical between the two by construction (mask() preserves length).
|
||||
brace = masked.index('{', begin)
|
||||
patched = (text[:brace + 1] +
|
||||
'\n // p3a_untouched_regions.sh --self-test: a body that MOVED.\n' +
|
||||
text[brace + 1:])
|
||||
if one_token:
|
||||
# ONE TOKEN - a single empty statement - and nothing else. ID-41(d) asks the pinned row's
|
||||
# control to perturb the LADDER rather than a comment beside it, and this is the smallest
|
||||
# edit that is unambiguously code: a reader cannot answer "the gate only notices comments".
|
||||
# The perturbed copy is never compiled, only hashed, so an empty statement is legal here in
|
||||
# a way it would not be in the tree.
|
||||
patched = text[:brace + 1] + ';' + text[brace + 1:]
|
||||
else:
|
||||
patched = (text[:brace + 1] +
|
||||
'\n // p3a_untouched_regions.sh --self-test: a body that MOVED.\n' +
|
||||
text[brace + 1:])
|
||||
open(dst, 'w', encoding='utf-8', newline='').write(patched)
|
||||
return 0
|
||||
|
||||
@@ -282,8 +313,8 @@ def main(argv):
|
||||
for sha, name in rows:
|
||||
sys.stdout.write('%s %s\n' % (sha, name))
|
||||
return 2 if problems else 0
|
||||
if mode == 'perturb':
|
||||
return perturb(argv[2], argv[3], names, argv[4])
|
||||
if mode in ('perturb', 'perturb-token'):
|
||||
return perturb(argv[2], argv[3], names, argv[4], mode == 'perturb-token')
|
||||
sys.stderr.write('[p3a-untouched] unknown mode %r\n' % mode)
|
||||
return 2
|
||||
|
||||
@@ -304,38 +335,74 @@ extract_ref() {
|
||||
return $?
|
||||
}
|
||||
|
||||
# The BASELINE side (<ref-a>). Same extraction, with one difference that the eleventh row makes
|
||||
# necessary: a function born in P3a has no body at the P3a base ref, and CI passes exactly that
|
||||
# ref as <ref-a>. Asking for it there is not "the gate could not run" - it is the expected
|
||||
# answer - so a name in PINNED_FUNCTIONS that is missing at <ref-a> takes the sha pinned at the
|
||||
# top of this script instead. Everything else still has to be found: the fallback is entered
|
||||
# only after a strict extraction failed, and it then re-extracts the pre-P3a ten strictly, so a
|
||||
# genuine rename of one of THOSE is still exit 2 rather than a silently short list.
|
||||
# True when $1 is one of the rows whose baseline is the PIN rather than <ref-a>.
|
||||
is_pinned_row() {
|
||||
local name candidate
|
||||
for candidate in $PINNED_FUNCTIONS; do
|
||||
[ "$candidate" = "$1" ] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Overwrite the pinned rows of a `<sha> <name>` list with the shas PINNED at the top of this
|
||||
# script. ONE spelling of the substitution, called both by extract_baseline and by --self-test's
|
||||
# pin controls, so the control drives the gate's own path instead of re-spelling it - a control
|
||||
# that re-implements what it checks proves only that the copy agrees with itself.
|
||||
apply_pinned_shas() {
|
||||
local file=$1 name pinned
|
||||
for name in $PINNED_FUNCTIONS; do
|
||||
eval "pinned=\$PINNED_SHA_$name"
|
||||
if [ -z "$pinned" ] || [ "$pinned" = "PLACEHOLDER_SHA" ]; then
|
||||
say "$name has no pinned baseline sha; the eleventh row cannot be compared"
|
||||
return 2
|
||||
fi
|
||||
grep -v " $name\$" "$file" > "$file.unpinned" || true
|
||||
mv -f "$file.unpinned" "$file" || return 2
|
||||
# Appended LAST, which is also its position in the fixed order (it is the eleventh of eleven),
|
||||
# so the header's "stdout is always the sha list in the fixed order" stays true and a baseline
|
||||
# captured by redirect still diffs cleanly against a two-ref run.
|
||||
printf '%s %s\n' "$pinned" "$name" >> "$file"
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
# The BASELINE side (<ref-a>). The TEN are extracted strictly, so a rename of one of THOSE is
|
||||
# exit 2 rather than a silently short list. FlushPendingRangesFrom then takes the PINNED sha
|
||||
# WHETHER OR NOT <ref-a> defines it (ID-41), and a <ref-a> that defines it DIFFERENTLY is
|
||||
# reported - loudly - because the two answers disagreeing is itself a finding rather than a
|
||||
# reason to prefer the ref.
|
||||
extract_baseline() {
|
||||
local ref=$1 out=$2 blob="$WORK_DIR/$2.cpp" name sha
|
||||
local ref=$1 out=$2 blob="$WORK_DIR/$2.cpp" name pinned atRef
|
||||
if ! git show "$ref:$SOURCE_PATH" > "$blob" 2>"$WORK_DIR/show.err"; then
|
||||
say "cannot read $SOURCE_PATH at '$ref':"
|
||||
sed 's/^/[p3a-untouched] /' "$WORK_DIR/show.err" >&2
|
||||
return 2
|
||||
fi
|
||||
if python3 "$PY" extract "$blob" "$ALL_FUNCTIONS" > "$WORK_DIR/$out.sha" 2>"$WORK_DIR/$out.err"; then
|
||||
return 0
|
||||
fi
|
||||
if ! python3 "$PY" extract "$blob" "$FUNCTIONS" > "$WORK_DIR/$out.sha"; then
|
||||
if ! python3 "$PY" extract "$blob" "$FUNCTIONS" > "$WORK_DIR/$out.sha" 2>"$WORK_DIR/$out.err"; then
|
||||
say "the baseline ref '$ref' does not define the pre-P3a ten exactly once each:"
|
||||
sed 's/^/[p3a-untouched] /' "$WORK_DIR/$out.err" >&2
|
||||
return 2
|
||||
fi
|
||||
for name in $PINNED_FUNCTIONS; do
|
||||
eval "sha=\$PINNED_SHA_$name"
|
||||
if [ -z "$sha" ] || [ "$sha" = "PLACEHOLDER_SHA" ]; then
|
||||
say "$name has no pinned baseline sha; the eleventh row cannot be compared"
|
||||
return 2
|
||||
eval "pinned=\$PINNED_SHA_$name"
|
||||
atRef=$(python3 "$PY" extract "$blob" "$name" 2>/dev/null | awk -v n="$name" '$2 == n { print $1 }')
|
||||
if [ -z "$atRef" ]; then
|
||||
say "$name is not defined at '$ref' (it was born in P3a): its baseline is the sha PINNED in"
|
||||
say " this script, captured at $PINNED_BASELINE_REF ($PINNED_BASELINE_DECISION)"
|
||||
elif [ "$atRef" != "$pinned" ]; then
|
||||
say "NOTE: $name IS defined at '$ref' and hashes"
|
||||
say " $atRef, which is not the pin"
|
||||
say " ($pinned,"
|
||||
say " captured at $PINNED_BASELINE_REF, $PINNED_BASELINE_DECISION). THE PIN IS WHAT IS"
|
||||
say " COMPARED - it is the reviewed body - and this note is not a verdict in either"
|
||||
say " direction. If '$ref' PREDATES $PINNED_BASELINE_REF the two SHOULD disagree: the pinned"
|
||||
say " body is the change $PINNED_BASELINE_DECISION admitted, which is why it was re-pinned"
|
||||
say " rather than reverted. If it does not predate it, the ladder that ships has moved away"
|
||||
say " from the reviewed text without this gate being re-pinned - re-pin deliberately or"
|
||||
say " revert, but do not leave them disagreeing."
|
||||
fi
|
||||
printf '%s %s\n' "$sha" "$name" >> "$WORK_DIR/$out.sha"
|
||||
say "$name is not defined at '$ref' (it was born in P3a): its baseline is the sha PINNED in"
|
||||
say " this script, captured at $PINNED_BASELINE_REF"
|
||||
done
|
||||
apply_pinned_shas "$WORK_DIR/$out.sha" || return 2
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -350,6 +417,16 @@ compare_lists() {
|
||||
say "FIRST FUNCTION THAT MOVED: $name"
|
||||
say " $labelA $shaA"
|
||||
say " $labelB ${shaB:-<not found>}"
|
||||
if is_pinned_row "$name"; then
|
||||
# ITS OWN MESSAGE, and that is R-16 rather than decoration: the pinned row and the ten
|
||||
# ref-a rows fail differently and are fixed differently, so a reader who sees only the
|
||||
# generic paragraph below goes looking for a diff against <ref-a> that does not exist.
|
||||
say " $name IS A PINNED ROW ($PINNED_BASELINE_DECISION): its baseline is ALWAYS the sha"
|
||||
say " PINNED in this script - the body reviewed at $PINNED_BASELINE_REF - and never the"
|
||||
say " body at '$labelA'. So this is not a diff against the base ref: the ladder that"
|
||||
say " SHIPS has moved away from the text that was reviewed. Either re-pin deliberately,"
|
||||
say " replacing the sha AND the commit AND the decision beside it, or revert the body."
|
||||
fi
|
||||
say " G5 (ARCHITECTURE.md:316, :515) says the buffer pool, the deferred-release drain, the"
|
||||
say " three rings and BOTH arms of the three-tier flush drain - FlushPendingRangesNow in"
|
||||
say " the pull build, FlushPendingRangesFrom in the push build (BRIEF-P3A.md:420, :1708,"
|
||||
@@ -436,6 +513,81 @@ if [ "${1:-}" = "--self-test" ]; then
|
||||
fi
|
||||
say "negative control: a perturbed $target body is reported, and named"
|
||||
done
|
||||
|
||||
# --- THE PINNED ROW (ID-41) -----------------------------------------------------------------
|
||||
# TWO more controls, and they exist because none of the five above can see the pin at all: every
|
||||
# one of them compares one extraction of the working tree against another, so they would all be
|
||||
# green on a build of this script in which PINNED_SHA_* was never read by anything. The eleventh
|
||||
# row's whole claim is "the baseline is the PIN, not <ref-a>", and that claim needs its own two.
|
||||
for target in $PINNED_FUNCTIONS; do
|
||||
eval "pinned=\$PINNED_SHA_$target"
|
||||
|
||||
# (1) PIN PRECEDENCE, positive. A baseline that carries some OTHER sha for the pinned row -
|
||||
# which is the shape of every <ref-a> CI passes today, since ff2994d9 and 37da3c3a both DEFINE
|
||||
# FlushPendingRangesFrom - must come out of apply_pinned_shas carrying the PIN. This is the
|
||||
# control that would have caught the ID-41 defect itself: before it, the pin was consulted
|
||||
# only when <ref-a> lacked the function, so the row silently reverted to ref-a-vs-ref-b the
|
||||
# moment a base ref had one.
|
||||
grep -v " $target\$" "$WORK_DIR/pristine.sha" > "$WORK_DIR/pinprec.sha" || true
|
||||
printf '%s %s\n' \
|
||||
"0000000000000000000000000000000000000000000000000000000000000000" "$target" \
|
||||
>> "$WORK_DIR/pinprec.sha"
|
||||
apply_pinned_shas "$WORK_DIR/pinprec.sha" || exit 2
|
||||
got=$(awk -v n="$target" '$2 == n { print $1 }' "$WORK_DIR/pinprec.sha")
|
||||
if [ "$got" != "$pinned" ]; then
|
||||
say "PIN CONTROL FAILED: a baseline that carried a DIFFERENT sha for $target came out as"
|
||||
say " '${got:-<absent>}' and not as the pin ($pinned). The eleventh row would be compared"
|
||||
say " against <ref-a> again, which is exactly the defect $PINNED_BASELINE_DECISION closed."
|
||||
exit 2
|
||||
fi
|
||||
say "pin control: a baseline that defines $target differently is overridden by the PIN"
|
||||
|
||||
# (2) A ONE-TOKEN EDIT TO THE PINNED LADDER, negative, AGAINST THE PIN. The comparison must go
|
||||
# red, must name the row, and must say that the row is PINNED - R-16's "a control asserts its
|
||||
# OWN failure string": the pinned row and the ten ref-a rows are fixed differently, and a
|
||||
# reader who gets only the generic paragraph goes looking for a diff against <ref-a> that does
|
||||
# not exist.
|
||||
python3 "$PY" perturb-token "$WORK_DIR/pristine.cpp" "$WORK_DIR/pinperturbed.cpp" \
|
||||
"$target" "$ALL_FUNCTIONS" || exit 2
|
||||
python3 "$PY" extract "$WORK_DIR/pinperturbed.cpp" "$ALL_FUNCTIONS" \
|
||||
> "$WORK_DIR/pinperturbed.sha" || exit 2
|
||||
cp -f "$WORK_DIR/pristine.sha" "$WORK_DIR/pinbase.sha" || exit 2
|
||||
apply_pinned_shas "$WORK_DIR/pinbase.sha" || exit 2
|
||||
if compare_lists "$WORK_DIR/pinbase.sha" "$WORK_DIR/pinperturbed.sha" \
|
||||
"PIN($PINNED_BASELINE_REF)" "one-token-perturbed" 2> "$WORK_DIR/pinperturbed.err"; then
|
||||
say "NEGATIVE CONTROL DID NOT TRIP: one token was inserted into $target's body and the"
|
||||
say "comparison AGAINST THE PIN still reported every function as identical. The pinned row is"
|
||||
say "not being compared at all, so every green this gate has printed for it means nothing."
|
||||
exit 2
|
||||
fi
|
||||
if ! grep -q "FIRST FUNCTION THAT MOVED: $target" "$WORK_DIR/pinperturbed.err"; then
|
||||
say "NEGATIVE CONTROL TRIPPED FOR THE WRONG REASON: the comparison against the pin went red"
|
||||
say "but did not name $target as the first function that moved. It said:"
|
||||
sed 's/^/[p3a-untouched] /' "$WORK_DIR/pinperturbed.err" >&2
|
||||
exit 2
|
||||
fi
|
||||
if ! grep -q "$target IS A PINNED ROW" "$WORK_DIR/pinperturbed.err"; then
|
||||
say "NEGATIVE CONTROL TRIPPED FOR THE WRONG REASON: it went red and named $target, but did"
|
||||
say "not say that this row's baseline is the PIN. That is the half a reader acts on, and a"
|
||||
say "control that does not assert its own message is not a control (R-16). It said:"
|
||||
sed 's/^/[p3a-untouched] /' "$WORK_DIR/pinperturbed.err" >&2
|
||||
exit 2
|
||||
fi
|
||||
say "negative control: a ONE-TOKEN edit to the pinned $target body goes red AGAINST THE PIN,"
|
||||
say " is named, and says the row is pinned"
|
||||
|
||||
# NOT a failure, deliberately: --self-test is about whether the comparison works, and it runs
|
||||
# on the WORKING tree, which may legitimately carry an uncommitted edit. The two-ref gate is
|
||||
# what fails when the committed ladder has left the pin. But say so, because a self-test that
|
||||
# was green on a tree whose ladder no longer matches its pin is confusing in exactly one
|
||||
# direction.
|
||||
treeSha=$(awk -v n="$target" '$2 == n { print $1 }' "$WORK_DIR/pristine.sha")
|
||||
if [ "$treeSha" != "$pinned" ]; then
|
||||
say "NOTE: this working tree's $target hashes $treeSha, not the pin ($pinned). The"
|
||||
say " self-test's verdict is unaffected; the two-ref gate will be RED until you re-pin or"
|
||||
say " revert."
|
||||
fi
|
||||
done
|
||||
say "self-test passed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -110,12 +110,21 @@
|
||||
# next reader of this file meets them:
|
||||
# D-N/1 the namespace region kind, above.
|
||||
# D-N/2 the PINNED baseline is CONSULTED UNCONDITIONALLY for FlushPendingRangesFrom, where the
|
||||
# parent consults it only when <ref-a> does not define the function. D-N says that row is
|
||||
# compared "against its pinned 3e298c9a sha", and at P4a's base ref the function DOES
|
||||
# exist - so the parent's fallback would silently never fire and the pin would stop being
|
||||
# the baseline the brief names. Both readings agree on this tree (measured: the body at
|
||||
# 37da3c3a hashes to the pinned value); where they would ever disagree, this script says
|
||||
# so on stderr and keeps the PIN, because the pin is the reviewed text.
|
||||
# parent consulted it only when <ref-a> did not define the function. D-N says that row is
|
||||
# compared "against its pinned sha", and at P4a's base ref the function DOES exist - so
|
||||
# the parent's fallback would silently never fire and the pin would stop being the
|
||||
# baseline the brief names. INTEGRATOR DECISION ID-41 has since made this the parent's
|
||||
# reading too, so D-N/2 is no longer a deviation between the two scripts; it is kept here
|
||||
# as the record of why this one got there first.
|
||||
#
|
||||
# The two answers now DISAGREE on every ref CI passes, and that is the expected state
|
||||
# rather than a finding: P5 (b1) re-parameterised the shipping ladder
|
||||
# (hostBaseFrom/hostBaseTo, a MOBILEGL_PIPE_VERIFY-only StageSnapshotTooNarrow log, the
|
||||
# tier-1 access computation moved into InvalidateFlushAccessFor), the PULL arm is
|
||||
# byte-identical across that change and G1 reports .text +0, and ID-41 ruled that the row
|
||||
# be RE-PINNED on the reviewed P5 body rather than reverted or quietly left comparing
|
||||
# against <ref-a>. This script says so on stderr, in both directions, and keeps the PIN -
|
||||
# because the pin is the reviewed text.
|
||||
set -u -o pipefail
|
||||
|
||||
# One row per region: <name>@<kind>@<path>. The ORDER is the fixed order the sha list is printed
|
||||
@@ -143,11 +152,20 @@ ShouldUseCaveatTextureFormat@function@MobileGL/MG_Backend/DirectGLES/Utils.cpp"
|
||||
EXPECTED_FUNCTION_COUNT=17
|
||||
|
||||
# The one region born in P3a, so there is no body at P4a's base ref that this phase reviewed: its
|
||||
# baseline is the sha captured at 3e298c9a, the commit at which the two-arm shape was reviewed and
|
||||
# accepted (ID-15). See DEVIATIONS D-N/2 for why it is consulted unconditionally.
|
||||
# baseline is PINNED and consulted UNCONDITIONALLY (DEVIATIONS D-N/2, and ID-41 for the parent).
|
||||
#
|
||||
# THE PIN, AND WHAT RE-PINS IT. Whoever moves this body deliberately replaces BOTH lines and
|
||||
# writes the decision beside them; a pin with no commit and no decision next to it is a number
|
||||
# nobody can audit. The parent script carries the identical table and the two must not drift.
|
||||
# 3e298c9a 37fc94ff... ID-15, P3a: the two-arm shape, reviewed and accepted
|
||||
# 3dadd4c1 172b0222... ID-41, P5 (b1): [Fix] (DirectGLES): make the extent hostBase is good
|
||||
# for a parameter of the flush ladder, so tier 1's widening refusal is
|
||||
# live code the moment a SEG_STAGE snapshot is narrower than the queued
|
||||
# range. <- CURRENT
|
||||
PINNED_FUNCTIONS="FlushPendingRangesFrom"
|
||||
PINNED_BASELINE_REF=3e298c9a
|
||||
PINNED_SHA_FlushPendingRangesFrom=37fc94ffc5991923d222d585daa3af6511d2352d255623026ce35a3b6963c4a6
|
||||
PINNED_BASELINE_REF=3dadd4c1
|
||||
PINNED_BASELINE_DECISION=ID-41
|
||||
PINNED_SHA_FlushPendingRangesFrom=172b022273db01b16e772d15b269ffcd797fe38c767f7354d83ce113a66040d0
|
||||
|
||||
# The regions the self-test perturbs, one negative control each. FOUR, exactly as D-N requires, and
|
||||
# each is a different shape so that a control which only ever perturbed the easy one cannot leave
|
||||
@@ -372,7 +390,7 @@ def extract(rows):
|
||||
|
||||
|
||||
def perturb(rows, target, src, dst, where='head'):
|
||||
"""Insert one line into a region's body, at its HEAD or at its TAIL.
|
||||
"""Insert one line into a region's body at its HEAD or its TAIL, or one TOKEN at its head.
|
||||
|
||||
TWO POSITIONS, AND THE SECOND ONE IS REVIEW FINDING F-m2. Every control used to insert at the
|
||||
very first byte after the opening brace, so all four of them would still have tripped if
|
||||
@@ -393,7 +411,15 @@ def perturb(rows, target, src, dst, where='head'):
|
||||
% (target, len(hits)))
|
||||
return 2
|
||||
begin, end = hits[0]
|
||||
if where == 'tail':
|
||||
if where == 'token':
|
||||
# ONE TOKEN - a single empty statement at the head of the body - and nothing else.
|
||||
# ID-41(d) asks the pinned row's control to perturb the LADDER rather than a comment
|
||||
# beside it, and this is the smallest edit that is unambiguously code: a reader cannot
|
||||
# answer "the gate only notices comments". The perturbed copy is never compiled, only
|
||||
# hashed, so an empty statement is legal here in a way it would not be in the tree.
|
||||
brace = masked.index('{', begin)
|
||||
patched = text[:brace + 1] + ';' + text[brace + 1:]
|
||||
elif where == 'tail':
|
||||
# end is one PAST the closing brace (find_function / find_namespace both return
|
||||
# `match_forward(...) + 1`), so end - 1 is the brace itself and this lands inside the
|
||||
# body, one character before it ends.
|
||||
@@ -495,31 +521,59 @@ extract_baseline() {
|
||||
sed 's/^/[p4a-untouched] /' "$WORK_DIR/$out.err" >&2
|
||||
return 2
|
||||
fi
|
||||
for name in $PINNED_FUNCTIONS; do
|
||||
eval "pinned=\$PINNED_SHA_$name"
|
||||
grep "^$name$(printf '\t')" "$WORK_DIR/$out.spec.all" > "$WORK_DIR/$out.spec.pinned" || true
|
||||
atRef=$(python3 "$PY" extract "$WORK_DIR/$out.spec.pinned" 2>/dev/null | awk '{ print $1 }')
|
||||
if [ -n "$atRef" ] && [ "$atRef" != "$pinned" ]; then
|
||||
say "NOTE: $name IS defined at '$ref' and hashes"
|
||||
say " $atRef, which is not the pin"
|
||||
say " ($pinned,"
|
||||
say " captured at $PINNED_BASELINE_REF, $PINNED_BASELINE_DECISION). THE PIN IS WHAT IS"
|
||||
say " COMPARED - it is the reviewed body - and this note is not a verdict in either"
|
||||
say " direction. If '$ref' PREDATES $PINNED_BASELINE_REF the two SHOULD disagree: the pinned"
|
||||
say " body is the change $PINNED_BASELINE_DECISION admitted, which is why it was re-pinned"
|
||||
say " rather than reverted. If it does not predate it, the ladder that ships has moved away"
|
||||
say " from the reviewed text without this gate being re-pinned - re-pin deliberately or"
|
||||
say " revert, but do not leave them disagreeing."
|
||||
fi
|
||||
done
|
||||
# The pinned rows are appended and the list is put back into the FIXED ORDER, both inside
|
||||
# apply_pinned_shas - ONE spelling of the substitution, which --self-test's pin controls drive
|
||||
# as well, so a control cannot prove only that a copy of the logic agrees with itself.
|
||||
apply_pinned_shas "$WORK_DIR/$out.sha" || return 2
|
||||
return 0
|
||||
}
|
||||
|
||||
# True when $1 is one of the rows whose baseline is the PIN rather than <ref-a>.
|
||||
is_pinned_row() {
|
||||
local name candidate
|
||||
for candidate in $PINNED_FUNCTIONS; do
|
||||
[ "$candidate" = "$1" ] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Overwrite the pinned rows of a `<sha> <region>` list with the shas PINNED at the top of this
|
||||
# script, then restore the FIXED ORDER (review F-m1: the pinned rows would otherwise be emitted
|
||||
# LAST while extract_ref emits everything in REGIONS order. The gate itself never noticed -
|
||||
# compare_lists looks rows up by name - but the documented capture workflow did: the header
|
||||
# promises "stdout is always the sha list ... in the fixed order above - so a baseline capture is a
|
||||
# plain redirect", and a baseline captured that way then diffed against a two-ref stdout showed
|
||||
# seven spurious differences purely from row order).
|
||||
apply_pinned_shas() {
|
||||
local file=$1 name pinned
|
||||
for name in $PINNED_FUNCTIONS; do
|
||||
eval "pinned=\$PINNED_SHA_$name"
|
||||
if [ -z "$pinned" ] || [ "$pinned" = "PLACEHOLDER_SHA" ]; then
|
||||
say "$name has no pinned baseline sha; that row cannot be compared"
|
||||
return 2
|
||||
fi
|
||||
grep "^$name$(printf '\t')" "$WORK_DIR/$out.spec.all" > "$WORK_DIR/$out.spec.pinned" || true
|
||||
atRef=$(python3 "$PY" extract "$WORK_DIR/$out.spec.pinned" 2>/dev/null | awk '{ print $1 }')
|
||||
if [ -n "$atRef" ] && [ "$atRef" != "$pinned" ]; then
|
||||
say "NOTE: $name IS defined at '$ref' and hashes $atRef, which is NOT the sha pinned in this"
|
||||
say " script ($pinned, captured at $PINNED_BASELINE_REF). The PIN is what is compared - it is"
|
||||
say " the reviewed text (ID-15) - but the two disagreeing means the push ladder moved between"
|
||||
say " $PINNED_BASELINE_REF and '$ref' without this gate being re-pinned. Re-pin deliberately or"
|
||||
say " revert; do not leave them disagreeing."
|
||||
fi
|
||||
printf '%s %s\n' "$pinned" "$name" >> "$WORK_DIR/$out.sha"
|
||||
grep -v " $name\$" "$file" > "$file.unpinned" || true
|
||||
mv -f "$file.unpinned" "$file" || return 2
|
||||
printf '%s %s\n' "$pinned" "$name" >> "$file"
|
||||
done
|
||||
# ...and put the list back into the FIXED ORDER (review F-m1). The pinned rows were stripped out
|
||||
# of the spec above and appended here, so without this the baseline side emits them LAST while
|
||||
# extract_ref emits everything in REGIONS order. The gate itself never noticed - compare_lists
|
||||
# looks rows up by name - but the documented capture workflow did: the header promises "stdout is
|
||||
# always the sha list ... in the fixed order above - so a baseline capture is a plain redirect",
|
||||
# and a baseline captured that way then diffed against a two-ref stdout showed seven spurious
|
||||
# differences purely from row order.
|
||||
reorder_sha_list "$WORK_DIR/$out.sha" || return 2
|
||||
reorder_sha_list "$file" || return 2
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -553,6 +607,18 @@ compare_lists() {
|
||||
say "FIRST REGION THAT MOVED: $name"
|
||||
say " $labelA ${shaA:-<not found>}"
|
||||
say " $labelB ${shaB:-<not found>}"
|
||||
if is_pinned_row "$name"; then
|
||||
# ITS OWN MESSAGE, and that is R-16 rather than decoration: the pinned row and the
|
||||
# sixteen ref-a rows fail differently and are fixed differently, so a reader who sees
|
||||
# only the generic paragraph below goes looking for a diff against <ref-a> that does not
|
||||
# exist.
|
||||
say " $name IS A PINNED ROW ($PINNED_BASELINE_DECISION): its baseline is ALWAYS the sha"
|
||||
say " PINNED in this script - the body reviewed at $PINNED_BASELINE_REF - and never the"
|
||||
say " body at '$labelA'. So this is not a diff against the base ref: the ladder that"
|
||||
say " SHIPS has moved away from the text that was reviewed. Either re-pin deliberately,"
|
||||
say " replacing the sha AND the commit AND the decision beside it in BOTH this script"
|
||||
say " and its parent scripts/p3a_untouched_regions.sh, or revert the body."
|
||||
fi
|
||||
say " G5 (ARCHITECTURE.md:318, :321, :515) says the Espryt do-not-touch list is literal:"
|
||||
say " P3a's buffer pool, deferred-release drain, three rings and BOTH arms of the three-tier"
|
||||
say " flush drain, plus P4a's unpack-PBO staging repack and its two ring helpers, the"
|
||||
@@ -573,10 +639,12 @@ compare_lists() {
|
||||
# --- self-test ------------------------------------------------------------------------------
|
||||
# A gate that always says "identical" and a gate that is working produce the same green, so the
|
||||
# comparison has to be shown failing. Both controls run: the POSITIVE ones (an untouched copy
|
||||
# compares equal; an edit OUTSIDE the regions is invisible) rule out a comparison that reports
|
||||
# every region as moved, and the eight NEGATIVE ones - D-N's four regions, each perturbed at the
|
||||
# HEAD of its body and again at its TAIL - rule out both the comparison that never reports any and
|
||||
# the extraction whose extent stops before the closing brace (F-m2).
|
||||
# compares equal; an edit OUTSIDE the regions is invisible; a baseline that names another sha for
|
||||
# the PINNED row is overridden by the pin) rule out a comparison that reports every region as
|
||||
# moved, and the NINE NEGATIVE ones - D-N's four regions, each perturbed at the HEAD of its body
|
||||
# and again at its TAIL, plus a ONE-TOKEN edit to the pinned ladder compared AGAINST THE PIN
|
||||
# (ID-41) - rule out the comparison that never reports any, the extraction whose extent stops
|
||||
# before the closing brace (F-m2), and a pin that nothing consults.
|
||||
if [ "${1:-}" = "--self-test" ]; then
|
||||
[ $# -eq 1 ] || { say "--self-test takes no other arguments"; exit 2; }
|
||||
mkdir -p "$WORK_DIR/pristine" || exit 2
|
||||
@@ -678,6 +746,85 @@ if [ "${1:-}" = "--self-test" ]; then
|
||||
say "its tail), ran $controls"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# --- THE PINNED ROW (ID-41) -----------------------------------------------------------------
|
||||
# TWO more controls, and they exist because none of the ten above can see the pin at all: every
|
||||
# one of them compares one extraction of the working tree against another, so they would all be
|
||||
# green on a build of this script in which PINNED_SHA_* was never read by anything. The pinned
|
||||
# row's whole claim is "the baseline is the PIN, not <ref-a>" (D-N/2, ID-41), and that claim
|
||||
# needs its own two.
|
||||
for target in $PINNED_FUNCTIONS; do
|
||||
eval "pinned=\$PINNED_SHA_$target"
|
||||
targetSource=$(printf '%s\n' "$REGIONS" | awk -F@ -v n="$target" '$1 == n { print $3 }')
|
||||
[ -n "$targetSource" ] || { say "$target is not one of the regions"; exit 2; }
|
||||
|
||||
# (1) PIN PRECEDENCE, positive. A baseline that carries some OTHER sha for the pinned row -
|
||||
# which is the shape of every <ref-a> CI passes, since 37da3c3a DOES define
|
||||
# FlushPendingRangesFrom and no longer hashes the pin - must come out of apply_pinned_shas
|
||||
# carrying the PIN.
|
||||
grep -v " $target\$" "$WORK_DIR/pristine.sha" > "$WORK_DIR/pinprec.sha" || true
|
||||
printf '%s %s\n' \
|
||||
"0000000000000000000000000000000000000000000000000000000000000000" "$target" \
|
||||
>> "$WORK_DIR/pinprec.sha"
|
||||
apply_pinned_shas "$WORK_DIR/pinprec.sha" || exit 2
|
||||
got=$(awk -v n="$target" '$2 == n { print $1 }' "$WORK_DIR/pinprec.sha")
|
||||
if [ "$got" != "$pinned" ]; then
|
||||
say "PIN CONTROL FAILED: a baseline that carried a DIFFERENT sha for $target came out as"
|
||||
say " '${got:-<absent>}' and not as the pin ($pinned). That row would be compared against"
|
||||
say " <ref-a> again, which is exactly what D-N/2 and $PINNED_BASELINE_DECISION forbid."
|
||||
exit 2
|
||||
fi
|
||||
say "pin control: a baseline that defines $target differently is overridden by the PIN"
|
||||
|
||||
# (2) A ONE-TOKEN EDIT TO THE PINNED LADDER, negative, AGAINST THE PIN. The comparison must go
|
||||
# red, must name the region, and must say the region is PINNED - R-16's "a control asserts its
|
||||
# OWN failure string": the pinned row and the sixteen ref-a rows are fixed differently, and a
|
||||
# reader who gets only the generic paragraph goes looking for a diff against <ref-a> that does
|
||||
# not exist.
|
||||
rm -rf "$WORK_DIR/pinperturbed"
|
||||
cp -r "$WORK_DIR/pristine" "$WORK_DIR/pinperturbed" || exit 2
|
||||
write_spec "$WORK_DIR/pinperturbed" "$WORK_DIR/pinperturbed.spec"
|
||||
python3 "$PY" perturb "$WORK_DIR/pinperturbed.spec" "$target" \
|
||||
"$WORK_DIR/pristine/$(blob_name "$targetSource")" \
|
||||
"$WORK_DIR/pinperturbed/$(blob_name "$targetSource")" token || exit 2
|
||||
python3 "$PY" extract "$WORK_DIR/pinperturbed.spec" > "$WORK_DIR/pinperturbed.sha" || exit 2
|
||||
cp -f "$WORK_DIR/pristine.sha" "$WORK_DIR/pinbase.sha" || exit 2
|
||||
apply_pinned_shas "$WORK_DIR/pinbase.sha" || exit 2
|
||||
if compare_lists "$WORK_DIR/pinbase.sha" "$WORK_DIR/pinperturbed.sha" \
|
||||
"PIN($PINNED_BASELINE_REF)" "one-token-perturbed" 2> "$WORK_DIR/pinperturbed.err"; then
|
||||
say "NEGATIVE CONTROL DID NOT TRIP: one token was inserted into $target's body and the"
|
||||
say "comparison AGAINST THE PIN still reported every region as identical. The pinned row is"
|
||||
say "not being compared at all, so every green this gate has printed for it means nothing."
|
||||
exit 2
|
||||
fi
|
||||
if ! grep -q "FIRST REGION THAT MOVED: $target" "$WORK_DIR/pinperturbed.err"; then
|
||||
say "NEGATIVE CONTROL TRIPPED FOR THE WRONG REASON: the comparison against the pin went red"
|
||||
say "but did not name $target as the first region that moved. It said:"
|
||||
sed 's/^/[p4a-untouched] /' "$WORK_DIR/pinperturbed.err" >&2
|
||||
exit 2
|
||||
fi
|
||||
if ! grep -q "$target IS A PINNED ROW" "$WORK_DIR/pinperturbed.err"; then
|
||||
say "NEGATIVE CONTROL TRIPPED FOR THE WRONG REASON: it went red and named $target, but did"
|
||||
say "not say that this row's baseline is the PIN. That is the half a reader acts on, and a"
|
||||
say "control that does not assert its own message is not a control (R-16). It said:"
|
||||
sed 's/^/[p4a-untouched] /' "$WORK_DIR/pinperturbed.err" >&2
|
||||
exit 2
|
||||
fi
|
||||
controls=$((controls + 1))
|
||||
say "negative control $controls: a ONE-TOKEN edit to the pinned $target body goes red AGAINST"
|
||||
say " THE PIN, is named, and says the row is pinned"
|
||||
|
||||
# NOT a failure, deliberately: --self-test is about whether the comparison works, and it runs
|
||||
# on the WORKING tree, which may legitimately carry an uncommitted edit. The two-ref gate is
|
||||
# what fails when the committed region has left the pin.
|
||||
treeSha=$(awk -v n="$target" '$2 == n { print $1 }' "$WORK_DIR/pristine.sha")
|
||||
if [ "$treeSha" != "$pinned" ]; then
|
||||
say "NOTE: this working tree's $target hashes $treeSha, not the pin ($pinned). The"
|
||||
say " self-test's verdict is unaffected; the two-ref gate will be RED until you re-pin or"
|
||||
say " revert."
|
||||
fi
|
||||
done
|
||||
|
||||
say "self-test passed: $controls negative controls, all tripped and all named"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user