Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 40 additions & 3 deletions .github/workflows/quality-resolve-probe.yml
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,42 @@ jobs:
PY
echo "OK — the matrix is derived from info.xml, and it changes when info.xml does."

coverage-guard:
name: "The coverage guard's deletion rule"
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4

# quality-config/coverage-guard.php is copied into all 18 app repos and its
# verdict blocks pull requests, and until now nothing in this repository
# executed it. That is the same shape as every other job here: an
# instrument nobody has watched refuse.
#
# The suite runs the SHIPPED program against clover reports built to the
# shape PHPUnit actually emits, and it FAILS FIRST — the two pull requests
# the file-scoped rule blocked (opencatalogi#895, launchpad#128) are
# asserted to still fail under the old flags, with the exact percentages CI
# printed, before the new flag is exercised at all. A test that only ever
# sees the fixed program cannot tell a fix from a no-op.
- name: "Exercise the shipped coverage guard"
run: python3 quality-config/tests/test-coverage-guard.py

# POSITIVE CONTROL FOR THE HARNESS ITSELF. The suite above shells out to
# `php`; if php were missing, or the guard path wrong, a harness that
# treated "could not run" as "nothing to report" would print a clean pass
# forever. Point it at a file that is not the guard and require it to fail.
- name: "Positive control — the suite must fail against a program that is not the guard"
run: |
set -eu
printf '#!/usr/bin/env php\n<?php\nexit(0);\n' > "$RUNNER_TEMP/not-the-guard.php"
if python3 quality-config/tests/test-coverage-guard.py "$RUNNER_TEMP/not-the-guard.php" > /dev/null 2>&1; then
echo "::error::the coverage-guard suite reported PASS against a program that exits 0 and \
measures nothing. It is not executing the subject, so its verdict above means nothing."
exit 1
fi
echo "OK — the suite fails when handed the wrong program. Its clean pass above is a verdict."

probe:
name: "quality.yml resolves (job count > 0)"
runs-on: ubuntu-latest
Expand Down Expand Up @@ -412,7 +448,7 @@ jobs:
guard:
name: "Shared-workflow guard"
runs-on: ubuntu-latest
needs: [static-limits, seed-semantics, gate-completeness, matrix-derivation, probe]
needs: [static-limits, seed-semantics, gate-completeness, matrix-derivation, coverage-guard, probe]
if: ${{ !cancelled() }}
steps:
- name: Assert both guards reached a verdict
Expand All @@ -421,13 +457,14 @@ jobs:
SEED: ${{ needs.seed-semantics.result }}
LEGS: ${{ needs.gate-completeness.result }}
MATRIX: ${{ needs.matrix-derivation.result }}
COVERAGE: ${{ needs.coverage-guard.result }}
PROBE: ${{ needs.probe.result }}
run: |
set -eu
echo "static-limits=${STATIC} seed-semantics=${SEED} gate-completeness=${LEGS} matrix-derivation=${MATRIX} probe=${PROBE}"
echo "static-limits=${STATIC} seed-semantics=${SEED} gate-completeness=${LEGS} matrix-derivation=${MATRIX} coverage-guard=${COVERAGE} probe=${PROBE}"
# `skipped` is failed here on purpose. A guard that did not run is
# not a guard that passed.
for pair in "static-limits:${STATIC}" "seed-semantics:${SEED}" "gate-completeness:${LEGS}" "matrix-derivation:${MATRIX}" "probe:${PROBE}"; do
for pair in "static-limits:${STATIC}" "seed-semantics:${SEED}" "gate-completeness:${LEGS}" "matrix-derivation:${MATRIX}" "coverage-guard:${COVERAGE}" "probe:${PROBE}"; do
name="${pair%%:*}"; result="${pair##*:}"
[ "${result}" = "success" ] || {
echo "::error::${name} did not succeed (result=${result}). A guard that \
Expand Down
44 changes: 43 additions & 1 deletion .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2596,9 +2596,51 @@ jobs:
# changed, nothing to compare" rather than as zero coverage.
git diff --name-only "$MERGE_BASE" HEAD -- '*.php' > "$RUNNER_TEMP/changed-php.txt" || true
echo "Changed PHP files in this PR: $(wc -l < "$RUNNER_TEMP/changed-php.txt")"

# ── DELETIONS ARE NOT COVERAGE REGRESSIONS ────────────────────────
#
# The scoping above removed the noise; it did not remove the
# ARITHMETIC. `cgRatioDropped()` is one integer cross-product with no
# tolerance, and read plainly it demands that the code you touched be
# at least as well covered as the code you did not. For additions that
# is exactly right. For deletions it inverts: removing `d` statements
# of which `c` were covered lowers the ratio whenever the deleted code
# was better tested than what remains — and DEAD CODE IS DEAD BECAUSE
# NOTHING CALLS IT, NOT BECAUSE NOTHING TESTED IT. gate-57
# (orphaned-write-capability) exists to find precisely that code, so
# the two are in arithmetic opposition, not tension. Such a pull
# request cannot satisfy the ratchet from inside its own subject: the
# only moves are delete less, add filler, or delete additional
# *uncovered* statements until it balances. Measured on
# opencatalogi#895 (-0.62% scoped) and launchpad#128 (-0.32% scoped),
# both of which deleted dead code and neither of which could comply.
#
# `--deletion-neutral` compares METHOD BUCKETS asymmetrically:
# base-only methods leave the base side, head-only methods stay on the
# head side. A pure deletion becomes exactly neutral; a regression in
# surviving code and new untested code both still fail. The symmetric
# version of that rule is the obvious one and it is broken — it also
# drops head-only statements, so forty new statements with none
# covered pass. quality-config/tests/test-coverage-guard.py asserts all
# four cases and kills a mutant that reintroduces the symmetric rule.
#
# PROBED, not assumed, for the same reason as the flag above: a copy
# predating this flag would accept it and ignore it, silently
# restoring the deletion penalty while reporting success. Older copies
# keep today's behaviour rather than erroring — this narrows an
# existing check, it is not the difference between a check running and
# not running.
CG_MODE=()
if php scripts/coverage-guard.php --capabilities 2>/dev/null | grep -qx deletion-neutral; then
CG_MODE+=(--deletion-neutral)
else
echo "::notice::scripts/coverage-guard.php predates --deletion-neutral, so deleting well-tested dead code will still read as a coverage drop. Copy the canonical version from ConductionNL/.github at quality-config/coverage-guard.php to pick it up."
fi

php scripts/coverage-guard.php coverage/clover.xml \
--against="$RUNNER_TEMP/base-clover.xml" \
--changed-files="$RUNNER_TEMP/changed-php.txt"
--changed-files="$RUNNER_TEMP/changed-php.txt" \
"${CG_MODE[@]}"
else
php scripts/coverage-guard.php coverage/clover.xml --against="$RUNNER_TEMP/base-clover.xml"
fi
Expand Down
Loading
Loading