Kristian asked the family to check WordPress 7.1.2. Run pinned rather than against "latest", because latest drifts and then nobody knows which version a green belonged to: PHP 7.4 69 assertions, 0 failures "none from this plugin" PHP 8.3 69 assertions, 0 failures "none from this plugin" PHP 8.4 69 assertions, 0 failures "none from this plugin" The container reported WordPress 7.1.2 and WooCommerce 11.1.2 on all three, read out of the run rather than from the variable it was given. So there is no problem with 7.1.2 here. The findings were in the reporting, not the code. The headers were stale: "Tested up to: 7.1" and "WC tested up to: 11.1.0" against current 7.1.2 and 11.1.2. The WooCommerce one is the visible half — a shop on 11.1.2 sees an untested warning on the WooCommerce status page, and on this plugin that banner sits in front of somebody deciding whether to update a discount engine. And the suite said "All assertions passed" without a number, which is the exact failure every other check here was fixed for. It now prints the count. 🔴 Then the floor for that count found a bigger hole than it was written for. Putting it at the end of the smoke file does nothing: wp eval-file runs the file in function scope, so a `return` partway through skips everything after it, including the floor. Measured — truncating tests/smoke.php before the shipping section ran two thirds of the assertions and ci/site-test.sh reported GREEN. A file cannot assert about its own early exit. ci/site-test.sh now captures the output and checks the total against MIN_SITE_ASSERTIONS from outside, where an early return cannot reach, and fails when the summary line is missing altogether. The in-file floor stays for a short run that does reach the end. Proven with the same fixture that exposed it: the truncated suite now exits 1 saying it printed no assertion total. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
127 lines
4.8 KiB
Bash
127 lines
4.8 KiB
Bash
# Shared helpers for the ci/ scripts. Sourced, never executed.
|
|
#
|
|
# Nothing in here is specific to one plugin — ci/plugin.conf holds that.
|
|
|
|
# shellcheck shell=bash
|
|
|
|
ci_repo_root() {
|
|
cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd
|
|
}
|
|
|
|
# Load ci/plugin.conf and fail loudly on anything it forgot to set. A missing
|
|
# value would otherwise turn into an empty path and a test that passes by
|
|
# testing nothing.
|
|
ci_load_conf() {
|
|
local root
|
|
root="$(ci_repo_root)"
|
|
|
|
# shellcheck source=/dev/null
|
|
. "$root/ci/plugin.conf"
|
|
|
|
# MIN_* included on purpose: a port that copies this file but forgets them
|
|
# leaves the floors empty, every comparison becomes a no-op, and the hollow
|
|
# checks these were added to fix come back silently — the same shape of bug.
|
|
local missing=
|
|
for var in PLUGIN_SLUG PLUGIN_MAIN TEXT_DOMAIN NEEDS_WOOCOMMERCE MIN_SHIPPED_PHP_FILES MIN_TRANSLATION_CALLS RELEASE_REPO WPMGR_MENU_SHA OLD_PLUGIN_NAME MIN_SITE_ASSERTIONS; do
|
|
[ -n "${!var:-}" ] || missing="$missing $var"
|
|
done
|
|
if [ -n "$missing" ]; then
|
|
echo "ci/plugin.conf is missing:$missing" >&2
|
|
return 1
|
|
fi
|
|
|
|
if [ ! -f "$root/$PLUGIN_MAIN" ]; then
|
|
echo "ci/plugin.conf points PLUGIN_MAIN at '$PLUGIN_MAIN', which does not exist." >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# The version WordPress will compare against the update feed. Read from the
|
|
# plugin header, which is the only copy WordPress ever looks at.
|
|
ci_plugin_version() {
|
|
sed -n -E 's/^[[:space:]]*\*?[[:space:]]*Version:[[:space:]]*([^[:space:]]+).*/\1/p' \
|
|
"$(ci_repo_root)/$PLUGIN_MAIN" | head -1
|
|
}
|
|
|
|
# Read any other plugin header field, e.g. ci_plugin_header 'Requires at least'.
|
|
ci_plugin_header() {
|
|
sed -n -E "s/^[[:space:]]*\*?[[:space:]]*$1:[[:space:]]*(.*)/\1/p" \
|
|
"$(ci_repo_root)/$PLUGIN_MAIN" | head -1 | sed -E 's/[[:space:]]+$//'
|
|
}
|
|
|
|
# Run a shell script against a checkout of this repository, inside a container.
|
|
#
|
|
# The repository goes in over a tarball and `docker cp`, never a bind mount: a
|
|
# Gitea Actions job runs inside a container talking to the HOST's docker
|
|
# daemon, so a host path here lands on an empty directory nobody created and
|
|
# the check passes by checking nothing. The symptom reads like a typo in a path.
|
|
#
|
|
# Two details are load-bearing. The script is copied in as a FILE rather than
|
|
# interpolated into `sh -c`, so it can contain $, quotes and newlines without
|
|
# being mangled — which matters the moment it is more than one command. And
|
|
# --entrypoint sh is not decoration: some images (composer:2, for one) have an
|
|
# entrypoint that swallows its arguments.
|
|
#
|
|
# Taken from the subscriptions port, which needed it for composer.
|
|
ci_in_container() { # image script-text
|
|
local image="$1" script="$2"
|
|
local dir cid code=0
|
|
|
|
dir=$(mktemp -d -t wpci-XXXXXX)
|
|
( cd "$(ci_repo_root)" && git ls-files -z | tar --null -T - -cf "$dir/src.tar" )
|
|
printf '%s\n' "$script" > "$dir/run.sh"
|
|
|
|
cid=$(docker create --entrypoint sh -w /src "$image" \
|
|
-c 'mkdir -p /src && tar -xf /tmp/src.tar -C /src && exec sh /tmp/run.sh')
|
|
docker cp "$dir/src.tar" "$cid:/tmp/src.tar" >/dev/null
|
|
docker cp "$dir/run.sh" "$cid:/tmp/run.sh" >/dev/null
|
|
docker start -a "$cid" || code=$?
|
|
docker rm -f "$cid" >/dev/null
|
|
rm -rf "$dir"
|
|
return "$code"
|
|
}
|
|
|
|
# Run one of this repository's PHP scripts.
|
|
#
|
|
# Uses the local php when there is one — a laptop, GitHub's hosted runners —
|
|
# and a container otherwise. The Gitea runner image has no php, and losing a
|
|
# build to "php: command not found" after everything else passed is a silly way
|
|
# to spend an afternoon. Measured: it happened on the very first Gitea run.
|
|
ci_php() {
|
|
if command -v php >/dev/null 2>&1; then
|
|
php "$@"
|
|
return
|
|
fi
|
|
|
|
ci_in_container "php:8.4-cli" "exec php $(printf '%q ' "$@")"
|
|
}
|
|
|
|
# Run one of this repository's python scripts, with PyYAML available.
|
|
#
|
|
# Same shape and the same reason as ci_php above, and it is not hypothetical:
|
|
# the Gitea runner image has NO PyYAML (measured — `import yaml` raises
|
|
# ModuleNotFoundError there), while every laptop and GitHub's hosted runners
|
|
# have it. So a check that merely skips when the import fails is a check that
|
|
# is switched off on exactly one forge — and Gitea is the forge where a
|
|
# workflow that does not parse produces no run and no error to click on.
|
|
#
|
|
# alpine + py3-yaml rather than a python image + pip: no network install, one
|
|
# apk call, the same pinned answer every time.
|
|
ci_python_yaml() {
|
|
if python3 -c 'import yaml' 2>/dev/null; then
|
|
python3 "$@"
|
|
return
|
|
fi
|
|
|
|
ci_in_container "alpine:3.20" \
|
|
"apk add --no-cache python3 py3-yaml >/dev/null && exec python3 $(printf '%q ' "$@")"
|
|
}
|
|
|
|
ci_step() { printf '\n\033[1m→ %s\033[0m\n' "$*"; }
|
|
ci_fail() { printf '\033[31mFAIL\033[0m %s\n' "$*" >&2; }
|
|
|
|
# GitHub and Gitea both render these as annotations on the file and line.
|
|
ci_annotate() { # level file line message
|
|
printf '::%s file=%s,line=%s::%s\n' "$1" "$2" "$3" "$4"
|
|
}
|