2026-09-16 10:49:52 +02:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# Install the PREVIOUS version into a throwaway shop and let WordPress update
|
|
|
|
|
# it from the published feed, on its own.
|
|
|
|
|
#
|
|
|
|
|
# This is the only check that proves the whole chain: header, filter, feed,
|
|
|
|
|
# download address, zip layout and the folder WordPress unpacks into. Every
|
|
|
|
|
# part of it can be individually correct and the update still not arrive —
|
|
|
|
|
# a package URL on an address the shop cannot reach, a zip whose top-level
|
|
|
|
|
# directory is not the plugin slug, a version WordPress does not consider
|
|
|
|
|
# newer. Run it after a release.
|
|
|
|
|
#
|
|
|
|
|
# FROM_VERSION=1.3.1 ci/update-smoke.sh
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
cd "$(dirname "$0")/.."
|
|
|
|
|
. ci/lib.sh
|
|
|
|
|
ci_load_conf
|
|
|
|
|
|
|
|
|
|
FROM_VERSION="${FROM_VERSION:-0.0.1}"
|
|
|
|
|
expected=$(ci_plugin_version)
|
|
|
|
|
|
|
|
|
|
id="wpup-$$"
|
|
|
|
|
net="$id-net"
|
|
|
|
|
cleanup() {
|
|
|
|
|
docker rm -f "$id-db" "$id-cli" >/dev/null 2>&1 || true
|
|
|
|
|
docker volume rm -f "$id-wp" >/dev/null 2>&1 || true
|
|
|
|
|
docker network rm "$net" >/dev/null 2>&1 || true
|
|
|
|
|
}
|
|
|
|
|
trap cleanup EXIT
|
|
|
|
|
|
|
|
|
|
echo "Installing $PLUGIN_SLUG $FROM_VERSION, then asking WordPress to update it. Expecting $expected."
|
|
|
|
|
|
|
|
|
|
docker network create "$net" >/dev/null
|
|
|
|
|
docker volume create "$id-wp" >/dev/null
|
|
|
|
|
docker run -d --name "$id-db" --network "$net" \
|
|
|
|
|
-e MARIADB_ROOT_PASSWORD=root -e MARIADB_DATABASE=wordpress \
|
|
|
|
|
-e MARIADB_USER=wordpress -e MARIADB_PASSWORD=wordpress mariadb:lts >/dev/null
|
|
|
|
|
docker run --rm -v "$id-wp:/var/www/html" alpine chown 33:33 /var/www/html
|
|
|
|
|
docker run -d --name "$id-cli" --network "$net" -v "$id-wp:/var/www/html" \
|
|
|
|
|
-e WORDPRESS_DB_HOST="$id-db" wordpress:cli sleep infinity >/dev/null
|
|
|
|
|
docker exec -u root "$id-cli" sh -c 'echo memory_limit=512M > /usr/local/etc/php/conf.d/zz-memory.ini'
|
|
|
|
|
wp() { docker exec "$id-cli" wp "$@"; }
|
|
|
|
|
|
|
|
|
|
for i in $(seq 1 60); do
|
|
|
|
|
docker exec "$id-db" mariadb -uwordpress -pwordpress -e 'select 1' wordpress >/dev/null 2>&1 && break
|
|
|
|
|
[ "$i" = 60 ] && { echo "MariaDB never came up." >&2; exit 1; }
|
|
|
|
|
sleep 2
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
ci_step "a shop running the previous version"
|
|
|
|
|
wp core download --quiet
|
|
|
|
|
wp config create --dbname=wordpress --dbuser=wordpress --dbpass=wordpress --dbhost="$id-db" --quiet
|
|
|
|
|
wp core install --url=http://plugin.test --title="update smoke" --admin_user=admin \
|
|
|
|
|
--admin_password=admin --admin_email=ci@example.com --skip-email --quiet
|
|
|
|
|
[ "$NEEDS_WOOCOMMERCE" = yes ] && wp plugin install woocommerce --activate --quiet
|
|
|
|
|
|
|
|
|
|
# The working tree, with the version wound back. WordPress compares the feed
|
|
|
|
|
# against this header and nothing else, so this is what makes it an "older"
|
|
|
|
|
# install as far as the update check is concerned.
|
|
|
|
|
./ci/dist-files.sh | tr '\n' '\0' | tar --null -T - -cf - \
|
|
|
|
|
| docker exec -i "$id-cli" sh -c \
|
|
|
|
|
"mkdir -p /var/www/html/wp-content/plugins/$PLUGIN_SLUG \
|
|
|
|
|
&& tar -xf - -C /var/www/html/wp-content/plugins/$PLUGIN_SLUG"
|
|
|
|
|
docker exec "$id-cli" sed -i -E \
|
|
|
|
|
"s/^([[:space:]]*\*[[:space:]]*Version:[[:space:]]*).*/\1$FROM_VERSION/" \
|
|
|
|
|
"/var/www/html/wp-content/plugins/$PLUGIN_SLUG/$PLUGIN_MAIN"
|
|
|
|
|
wp plugin activate "$PLUGIN_SLUG" --quiet
|
|
|
|
|
installed=$(wp plugin get "$PLUGIN_SLUG" --field=version)
|
|
|
|
|
echo " installed $installed"
|
|
|
|
|
[ "$installed" = "$FROM_VERSION" ] || { echo "Winding the version back did not take." >&2; exit 1; }
|
|
|
|
|
|
|
|
|
|
ci_step "what WordPress thinks, unprompted"
|
|
|
|
|
# Not the plugin's own code — WordPress's update check, the same one that runs
|
|
|
|
|
# on a schedule in a live shop.
|
|
|
|
|
wp plugin update --all --dry-run 2>&1 | sed 's/^/ /' || true
|
|
|
|
|
available=$(wp plugin list --name="$PLUGIN_SLUG" --field=update)
|
|
|
|
|
echo " update status: $available"
|
|
|
|
|
[ "$available" = available ] || {
|
|
|
|
|
echo "WordPress was not offered an update. The header, the filter or the feed is wrong." >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ci_step "letting WordPress install it"
|
|
|
|
|
wp plugin update "$PLUGIN_SLUG" 2>&1 | sed 's/^/ /'
|
|
|
|
|
|
|
|
|
|
now=$(wp plugin get "$PLUGIN_SLUG" --field=version)
|
|
|
|
|
status=$(wp plugin list --name="$PLUGIN_SLUG" --field=status)
|
|
|
|
|
echo
|
|
|
|
|
echo " version after update: $now (was $FROM_VERSION)"
|
|
|
|
|
echo " status: $status"
|
|
|
|
|
|
|
|
|
|
[ "$now" = "$expected" ] || { echo "Expected $expected after the update." >&2; exit 1; }
|
|
|
|
|
[ "$status" = active ] || { echo "The plugin is no longer active after updating itself." >&2; exit 1; }
|
|
|
|
|
|
2026-09-16 14:00:21 +02:00
|
|
|
ci_step "one copy of the plugin, not two"
|
|
|
|
|
# A zip whose top-level directory is not the slug installs a SECOND copy beside
|
|
|
|
|
# the first instead of replacing it, and the old one stays active. This used to
|
|
|
|
|
# count the directories into a variable and then never look at it — a check
|
|
|
|
|
# that could not fail, sitting under a comment explaining exactly what it was
|
|
|
|
|
# for.
|
2026-09-16 10:49:52 +02:00
|
|
|
docker exec "$id-cli" ls /var/www/html/wp-content/plugins | sed 's/^/ /'
|
2026-09-16 14:00:21 +02:00
|
|
|
copies=$(docker exec "$id-cli" sh -c \
|
|
|
|
|
"ls -d /var/www/html/wp-content/plugins/$PLUGIN_SLUG* 2>/dev/null | wc -l" | tr -d '[:space:]')
|
|
|
|
|
echo " directories starting with $PLUGIN_SLUG: $copies"
|
|
|
|
|
[ "$copies" = 1 ] || {
|
|
|
|
|
echo "Expected exactly one, got $copies. The zip unpacked to a directory that is not the slug." >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ci_step "the code that is now running reports the new version"
|
|
|
|
|
# Files can be replaced while the running code still reports the old version —
|
|
|
|
|
# an opcache that was not invalidated, or a constant defined somewhere else.
|
|
|
|
|
# The constant name is read out of the plugin header rather than hard-coded:
|
|
|
|
|
# this script is copied between plugins, and a name that does not exist over
|
|
|
|
|
# there prints an empty value and carries on cheerfully.
|
|
|
|
|
const=$(sed -n -E "s/^[[:space:]]*define\([[:space:]]*'([A-Z_]*VERSION)'.*/\1/p" "$PLUGIN_MAIN" | head -1)
|
|
|
|
|
if [ -n "$const" ]; then
|
|
|
|
|
running=$(wp eval "echo defined( '$const' ) ? $const : '';" | tr -d '[:space:]')
|
|
|
|
|
echo " $const = ${running:-<undefined>}"
|
|
|
|
|
[ "$running" = "$expected" ] || {
|
|
|
|
|
echo "The files say $expected but the running code says '${running:-undefined}'." >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
echo " $PLUGIN_MAIN defines no *_VERSION constant — nothing to cross-check."
|
|
|
|
|
fi
|
2026-09-16 10:49:52 +02:00
|
|
|
|
|
|
|
|
wp plugin list --name="$PLUGIN_SLUG" --field=update | grep -qx none \
|
|
|
|
|
&& echo " WordPress no longer offers an update — the header and the feed agree."
|
|
|
|
|
|
|
|
|
|
echo
|
|
|
|
|
echo "WordPress found, downloaded and installed $expected from the published feed on its own."
|