Make the self-test run on Gitea, where it has been failing since it landed
ci/selftest.sh called `php` directly, four times. The Gitea runner image has no php — which is exactly why ci_php exists and says so in its own comment — so Static checks has been RED on Gitea since the self-test merged, while GitHub stayed green throughout. Found only by going to look at the other forge. Two of the four assertions failed there. The other two passed FOR THE WRONG REASON, and that is the worse half: they expect a non-zero exit, and `php: command not found` is a non-zero exit. A green tick for a check that never ran is the precise thing this script exists to prevent, reproduced inside it. Fixed by routing every call through ci_php, and by moving the fixtures inside the repo copy and staging them: ci_php's container gets its filesystem from `git ls-files`, so a fixture outside the repo or left unstaged is simply not there. selftest-empty/ carries a .keep file because git cannot track an empty directory, and an absent directory would make the check fail for a different reason than the one being asserted. Verified through the container path with php hidden from `command -v`, not just locally where php exists: all four verdicts identical either way, and the annotated path still resolves to includes/class-db.php. New 30th assertion: no ci/ script may call an interpreter directly. Its first version flagged five lines, all false: string literals mentioning php, and the calls inside the docker scripts in static.sh and site-test.sh, where php is correct because the container is a php image. Its second still conflated python3 with PyYAML. The runner HAS python3 and has NO php, and PyYAML is the only missing module — so bare python3 over the stdlib (build-zip.sh's zipfile, publish-release.sh's JSON readers) is correct and is not flagged. Measured, so the rule is not uniform. Controlled both ways: zero hits on this tree, and all four bare-php lines caught in the version that broke Gitea. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
03b0e13462
commit
8121ca18dd
+67
-11
@@ -132,31 +132,47 @@ echo "$out" | grep -qE '^ [0-9]+ shipped PHP files examined' \
|
|||||||
|| bad "the direct-access check printed no count — it cannot tell 13 files from 0"
|
|| bad "the direct-access check printed no count — it cannot tell 13 files from 0"
|
||||||
|
|
||||||
# ---------------------------------------------------------------- text domain
|
# ---------------------------------------------------------------- text domain
|
||||||
# No docker, so these are cheap. Three zeros, three verdicts.
|
# 🔴 ci_php, never a bare `php`. The Gitea runner image has NO php — that is
|
||||||
|
# why ci_php exists and says so — and this section originally called `php`
|
||||||
|
# directly. Two of its four assertions then failed there, and the other two
|
||||||
|
# PASSED FOR THE WRONG REASON: they expect a non-zero exit, and `php: command
|
||||||
|
# not found` is a non-zero exit. A green tick for a check that never ran is the
|
||||||
|
# exact thing this whole script exists to prevent, reproduced inside it.
|
||||||
|
#
|
||||||
|
# ci_php falls back to a container whose filesystem comes from `git ls-files`,
|
||||||
|
# so the fixtures live INSIDE this copy of the repo and are staged. A fixture
|
||||||
|
# outside the repo, or an unstaged one, is simply not there when it matters.
|
||||||
cd "$work/repo"
|
cd "$work/repo"
|
||||||
echo
|
echo
|
||||||
echo "→ ci/check-textdomain.php verdicts"
|
echo "→ ci/check-textdomain.php verdicts"
|
||||||
mkdir -p "$work/empty" "$work/nostrings"
|
|
||||||
printf '<?php\n$x = 1;\n' > "$work/nostrings/plugin.php"
|
|
||||||
|
|
||||||
php ci/check-textdomain.php --min=0 "$TEXT_DOMAIN" "$work/empty" >/dev/null 2>&1 \
|
# Not a literally empty directory: git cannot track one, so it would not reach
|
||||||
|
# the container at all and the check would fail for a different reason than the
|
||||||
|
# one being asserted.
|
||||||
|
mkdir -p selftest-empty selftest-nostrings selftest-dotted/includes
|
||||||
|
printf 'no php in here\n' > selftest-empty/.keep
|
||||||
|
printf '<?php\n$x = 1;\n' > selftest-nostrings/plugin.php
|
||||||
|
printf "<?php\necho __( 'x' );\n" > selftest-dotted/includes/class-db.php
|
||||||
|
git add -N selftest-empty/.keep selftest-nostrings/plugin.php \
|
||||||
|
selftest-dotted/includes/class-db.php >/dev/null
|
||||||
|
|
||||||
|
ci_php ci/check-textdomain.php --min=0 "$TEXT_DOMAIN" selftest-empty >/dev/null 2>&1 \
|
||||||
&& bad "an empty scan root passed — the root verdict must not be configurable away" \
|
&& bad "an empty scan root passed — the root verdict must not be configurable away" \
|
||||||
|| ok "an empty scan root is fatal even with --min=0"
|
|| ok "an empty scan root is fatal even with --min=0"
|
||||||
|
|
||||||
php ci/check-textdomain.php --min=999999 "$TEXT_DOMAIN" . >/dev/null 2>&1 \
|
ci_php ci/check-textdomain.php --min=999999 "$TEXT_DOMAIN" . >/dev/null 2>&1 \
|
||||||
&& bad "a --min above the real count passed" \
|
&& bad "a --min above the real count passed" \
|
||||||
|| ok "a --min above the real count is fatal"
|
|| ok "a --min above the real count is fatal"
|
||||||
|
|
||||||
php ci/check-textdomain.php "$TEXT_DOMAIN" "$work/nostrings" >/dev/null 2>&1 \
|
ci_php ci/check-textdomain.php "$TEXT_DOMAIN" selftest-nostrings >/dev/null 2>&1 \
|
||||||
&& ok "zero calls without --min warns rather than failing" \
|
&& ok "zero calls without --min warns rather than failing" \
|
||||||
|| bad "zero calls without --min failed — a small plugin may legitimately have none"
|
|| bad "zero calls without --min failed — a small plugin may legitimately have none"
|
||||||
|
|
||||||
# The annotated path must be a file that exists. str_replace( $root, '', $path )
|
# The annotated path must be a file that exists. str_replace( $root, '', $path )
|
||||||
# with the default root of "." strips every dot and yields "class-dbphp".
|
# with a root of "." strips every dot and yields "class-dbphp".
|
||||||
mkdir -p "$work/dotted/includes"
|
ann=$(ci_php ci/check-textdomain.php "$TEXT_DOMAIN" selftest-dotted 2>&1 \
|
||||||
printf "<?php\necho __( 'x' );\n" > "$work/dotted/includes/class-db.php"
|
| sed -n 's/.*file=\([^,]*\),.*/\1/p' | head -1)
|
||||||
ann=$(cd "$work/dotted" && php "$work/repo/ci/check-textdomain.php" "$TEXT_DOMAIN" . 2>&1 | sed -n 's/.*file=\([^,]*\),.*/\1/p' | head -1)
|
[ -n "$ann" ] && [ -f "selftest-dotted/$ann" ] \
|
||||||
[ -n "$ann" ] && [ -f "$work/dotted/$ann" ] \
|
|
||||||
&& ok "the annotated path exists on disk: $ann" \
|
&& ok "the annotated path exists on disk: $ann" \
|
||||||
|| bad "the annotated path is not a file: '$ann'"
|
|| bad "the annotated path is not a file: '$ann'"
|
||||||
|
|
||||||
@@ -225,6 +241,46 @@ ci_python_yaml ci/check-workflows.py >/dev/null 2>&1 \
|
|||||||
&& bad "check-workflows.py passed having parsed no file at all" \
|
&& bad "check-workflows.py passed having parsed no file at all" \
|
||||||
|| ok "parsing zero workflow files is fatal"
|
|| ok "parsing zero workflow files is fatal"
|
||||||
|
|
||||||
|
# ------------------------------------------------- no bare interpreter calls
|
||||||
|
# The bug that cost a green Gitea build: this script called `php` directly.
|
||||||
|
# The Gitea runner image has no php and no PyYAML, so a bare call there is
|
||||||
|
# "command not found" — which is a NON-ZERO exit, and therefore indistinguishable
|
||||||
|
# from the failure two of these assertions were written to expect. They went
|
||||||
|
# green having run nothing.
|
||||||
|
#
|
||||||
|
# ci_php and ci_python_yaml exist for this and say so in their comments. lib.sh
|
||||||
|
# is excluded because it is where the real calls live.
|
||||||
|
#
|
||||||
|
# Measured, so the rule is not uniform: the runner HAS python3 and has NO php,
|
||||||
|
# and PyYAML is the only missing module. A bare python3 over the stdlib is
|
||||||
|
# therefore correct and is not flagged.
|
||||||
|
echo
|
||||||
|
echo "→ no ci/ script may call php or python3 directly"
|
||||||
|
# Narrow on purpose. A broad "the word php appears" fires on every string
|
||||||
|
# literal that mentions php, and on the calls INSIDE the docker scripts in
|
||||||
|
# static.sh and site-test.sh, where php is exactly right because the container
|
||||||
|
# is a php image. A check that cries wolf gets commented out, which is worse
|
||||||
|
# than not having it. These two shapes are what the real bug looked like: the
|
||||||
|
# interpreter as the first word of a command, or inside $( ).
|
||||||
|
bare=$(cd "$work/repo" && for f in ci/*.sh; do
|
||||||
|
[ "$f" = ci/lib.sh ] && continue
|
||||||
|
sed 's/#.*//' "$f" \
|
||||||
|
| grep -nE '^[[:space:]]*php[[:space:]]|\$\((cd [^)]*&&[[:space:]]*)?php[[:space:]]' \
|
||||||
|
| sed "s|^|$f:|"
|
||||||
|
# python3 itself IS on the Gitea runner; only PyYAML is missing. So a bare
|
||||||
|
# python3 is fine for anything stdlib (build-zip.sh's zipfile, the JSON
|
||||||
|
# readers in publish-release.sh) and wrong only when yaml is involved.
|
||||||
|
sed 's/#.*//' "$f" \
|
||||||
|
| grep -nE '^[[:space:]]*python3[[:space:]].*(yaml|check-workflows)' \
|
||||||
|
| sed "s|^|$f:|"
|
||||||
|
done)
|
||||||
|
if [ -n "$bare" ]; then
|
||||||
|
printf '%s\n' "$bare" | sed 's/^/ /'
|
||||||
|
bad "a ci/ script calls an interpreter directly instead of ci_php / ci_python_yaml"
|
||||||
|
else
|
||||||
|
ok "every interpreter call goes through the container-capable wrapper"
|
||||||
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
if [ "$fail" != 0 ]; then
|
if [ "$fail" != 0 ]; then
|
||||||
echo "Self-test: $fail of $((pass + fail)) assertions FAILED."
|
echo "Self-test: $fail of $((pass + fail)) assertions FAILED."
|
||||||
|
|||||||
Reference in New Issue
Block a user