[Fix] (CI): make split controls mandatory and isolate runner evidence

This commit is contained in:
2026-09-16 12:34:33 -04:00
parent 37fc4fdb0f
commit 6fa6a925bd
14 changed files with 148 additions and 51 deletions
+18 -20
View File
@@ -962,6 +962,8 @@ jobs:
# workflow passed that option. They had never been compiled by CI, let alone run. # workflow passed that option. They had never been compiled by CI, let alone run.
- name: Unit tests on the split runtime - name: Unit tests on the split runtime
working-directory: build-split working-directory: build-split
env:
MOBILEGL_ITEST_REQUIRE_GPU: "1"
run: ctest --output-on-failure -L unit --no-tests=error -j "$(nproc)" run: ctest --output-on-failure -L unit --no-tests=error -j "$(nproc)"
# --no-tests=error is half the gate, exactly as in integration-verify: the integration-split # --no-tests=error is half the gate, exactly as in integration-verify: the integration-split
@@ -979,13 +981,11 @@ jobs:
run: | run: |
ulimit -c unlimited ulimit -c unlimited
sudo sysctl -w kernel.core_pattern='/tmp/core.%e.%p' sudo sysctl -w kernel.core_pattern='/tmp/core.%e.%p'
ctest --output-on-failure -L integration-split --no-tests=error -j 4 ctest --output-on-failure -L integration-split --no-tests=error -j 4 --output-junit "${RUNNER_TEMP}/split-baseline.xml"
python3 ../scripts/ci/junit_tally.py "${RUNNER_TEMP}/split-baseline.xml" --require-split-ran
# ARCHITECTURE.md:521 asks for `ctest -L integration-gpu` to be name-for-name identical # ID-65 supersedes broad inproc status parity: class-C aborts and named wrong-answer
# between the monolith and the split arm of the SAME build - the G2 shape extended to a # debts are recorded. Monolith, integration-split and the controls remain hard gates.
# third arm. The knob goes in the JOB environment rather than in a ctest property, for the
# reason the MOBILEGL_ESPRYT_DISABLE_INVALIDATE_FLUSH precedent in `integration` gives: a
# property would override it and the arm would not be an arm.
# #
# The entries that name MOBILEGL_TRANSPORT in their OWN property (the Split. lanes) keep # The entries that name MOBILEGL_TRANSPORT in their OWN property (the Split. lanes) keep
# their value in both passes, which is correct: they are the split family in both arms and # their value in both passes, which is correct: they are the split family in both arms and
@@ -1007,14 +1007,11 @@ jobs:
fi fi
echo "integration-gpu entries in the split build: ${count}" echo "integration-gpu entries in the split build: ${count}"
MOBILEGL_TRANSPORT=monolith ctest --output-on-failure -L integration-gpu --no-tests=error -j 4 --output-junit "${RUNNER_TEMP}/arm-monolith.xml" MOBILEGL_TRANSPORT=monolith ctest --output-on-failure -L integration-gpu --no-tests=error -j 4 --output-junit "${RUNNER_TEMP}/arm-monolith.xml"
MOBILEGL_TRANSPORT=inproc ctest --output-on-failure -L integration-gpu --no-tests=error -j 4 --output-junit "${RUNNER_TEMP}/arm-inproc.xml" # ID-65: broad inproc is a recorded debt census. Reduced split + controls gate below.
# NAME **AND STATUS**, and it is the comparison this step claimed to make and did not inproc_rc=0
# (review finding N-3): the first version wrote a names file and never read it, and MOBILEGL_TRANSPORT=inproc ctest --output-on-failure -L integration-gpu --no-tests=error -j 4 --output-junit "${RUNNER_TEMP}/arm-inproc.xml" || inproc_rc=$?
# `--output-on-failure` treats a SKIPPED test as not-a-failure - so the very failure python3 ../scripts/ci/census_junit.py "${RUNNER_TEMP}/arm-inproc.xml" "${inproc_rc}" >> "${GITHUB_STEP_SUMMARY}"
# ARCHITECTURE.md:521 is about, "an inproc arm that skipped forty entries the monolith arm # Retain the per-name status delta as evidence, not as the reduced-path gate.
# ran", was invisible here and caught only by the local gate. `ctest -N` cannot see it
# either: this is one build directory, so the two arms have identical name lists by
# construction and the difference is entirely in what each entry DID.
python3 - "${RUNNER_TEMP}/arm-monolith.xml" "${RUNNER_TEMP}/arm-inproc.xml" <<'PY' python3 - "${RUNNER_TEMP}/arm-monolith.xml" "${RUNNER_TEMP}/arm-inproc.xml" <<'PY'
import sys, xml.etree.ElementTree as ET import sys, xml.etree.ElementTree as ET
def rows(path): def rows(path):
@@ -1031,12 +1028,9 @@ jobs:
diff = sorted(set(a) ^ set(b)) + sorted(n for n in set(a) & set(b) if a[n] != b[n]) diff = sorted(set(a) ^ set(b)) + sorted(n for n in set(a) & set(b) if a[n] != b[n])
if diff: if diff:
for name in diff[:40]: for name in diff[:40]:
print(f"::error::{name}: monolith={a.get(name, '<absent>')} inproc={b.get(name, '<absent>')}") print(f"{name}: monolith={a.get(name, '<absent>')} inproc={b.get(name, '<absent>')}")
print(f"::error::the monolith and inproc arms of ctest -L integration-gpu differ on " print(f"Recorded ID-65 census: {len(diff)} name/status differences; not a parity gate")
f"{len(diff)} entries. ARCHITECTURE.md:521 requires them identical name for name " else:
f"AND status; an entry that SKIPPED on one arm and ran on the other is the "
f"failure this compares for, and it is not a failure to --output-on-failure.")
raise SystemExit(1)
print(f"the two arms agree on all {len(a)} entries, name and status") print(f"the two arms agree on all {len(a)} entries, name and status")
PY PY
@@ -1095,6 +1089,7 @@ jobs:
# and FAILED as evidence the lane was live, so the controls could be measured against a # and FAILED as evidence the lane was live, so the controls could be measured against a
# baseline that was already red. # baseline that was already red.
- name: Negative controls - the verb barrier and the persistent-map push must be load-bearing - name: Negative controls - the verb barrier and the persistent-map push must be load-bearing
if: ${{ !cancelled() }}
working-directory: build-split working-directory: build-split
env: env:
MOBILEGL_ITEST_REQUIRE_GPU: "1" MOBILEGL_ITEST_REQUIRE_GPU: "1"
@@ -1109,6 +1104,8 @@ jobs:
path: | path: |
build-split/MobileGL/MG_IntegrationTest/*.log* build-split/MobileGL/MG_IntegrationTest/*.log*
build-split/MobileGL/MG_IntegrationTest/split-logs/*.log build-split/MobileGL/MG_IntegrationTest/split-logs/*.log
${{ runner.temp }}/arm-inproc.xml
${{ runner.temp }}/arm-monolith.xml
if-no-files-found: warn if-no-files-found: warn
- name: Upload core dumps - name: Upload core dumps
@@ -1978,6 +1975,7 @@ jobs:
env: env:
CONTROL_TMPDIR: ${{ runner.temp }} CONTROL_TMPDIR: ${{ runner.temp }}
LIBRARY_LOG: ${{ matrix.case }}/${{ matrix.backend }}/output/mobilegl.log LIBRARY_LOG: ${{ matrix.case }}/${{ matrix.backend }}/output/mobilegl.log
FROZEN_LIBRARY: ${{ github.workspace }}/build-linux/libMobileGL.so
run: >- run: >-
bash "${GITHUB_WORKSPACE}/scripts/ci/retrace_drop_draw_control.sh" bash "${GITHUB_WORKSPACE}/scripts/ci/retrace_drop_draw_control.sh"
'${{ matrix.case }}' '${{ matrix.backend }}' '${{ matrix.case }}' '${{ matrix.backend }}'
@@ -68,6 +68,14 @@ def main():
for n in selected for c in by_name[n]) for n in selected for c in by_name[n])
if not_failed: if not_failed:
raise ValueError(f"{label} control: {not_failed} selected entries did not fail") raise ValueError(f"{label} control: {not_failed} selected entries did not fail")
elif mode == "assertion":
cases = ET.parse(sys.argv[4]).getroot().findall(".//testcase")
by_name = {case.get("name"): case for case in cases}
for name in selected:
case = by_name.get(name)
output = "" if case is None else " ".join(" ".join(case.itertext()).split())
if not re.search(sys.argv[5], output):
raise ValueError(f"E3(a) FAILED: {name} red lacks its persistent-map push diagnostic")
elif mode == "evidence": elif mode == "evidence":
missing = [] missing = []
label = sys.argv[5] if len(sys.argv) > 5 else "" label = sys.argv[5] if len(sys.argv) > 5 else ""
+13 -6
View File
@@ -69,7 +69,7 @@ def cases():
[SUITE+'BoundPackBufferOffsetReadRefusesByName'], 'RemoteClientTest'), [SUITE+'BoundPackBufferOffsetReadRefusesByName'], 'RemoteClientTest'),
('codex12-repeat-skip', BACKEND, ('codex12-repeat-skip', BACKEND,
replace('if (draw != EGL_NO_SURFACE && ctx != EGL_NO_CONTEXT) {', 'if (false) {'), replace('if (draw != EGL_NO_SURFACE && ctx != EGL_NO_CONTEXT) {', 'if (false) {'),
[SUITE+'RepeatedMakeCurrentAdoptsRepublishedCapsWithoutAPumpOrPresent'], 'RemoteClientTest'), [SUITE+'ADifferentTupleMakeCurrentIsAdoptedWithoutAPumpOrPresent'], 'RemoteClientTest'),
('BlobMissing-optional-to-required', WIRE, ('BlobMissing-optional-to-required', WIRE,
replace('record.Blob = StageOptional(session, blobBytes, blobByteCount);', replace('record.Blob = StageOptional(session, blobBytes, blobByteCount);',
'record.Blob = StageRequired(session, "SetDynamicState", blobBytes, blobByteCount);'), 'record.Blob = StageRequired(session, "SetDynamicState", blobBytes, blobByteCount);'),
@@ -122,13 +122,20 @@ def main():
binary = ROOT / 'build-split/MobileGL/MG_Test' / ('Pipe' if target == 'PipeCatalogueTest' else 'Wire') / target binary = ROOT / 'build-split/MobileGL/MG_Test' / ('Pipe' if target == 'PipeCatalogueTest' else 'Wire') / target
build = ['cmake', '--build', 'build-split', '-j', '24', '--target', target] build = ['cmake', '--build', 'build-split', '-j', '24', '--target', target]
run = [str(binary), '--gtest_filter='+':'.join(names)] run = [str(binary), '--gtest_filter='+':'.join(names)]
# ID-67: both replacement cases must execute green before/after this mutation.
# Suppressing client adoption only reddens the different-tuple case; an identical
# tuple correctly republishes nothing and must remain green under the perturbation.
green_names = names
if label == 'codex12-repeat-skip':
green_names = names + [SUITE+'AnIdenticalRepeatedMakeCurrentRepublishesNothing']
green_run = [str(binary), '--gtest_filter='+':'.join(green_names)]
print('\n=== '+label+' ===', flush=True) print('\n=== '+label+' ===', flush=True)
try: try:
brc, out = command(build) brc, out = command(build)
if brc: if brc:
raise RuntimeError('baseline build failed\n'+out) raise RuntimeError('baseline build failed\n'+out)
rc, out = command(run) rc, out = command(green_run)
if rc or any('[ OK ] '+name+' (' not in out for name in names): if rc or any('[ OK ] '+name+' (' not in out for name in green_names):
raise RuntimeError('baseline not green\n'+out) raise RuntimeError('baseline not green\n'+out)
path.write_text(mutate(original.decode())) path.write_text(mutate(original.decode()))
brc, out = command(build) brc, out = command(build)
@@ -146,12 +153,12 @@ def main():
finally: finally:
path.write_bytes(original) path.write_bytes(original)
brc, out = command(build) brc, out = command(build)
rc, out = command(run) if brc == 0 else (brc, out) rc, out = command(green_run) if brc == 0 else (brc, out)
if rc or any('[ OK ] '+name+' (' not in out for name in names): if rc or any('[ OK ] '+name+' (' not in out for name in green_names):
print('RESTORE-FAIL\n'+out, flush=True) print('RESTORE-FAIL\n'+out, flush=True)
failures.append(label+' restore') failures.append(label+' restore')
else: else:
print('RESTORED GREEN: '+', '.join(names), flush=True) print('RESTORED GREEN: '+', '.join(green_names), flush=True)
print('FAILED_CONTROLS='+repr(failures), flush=True) print('FAILED_CONTROLS='+repr(failures), flush=True)
return bool(failures) return bool(failures)
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env python3
"""Summarize the indebted broad lane without turning debt into a hard gate."""
import collections
import sys
import xml.etree.ElementTree as ET
cases = ET.parse(sys.argv[1]).getroot().findall('.//testcase')
if not cases:
sys.exit('Census FAILED: no executed test records')
counts = collections.Counter()
for case in cases:
failure = case.find('failure')
if failure is not None:
reason = failure.get('message', '')
status = 'aborted' if 'aborted' in reason.lower() else 'failed'
elif case.find('skipped') is not None:
status = 'skipped'
else:
status = 'passed'
counts[status] += 1
print('### Broad inproc census (ID-65: recorded debt, not a gate)')
print(f'CTest exit: {sys.argv[2]}; total: {len(cases)}')
print('\n| passed | skipped | aborted | failed |\n|---:|---:|---:|---:|')
print('| ' + ' | '.join(str(counts[k]) for k in ('passed', 'skipped', 'aborted', 'failed')) + ' |')
+12 -5
View File
@@ -56,10 +56,15 @@ run_split() { # $1 = STUB_MODE
run_retrace() { # $1 = STUB_MODE run_retrace() { # $1 = STUB_MODE
cd "${WORK}" || return 127 cd "${WORK}" || return 127
mkdir -p "${WORK}/OpenRA" mkdir -p "${WORK}/OpenRA"
local rc=0
env -i PATH="${STUB_DIR}:/usr/bin:/bin" STUB_MODE="$1" \ env -i PATH="${STUB_DIR}:/usr/bin:/bin" STUB_MODE="$1" \
CTEST=ctest CONTROL_TMPDIR="${WORK}/tmp-$1" \ CTEST=ctest CONTROL_TMPDIR="${WORK}/tmp-$1" \
PULL_LIBRARY="${WORK}/pull.so" FROZEN_LIBRARY="${WORK}/frozen.so" \ PULL_LIBRARY="${WORK}/pull.so" FROZEN_LIBRARY="${WORK}/frozen.so" \
bash "${HERE}/retrace_pull_library_control.sh" OpenRA DirectGLES bash "${HERE}/retrace_pull_library_control.sh" OpenRA DirectGLES || rc=$?
cmp -s "${WORK}/frozen.so" "${WORK}/split.so" || {
echo 'F6 FAILED: pull control did not restore the split library'; return 1;
}
return "${rc}"
} }
run_drop_draw() { # $1 = STUB_MODE run_drop_draw() { # $1 = STUB_MODE
@@ -67,7 +72,7 @@ run_drop_draw() { # $1 = STUB_MODE
mkdir -p "${WORK}/OpenRA" mkdir -p "${WORK}/OpenRA"
env -i PATH="${STUB_DIR}:/usr/bin:/bin" STUB_MODE="$1" \ env -i PATH="${STUB_DIR}:/usr/bin:/bin" STUB_MODE="$1" \
CTEST=ctest CONTROL_TMPDIR="${WORK}/tmp-$1" \ CTEST=ctest CONTROL_TMPDIR="${WORK}/tmp-$1" \
LIBRARY_LOG="${WORK}/tmp-$1/mobilegl.log" \ FROZEN_LIBRARY="${WORK}/frozen.so" LIBRARY_LOG="${WORK}/tmp-$1/mobilegl.log" \
bash "${HERE}/retrace_drop_draw_control.sh" OpenRA DirectGLES bash "${HERE}/retrace_drop_draw_control.sh" OpenRA DirectGLES
} }
@@ -80,8 +85,8 @@ expect PASSED "the scenarios' own diagnostic" -- run_split evidenc
expect FAILED "the knob leaves the selection green" -- run_split green 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. # 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 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. # P5 is complete: losing the runtime implementation must no longer disarm the gate.
expect PASSED "every split entry skipped (lane not armed)" -- run_split all-skipped expect FAILED "every split entry skipped (implementation lost)" -- run_split all-skipped
echo echo
echo "=== the retrace lane's pull-library control (scripts/ci/retrace_pull_library_control.sh)" echo "=== the retrace lane's pull-library control (scripts/ci/retrace_pull_library_control.sh)"
@@ -93,7 +98,9 @@ else
echo "no cc available; the retrace half of this smoke test needs one" >&2 echo "no cc available; the retrace half of this smoke test needs one" >&2
exit 1 exit 1
fi fi
: > "${WORK}/frozen.so" printf '%s\n' 'int MG_Remote_stub(void) { return 1; }' > "${WORK}/split.c"
cc -shared -fPIC -o "${WORK}/frozen.so" "${WORK}/split.c" || exit 1
cp "${WORK}/frozen.so" "${WORK}/split.so"
# THE FINDING, part (b): a regex matching no tests. --no-tests=error exits non-zero and the old # 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". # control read that as "the pull library turned it red".
+9 -4
View File
@@ -20,9 +20,11 @@ import sys
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
def tally(path): def tally(path, split_only=False):
passed = failed = skipped = 0 passed = failed = skipped = 0
for case in ET.parse(path).getroot().iter('testcase'): for case in ET.parse(path).getroot().iter('testcase'):
if split_only and not case.get('name', '').startswith('DirectGLES.Split.'):
continue
if case.find('failure') is not None or case.find('error') is not None: if case.find('failure') is not None or case.find('error') is not None:
failed += 1 failed += 1
elif case.find('skipped') is not None or case.get('status') in ('notrun', 'disabled'): elif case.find('skipped') is not None or case.get('status') in ('notrun', 'disabled'):
@@ -33,15 +35,18 @@ def tally(path):
def main(): def main():
if len(sys.argv) != 2: if len(sys.argv) not in (2, 3) or (len(sys.argv) == 3 and sys.argv[2] != '--require-split-ran'):
print("usage: junit_tally.py <junit.xml>", file=sys.stderr) print("usage: junit_tally.py <junit.xml> [--require-split-ran]", file=sys.stderr)
return 2 return 2
try: try:
passed, failed, skipped = tally(sys.argv[1]) passed, failed, skipped = tally(sys.argv[1], split_only=len(sys.argv) == 3)
except Exception as exc: # a malformed file is not "zero of everything" 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) print(f"junit_tally: cannot parse {sys.argv[1]}: {exc}", file=sys.stderr)
return 1 return 1
print(f"{passed} {failed} {skipped}") print(f"{passed} {failed} {skipped}")
if len(sys.argv) == 3 and (passed == 0 or failed):
print('split baseline FAILED: no successful split runtime entries or an already-red selection', file=sys.stderr)
return 1
return 0 return 0
+1 -1
View File
@@ -55,7 +55,7 @@ split, retrace, dropdraw = sys.argv[1], sys.argv[2], sys.argv[3]
# other two catching the smoke test's "unrelated failure" case, and the case never flips - which # other two catching the smoke test's "unrelated failure" case, and the case never flips - which
# is what this script measured the first time the perturbation actually applied. # is what this script measured the first time the perturbation actually applied.
rules = [ rules = [
(split, [('LINE', 'grep -qE "${evidence}"', ('if ! ', 'elif ! ')), (split, [('LINE', '"${log_helper}" assertion', ('if ! ', 'elif ! ')),
('SUBST', '"[A-Za-z_][A-Za-z_0-9]*"\\}\' || exit 1', '"[A-Za-z_][A-Za-z_0-9]*"\\}\' || true'), ('SUBST', '"[A-Za-z_][A-Za-z_0-9]*"\\}\' || exit 1', '"[A-Za-z_][A-Za-z_0-9]*"\\}\' || true'),
('SUBST', '"${private_evidence}" "${name}" || exit 1', ('SUBST', '"${private_evidence}" "${name}" || exit 1',
'"${private_evidence}" "${name}" || true')]), '"${private_evidence}" "${name}" || true')]),
+10
View File
@@ -50,6 +50,14 @@ CTEST="${CTEST:-ctest}"
CONTROL_TMPDIR="${CONTROL_TMPDIR:-${RUNNER_TEMP:-/tmp}}" CONTROL_TMPDIR="${CONTROL_TMPDIR:-${RUNNER_TEMP:-/tmp}}"
LIBRARY_LOG="${LIBRARY_LOG:-${CASE}/${BACKEND}/output/mobilegl.log}" LIBRARY_LOG="${LIBRARY_LOG:-${CASE}/${BACKEND}/output/mobilegl.log}"
mkdir -p "${CONTROL_TMPDIR}" mkdir -p "${CONTROL_TMPDIR}"
FROZEN_LIBRARY="${FROZEN_LIBRARY:?FROZEN_LIBRARY must name the split library the replay loads}"
symbols=$(nm --defined-only "${FROZEN_LIBRARY}") || exit 1
remote_count=$(printf '%s\n' "${symbols}" | grep -ic MG_Remote || true)
echo "draw-drop control library: ${FROZEN_LIBRARY}: MG_Remote=${remote_count}"
if [ "${remote_count}" -lt 1 ]; then
echo '::error::draw-drop control requires a split library: MG_Remote=0'
exit 1
fi
selector="^MobileGLTraceReplay\.${CASE}\.${BACKEND}$" selector="^MobileGLTraceReplay\.${CASE}\.${BACKEND}$"
@@ -66,6 +74,8 @@ restore_good_output() {
echo "restored the verified run's output over the control's" echo "restored the verified run's output over the control's"
fi fi
} }
trap restore_good_output EXIT
trap 'exit 130' INT TERM
matched=$("${CTEST}" -N -R "${selector}" | grep -cE '^ *Test *#[0-9]+:') matched=$("${CTEST}" -N -R "${selector}" | grep -cE '^ *Test *#[0-9]+:')
if [ "${matched}" -lt 1 ]; then if [ "${matched}" -lt 1 ]; then
+17 -2
View File
@@ -69,6 +69,18 @@ restore_good_output() {
fi fi
} }
# Restore the exact split library on success, failure, and interruption. The following
# draw-drop control uses this same frozen path.
saved_library=$(mktemp "${CONTROL_TMPDIR}/split-library.XXXXXX") || exit 1
cp -p "${FROZEN_LIBRARY}" "${saved_library}" || exit 1
restore_control() {
cp -p "${saved_library}" "${FROZEN_LIBRARY}"
rm -f "${saved_library}"
restore_good_output
}
trap restore_control EXIT
trap 'exit 130' INT TERM
# HOLE 1: COUNT THE SELECTION FIRST. `--no-tests=error` turns an empty selection into a non-zero # 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. # exit, which is indistinguishable from a working control unless the selection is counted.
matched=$("${CTEST}" -N -R "${selector}" | grep -cE '^ *Test *#[0-9]+:') matched=$("${CTEST}" -N -R "${selector}" | grep -cE '^ *Test *#[0-9]+:')
@@ -82,8 +94,11 @@ fi
# in. It defines no MG_Remote symbol, so ConfigLoader has no transport parser and # 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 # MOBILEGL_TRANSPORT=inproc is accepted and ignored - the exact shape of "the split lane ran
# monolith". # monolith".
cp "${PULL_LIBRARY}" "${FROZEN_LIBRARY}" cp "${PULL_LIBRARY}" "${FROZEN_LIBRARY}" || exit 1
if nm --defined-only "${FROZEN_LIBRARY}" | grep -q -i MG_Remote; then symbols=$(nm --defined-only "${FROZEN_LIBRARY}") || exit 1
remote_count=$(printf '%s\n' "${symbols}" | grep -ic MG_Remote || true)
echo "pull control library: ${FROZEN_LIBRARY}: MG_Remote=${remote_count}"
if [ "${remote_count}" -ne 0 ]; then
restore_good_output 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" 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 exit 1
+8 -5
View File
@@ -51,6 +51,7 @@ CONTROL_TMPDIR="${CONTROL_TMPDIR:-${RUNNER_TEMP:-/tmp}}"
mkdir -p "${CONTROL_TMPDIR}" mkdir -p "${CONTROL_TMPDIR}"
junit="${CONTROL_TMPDIR}/isplit.xml" junit="${CONTROL_TMPDIR}/isplit.xml"
rm -f "${junit}"
log_helper="$(dirname "$0")/../../MobileGL/MG_IntegrationTest/Harness/split_log_paths.py" log_helper="$(dirname "$0")/../../MobileGL/MG_IntegrationTest/Harness/split_log_paths.py"
# Check ownership even while the runtime lane is disarmed and will skip. # Check ownership even while the runtime lane is disarmed and will skip.
@@ -87,14 +88,14 @@ echo "split entries - passed: ${baseline_passed}, failed: ${baseline_failed}, sk
# A RED BASELINE DISARMS THE CONTROLS RATHER THAN ARMING THEM (review finding 8, second half). # 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 # `|| 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. # lane was live. Turning an already-red entry red is not a measurement.
if [ "${baseline_failed}" -gt 0 ]; then 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'.)" 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 exit 1
fi fi
if [ "${baseline_passed}" -lt 1 ]; then 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." echo "::error::split baseline FAILED: every DirectGLES.Split. entry SKIPPED; the split implementation did not execute"
exit 0 exit 1
fi fi
# ---- the controls --------------------------------------------------------------------------- # ---- the controls ---------------------------------------------------------------------------
@@ -145,7 +146,7 @@ run_control() {
if [ "${evidence}" = "private-barrier-fatal" ]; then if [ "${evidence}" = "private-barrier-fatal" ]; then
python3 "${log_helper}" evidence "${manifest}" "${filter}" \ python3 "${log_helper}" evidence "${manifest}" "${filter}" \
'Fatal\{BarrierViolation, "[A-Za-z_][A-Za-z_0-9]*"\}' || exit 1 'Fatal\{BarrierViolation, "[A-Za-z_][A-Za-z_0-9]*"\}' || exit 1
elif ! tr -s '[:space:]' ' ' < "${out}" | grep -qE "${evidence}"; then elif ! python3 "${log_helper}" assertion "${manifest}" "${filter}" "${result}" "${evidence}"; then
echo "::error::${name} FAILED: red lacks its persistent-map push diagnostic. Required: ${evidence}" echo "::error::${name} FAILED: red lacks its persistent-map push diagnostic. Required: ${evidence}"
exit 1 exit 1
fi fi
@@ -179,8 +180,10 @@ run_control "negative control E1 (MOBILEGL_IPC_VERB_BARRIER=0)" \
# * the LIBRARY's own line in the entry's private file, saying the push was disabled by this # * 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 # 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. # 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)" \ run_control "negative control E3(a) (MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0)" \
'DirectGLES\.Split\.(SmallRing\.)?PersistentCoherentMapScenario' \ '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" \ "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' \ 'MGPipe: persistent-map push disabled - MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0' \
MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0 MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0
+2 -1
View File
@@ -7,7 +7,7 @@ trap 'rm -rf "${WORK}"' EXIT
cp "${HERE}/testdata/stub_ctest.sh" "${WORK}/ctest" cp "${HERE}/testdata/stub_ctest.sh" "${WORK}/ctest"
chmod +x "${WORK}/ctest" chmod +x "${WORK}/ctest"
passes=0 passes=0
for mode in missing-fatal stdout-fatal stale-fatal evidence e3-unrelated e3-no-private skipped-selection notrun-selection missing-selection partial-fatal wrong-fatal; do 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}" mkdir -p "${WORK}/${mode}"
rc=0 rc=0
STUB_MODE="${mode}" CTEST="${WORK}/ctest" CONTROL_TMPDIR="${WORK}/${mode}" \ STUB_MODE="${mode}" CTEST="${WORK}/ctest" CONTROL_TMPDIR="${WORK}/${mode}" \
@@ -25,6 +25,7 @@ for mode in missing-fatal stdout-fatal stale-fatal evidence e3-unrelated e3-no-p
case "${mode}" in case "${mode}" in
e3-no-private) message='no selected private log carries /MGPipe: persistent-map push disabled' ;; e3-no-private) message='no selected private log carries /MGPipe: persistent-map push disabled' ;;
skipped-selection) message='SplitLogPaths FAILED: E1 control: the knob killed the pre-flight, not the entry - 1 selected entries skipped' ;; skipped-selection) message='SplitLogPaths FAILED: E1 control: the knob killed the pre-flight, not the entry - 1 selected entries skipped' ;;
e3-skipped-selection) message='SplitLogPaths FAILED: E3(a) control: the knob killed the pre-flight, not the entry - 1 selected entries skipped' ;;
notrun-selection|missing-selection) message='SplitLogPaths FAILED: E1 control: 1 selected entries did not run' ;; notrun-selection|missing-selection) message='SplitLogPaths FAILED: E1 control: 1 selected entries did not run' ;;
esac esac
if [[ "${mode}" = *-selection ]]; then if [[ "${mode}" = *-selection ]]; then
+19 -2
View File
@@ -24,7 +24,7 @@
# never says the push was disabled (the half ID-65 added) # 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) # green baseline green; the control's own run PASSES (the knob is not load-bearing)
# red-baseline the baseline itself has a failed entry # red-baseline the baseline itself has a failed entry
# all-skipped the baseline is entirely skipped (the disarmed lane, a legitimate exit 0) # all-skipped the baseline is entirely skipped (lost implementation, a hard failure)
# retrace-noselect `ctest -N` matches nothing; the run exits 8 the way --no-tests=error does # 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-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-evidence one match; the run fails with run_trace_case.cmake's own sentence
@@ -76,10 +76,20 @@ write_junit() {
entry=DirectGLES.Split.ClearThenReadPixelsScenario.ClearWithNoDrawIsVisibleToDefaultFramebufferReadPixels entry=DirectGLES.Split.ClearThenReadPixelsScenario.ClearWithNoDrawIsVisibleToDefaultFramebufferReadPixels
[ "${MOBILEGL_IPC_PERSISTENT_BLOCK_KB:-64}" != 0 ] || entry=DirectGLES.Split.PersistentCoherentMapScenario.TwoWritesThroughTheCoherentPointerEachReachTheirOwnDraw [ "${MOBILEGL_IPC_PERSISTENT_BLOCK_KB:-64}" != 0 ] || entry=DirectGLES.Split.PersistentCoherentMapScenario.TwoWritesThroughTheCoherentPointerEachReachTheirOwnDraw
body="<testcase name=\"${entry}\" status=\"fail\"><failure message=\"control red\"/></testcase>" 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 if [ "${mode}" = partial-fatal ] && [ "${MOBILEGL_IPC_VERB_BARRIER:-1}" = 0 ]; then
body="${body}<testcase name=\"DirectGLES.Split.TriangleScenario.SecondEntry\" status=\"fail\"><failure/></testcase>" body="${body}<testcase name=\"DirectGLES.Split.TriangleScenario.SecondEntry\" status=\"fail\"><failure/></testcase>"
fi fi
case "${mode}" in 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>" ;; skipped-selection) body="<testcase name=\"${entry}\" status=\"notrun\"><skipped/></testcase>" ;;
notrun-selection) body="<testcase name=\"${entry}\" status=\"notrun\"/>" ;; notrun-selection) body="<testcase name=\"${entry}\" status=\"notrun\"/>" ;;
missing-selection) body='' ;; missing-selection) body='' ;;
@@ -130,7 +140,7 @@ fi
# The control's own run. # The control's own run.
if [ "${MOBILEGL_IPC_VERB_BARRIER:-1}" = 0 ]; then if [ "${MOBILEGL_IPC_VERB_BARRIER:-1}" = 0 ]; then
case "${mode}" in case "${mode}" in
evidence|e3-unrelated|e3-no-private|skipped-selection|notrun-selection|missing-selection|partial-fatal) echo 'Fatal{BarrierViolation, "DrawVbo"}' > "${log}" ;; 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}" ;; wrong-fatal) echo 'Fatal{ReplyMissing, "DrawVbo"}' > "${log}" ;;
missing-fatal) echo "library setup only; no fatal" > "${log}" ;; missing-fatal) echo "library setup only; no fatal" > "${log}" ;;
stdout-fatal) echo 'Fatal{BarrierViolation, "DrawVbo"}' ;; stdout-fatal) echo 'Fatal{BarrierViolation, "DrawVbo"}' ;;
@@ -144,6 +154,13 @@ if [ "${MOBILEGL_IPC_PERSISTENT_BLOCK_KB:-64}" = 0 ] && [ "${mode}" = evidence ]
> "${CONTROL_TMPDIR}/pmap.log" > "${CONTROL_TMPDIR}/pmap.log"
fi fi
case "${mode}" in 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) skipped-selection|notrun-selection|missing-selection)
echo '1/1 Test #1: selected entry ... ***Skipped' echo '1/1 Test #1: selected entry ... ***Skipped'
echo '100% tests passed, 0 tests failed out of 1' echo '100% tests passed, 0 tests failed out of 1'
+3 -2
View File
@@ -532,7 +532,8 @@ if [ "${1:-}" = "--self-test" ]; then
printf '%s %s\n' \ printf '%s %s\n' \
"0000000000000000000000000000000000000000000000000000000000000000" "$target" \ "0000000000000000000000000000000000000000000000000000000000000000" "$target" \
>> "$WORK_DIR/pinprec.sha" >> "$WORK_DIR/pinprec.sha"
apply_pinned_shas "$WORK_DIR/pinprec.sha" || exit 2 # Drive production extraction on a real historical ref whose body differs from the pin.
extract_baseline ff2994d9 pinprec || exit 2
got=$(awk -v n="$target" '$2 == n { print $1 }' "$WORK_DIR/pinprec.sha") got=$(awk -v n="$target" '$2 == n { print $1 }' "$WORK_DIR/pinprec.sha")
if [ "$got" != "$pinned" ]; then if [ "$got" != "$pinned" ]; then
say "PIN CONTROL FAILED: a baseline that carried a DIFFERENT sha for $target came out as" say "PIN CONTROL FAILED: a baseline that carried a DIFFERENT sha for $target came out as"
@@ -552,7 +553,7 @@ if [ "${1:-}" = "--self-test" ]; then
python3 "$PY" extract "$WORK_DIR/pinperturbed.cpp" "$ALL_FUNCTIONS" \ python3 "$PY" extract "$WORK_DIR/pinperturbed.cpp" "$ALL_FUNCTIONS" \
> "$WORK_DIR/pinperturbed.sha" || exit 2 > "$WORK_DIR/pinperturbed.sha" || exit 2
cp -f "$WORK_DIR/pristine.sha" "$WORK_DIR/pinbase.sha" || exit 2 cp -f "$WORK_DIR/pristine.sha" "$WORK_DIR/pinbase.sha" || exit 2
apply_pinned_shas "$WORK_DIR/pinbase.sha" || exit 2 extract_baseline HEAD pinbase || exit 2
if compare_lists "$WORK_DIR/pinbase.sha" "$WORK_DIR/pinperturbed.sha" \ if compare_lists "$WORK_DIR/pinbase.sha" "$WORK_DIR/pinperturbed.sha" \
"PIN($PINNED_BASELINE_REF)" "one-token-perturbed" 2> "$WORK_DIR/pinperturbed.err"; then "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 "NEGATIVE CONTROL DID NOT TRIP: one token was inserted into $target's body and the"
+3 -2
View File
@@ -766,7 +766,8 @@ if [ "${1:-}" = "--self-test" ]; then
printf '%s %s\n' \ printf '%s %s\n' \
"0000000000000000000000000000000000000000000000000000000000000000" "$target" \ "0000000000000000000000000000000000000000000000000000000000000000" "$target" \
>> "$WORK_DIR/pinprec.sha" >> "$WORK_DIR/pinprec.sha"
apply_pinned_shas "$WORK_DIR/pinprec.sha" || exit 2 # Drive production extraction on a real historical ref whose body differs from the pin.
extract_baseline 37da3c3a pinprec || exit 2
got=$(awk -v n="$target" '$2 == n { print $1 }' "$WORK_DIR/pinprec.sha") got=$(awk -v n="$target" '$2 == n { print $1 }' "$WORK_DIR/pinprec.sha")
if [ "$got" != "$pinned" ]; then if [ "$got" != "$pinned" ]; then
say "PIN CONTROL FAILED: a baseline that carried a DIFFERENT sha for $target came out as" say "PIN CONTROL FAILED: a baseline that carried a DIFFERENT sha for $target came out as"
@@ -789,7 +790,7 @@ if [ "${1:-}" = "--self-test" ]; then
"$WORK_DIR/pinperturbed/$(blob_name "$targetSource")" token || exit 2 "$WORK_DIR/pinperturbed/$(blob_name "$targetSource")" token || exit 2
python3 "$PY" extract "$WORK_DIR/pinperturbed.spec" > "$WORK_DIR/pinperturbed.sha" || 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 cp -f "$WORK_DIR/pristine.sha" "$WORK_DIR/pinbase.sha" || exit 2
apply_pinned_shas "$WORK_DIR/pinbase.sha" || exit 2 extract_baseline HEAD pinbase || exit 2
if compare_lists "$WORK_DIR/pinbase.sha" "$WORK_DIR/pinperturbed.sha" \ if compare_lists "$WORK_DIR/pinbase.sha" "$WORK_DIR/pinperturbed.sha" \
"PIN($PINNED_BASELINE_REF)" "one-token-perturbed" 2> "$WORK_DIR/pinperturbed.err"; then "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 "NEGATIVE CONTROL DID NOT TRIP: one token was inserted into $target's body and the"