Skip to content
Draft
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
130 changes: 128 additions & 2 deletions .github/workflows/reusable-upgrade-testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,18 @@ jobs:
# - Updates to the version of WordPress being tested.
# - Updates the database.
# - Checks the version of WordPress after the upgrade.
# - Serves the upgraded site and confirms it renders.
upgrade-tests:
name: PHP ${{ inputs.php }} with ${{ 'mariadb' == inputs.db-type && 'MariaDB' || 'MySQL' }} ${{ inputs.db-version }}${{ inputs.multisite && ' multisite' || '' }}
permissions:
contents: read
runs-on: ${{ inputs.os }}
timeout-minutes: 20

env:
WP_ADMIN_USER: admin
WP_ADMIN_PASSWORD: password

services:
database:
image: ${{ inputs.db-type }}:${{ inputs.db-version }}
Expand Down Expand Up @@ -97,8 +102,8 @@ jobs:
- name: Install WordPress
run: |
wp core ${{ inputs.multisite && 'multisite-install' || 'install' }} \
--url=http://localhost/ --title="Upgrade Test" --admin_user=admin \
--admin_password=password --admin_email=me@example.org --skip-email
--url=http://localhost/ --title="Upgrade Test" --admin_user="${WP_ADMIN_USER}" \
--admin_password="${WP_ADMIN_PASSWORD}" --admin_email=me@example.org --skip-email

- name: Pre-upgrade version check
run: wp core version
Expand Down Expand Up @@ -135,3 +140,124 @@ jobs:

- name: Post-upgrade version check
run: wp core version

- name: Post-upgrade smoke check
# A network keeps its domain in wp-config.php and the database, not just an option.
if: ${{ ! inputs.multisite }}
run: |
site_url="http://127.0.0.1:${SITE_PORT}"
server_log="${RUNNER_TEMP}/server.log"
jar="${RUNNER_TEMP}/cookies.txt"
response="${RUNNER_TEMP}/response.txt"
code="${RUNNER_TEMP}/http-code.txt"
http_code=''

# None of these three are undone, so this step has to stay last.
wp option update home "${site_url}"
wp option update siteurl "${site_url}"
# A cron spawn and the update checks both make requests no check asked for, and the
# update checks leave the runner for api.wordpress.org.
wp config set DISABLE_WP_CRON true --raw
wp config set WP_HTTP_BLOCK_EXTERNAL true --raw

# opcache counts the built-in server as a web SAPI, not as the CLI, so it runs the
# JIT that the runner turns on and stock PHP does not. That JIT crashed the server
# on 38 of 1600 runs. With it off, none of 1600 crashed.
php -d opcache.jit=disable -S "127.0.0.1:${SITE_PORT}" -t . > "${server_log}" 2>&1 &
server_pid=$!

stop_server() {
local status=$?
kill "${server_pid}" 2> /dev/null || true
wait "${server_pid}" 2> /dev/null || true
[ "${status}" -eq 0 ] || cat "${server_log}" || true
exit "${status}"
}
trap stop_server EXIT

# A poll that cannot connect prints 000 and exits 7, which would end the step here.
deadline=$(( SECONDS + 30 ))
while :; do
ready_code="$(curl --silent --max-time 5 --output /dev/null --write-out '%{http_code}' "${site_url}/" || true)"
[ "${ready_code}" = '000' ] || break
[ "${SECONDS}" -lt "${deadline}" ] || { echo '::error::the server never answered'; exit 1; }
sleep 1
done

# Prints what came back and fails the step.
fail() {
echo "::error::${1}"
tail -n 20 "${response}"
exit 1
}

# Requests a path, leaving the body in ${response} and the status in ${http_code}.
# An HTTP error is a result and comes back to the caller. No reply at all is not:
# the server is gone, and the log the trap prints says why.
fetch() {
local path="${1}" status=0
shift
: > "${response}"
curl --silent --show-error --max-time 30 --output "${response}" \
--write-out '%{http_code}' --cookie "${jar}" --cookie-jar "${jar}" \
"$@" "${site_url}${path}" > "${code}" || status=$?
http_code="$(cat "${code}")"
[ "${status}" -eq 0 ] || [ "${status}" -eq 22 ] \
|| fail "${path} got no reply and curl exited ${status}"
return "${status}"
}

# Fails the step unless the response arrives whole and contains every given string.
check() {
local path="${1}" marker status=0
shift
fetch "${path}" --fail-with-body || status=$?
[ "${status}" -eq 0 ] || fail "${path} returned HTTP ${http_code}"
for marker in "$@"; do
grep -qF -e "${marker}" "${response}" || fail "${path} did not contain: ${marker}"
done
echo "ok ${path}"
}

version="$(wp core version)"

# A fatal after the first bytes leaves a 200 holding half a page, so every check also
# asks for the end of the document. Unlike an error string, that cannot be reworded.
check '/' "content=\"WordPress ${version}\"" '</html>'
check '/?p=1' 'Hello world!' '</html>'

fetch '/?rest_route=/' --fail-with-body || fail "/?rest_route=/ returned HTTP ${http_code}"
jq -e '.name == "Upgrade Test"' "${response}" > /dev/null \
|| fail 'the REST index was not the expected JSON'
echo 'ok /?rest_route=/'

# The page prints this for a database that needs no upgrade and for one that holds no
# install at all. The checks above have already ruled the second one out.
check '/wp-admin/upgrade.php' 'No Update Required' '</html>'
check '/wp-login.php' 'id="loginform"' '</html>'

fetch '/?p=99999999' || true
[ "${http_code}" = '404' ] || fail "a missing post returned ${http_code}, expected 404"
grep -qF '</html>' "${response}" || fail 'the 404 page was cut short'
echo 'ok 404 handling'

login_status=0
fetch '/wp-login.php' --fail-with-body \
--data-urlencode "log=${WP_ADMIN_USER}" --data-urlencode "pwd=${WP_ADMIN_PASSWORD}" || login_status=$?
[ "${login_status}" -eq 0 ] || fail "logging in returned HTTP ${http_code}"
grep -qF 'wordpress_logged_in_' "${jar}" || fail 'logging in did not set an authentication cookie'
echo 'ok login'

# The admin bar only renders for a logged-in user, and /wp-admin/ redirects when logged out.
check '/wp-admin/' 'id="wpadminbar"' '</html>'

# A fatal that lands after the markers, or during shutdown, still leaves a broken site.
# The server always writes a start line, so an empty log means the check reads nothing.
[ -s "${server_log}" ] || { echo '::error::the server wrote no log'; exit 1; }
if grep -qE 'Fatal error|Uncaught|Segmentation fault' "${server_log}"; then
echo '::error::the server logged a fatal error'
exit 1
fi
echo 'ok server log'
env:
SITE_PORT: 8889
Loading