Build/Test Tools: Add plugin compatibility testing workflow. - #13198
Build/Test Tools: Add plugin compatibility testing workflow.#13198adamsilverstein wants to merge 4 commits into
Conversation
Core CI covers core itself, but nothing checks that a new version of WordPress can still boot with popular plugins active. When a plugin's assumptions about core stop holding the result is a fatal error on every request, which is a white screen for real sites and is only discovered after release. Add a workflow that fetches the most popular plugins from the WordPress.org API at run time, then installs and activates each one on its own against the version of WordPress under test. A fatal is caught whether it happens on activation, while WP-CLI loads WordPress, on a front end or login request, or in the debug log, so a white screen with error display turned off is still detected. Failures are reported per plugin in the workflow summary and one broken plugin never stops the rest of the shard from being tested. Plugins that cannot be downloaded are reported as skipped rather than failed so that a network flake does not turn the run red. The run is manual or weekly rather than part of every commit, since a third party plugin breaking should be a signal to release leads, not a red check on unrelated work.
The first run of this workflow reported four failures that were not fatals. Core refuses to activate a plugin whose `Requires Plugins` dependency is missing, which every WooCommerce extension hits when plugins are tested one at a time, and WP-CLI exits non-zero when a plugin redirects while loading. Both are correct behaviour, so record them as skipped and reserve a failure for an actual fatal. A front end request also reported the nonsense status "200000", because the curl fallback appended to output curl had already written. Capture the exit code separately so a stalled transfer is reported as what it is. The stall itself came from WordPress spawning WP-Cron as a loopback request that the single threaded built-in server could not answer while still serving the request that spawned it. Disable WP-Cron and give the server workers so plugin loopback requests cannot deadlock it. Ignore the zizmor unpinned image finding on the database service, which cannot be pinned to a digest while the version is an input.
The WordPress.org API caps `per_page` at 250 and quietly returns 250 for anything larger, so asking for more than that silently tested fewer plugins than requested. Page through the API instead and trim to the requested count, de-duplicating across pages because popularity ordering can shift between two requests. Reject a count above 1000 with a clear message rather than truncating without saying so. Size the shards to the plugin count rather than always splitting into five, so a run of 10 does not spin up five near empty jobs and a run of 250 is not squeezed into the same five. Point pull request and push runs at the latest stable release with a small count. Those runs exist to check that this workflow still works, and a genuine ecosystem fatal against nightly should not sit as a red check on every later change to these files.
A run that builds a single shard logged "across 1 shards".
While Tide is still running, its been generally unsupported for years and I've long considered trying to get it shut down as I'm unsure how many entities are even making use of its available audit data set (with the exception of the PHP Compat Checker plugin). If there was interest in having this functionality more formally supported within Tide and then in consuming the resulting data, then that seems great to me but we'd likely want to get someone / a team / a sponsor to help ensure Tide continues to be supported after this implementation completes. |
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
This seems worth a follow-up to me given they make the popular list. So in general, if a plugin has |
Claude Code built this workflow, requirements and steering from me:
Adds a workflow that installs the top plugins from the WordPress.org directory one at a time against a given version of WordPress and checks that nothing fatals. The idea is to catch the WP Rocket class of breakage - a popular plugin fataling on every request against a new core version, taking real sites offline - while there is still time to fix core or reach the plugin author. See adamsilverstein#64 and https://x.com/austinginder/status/2090199834787541074
A Trac ticket is being opened for this and will be linked here once it exists. Core needs a ticket before anything can be committed, so this stays a draft until then.
How it works
plugin-compatibility.ymlquerieshttps://api.wordpress.org/plugins/info/1.2/for the most popular plugins at run time, so there is no list to go stale. The API capsper_pageat 250 and returns 250 without complaint for anything larger, so the fetch pages through the API and trims to the requested count, de-duplicating across pages because popularity ordering can shift between two requests. Counts above 1000 are rejected with a message rather than quietly truncated.Shards are sized rather than counted: it aims for 25 plugins per shard up to 10 shards, so a run of 10 does not spin up five near empty jobs and a run of 250 is not squeezed into the same five. Past 250 the shards get longer instead of more numerous.
Each shard calls
reusable-plugin-compatibility.yml, which installs WordPress withWP_DEBUGandWP_DEBUG_LOGon, startsphp -S, and then for each plugin installs it, activates it, runswp eval, requests/and/wp-login.php, checkswp-content/debug.log, and removes the plugin before moving to the next one.WP_DEBUG_DISPLAYis left off on purpose so the site behaves the way a production site does - a fatal is an empty page and an HTTP 500 rather than a printed stack trace. The fatal error handler is disabled too, otherwise recovery mode swallows the fatal and deactivates the plugin mid-test.Runs are
workflow_dispatch(so a release lead can point it at a beta or RC as part of the pre-release checklist, and pick how many plugins to cover) and weekly against nightly. It is signal-only, not a check on every commit - a third party plugin breaking shouldn't turn core CI red on unrelated work.What the first CI run turned up
The workflow ran on this PR through its own
pull_requestpaths trigger, which was the point of including that trigger. Three of five shards passed and two failed, and the failures were worth having:Four WooCommerce extensions (
woocommerce-payments,google-listings-and-ads,woocommerce-paypal-payments,woocommerce-gateway-stripe) were reported as failed when core had simply refused to activate them -Requires Plugins: woocommerce, which is unmet by design when plugins are tested one at a time. Those are now recorded as SKIPPED.wp-reviews-plugin-for-googlewas reported as failed because it callswp_safe_redirect()while loading, which makes WP-CLI exit non-zero without anything being broken. Thewp evalcheck now only fails on an actual fatal.instagram-feedwas reported with the nonsense statusHTTP 200000, which was a real bug - the curl fallback appended to output curl had already written. That is fixed by capturing the exit code separately. Digging into why the transfer stalled found something more interesting: WordPress spawned WP-Cron as a loopback request, and the single threaded built-in server could not answer it while still serving the request that spawned it, so the two deadlocked for the full 60 second curl timeout. WP-Cron is now disabled and the server getsPHP_CLI_SERVER_WORKERS.Underneath that deadlock was a genuine fatal on nightly, which is exactly the class of thing this workflow is for:
It fires from a cron callback, so with WP-Cron disabled the current checks no longer reach it. Running due cron events through WP-CLI would catch it deterministically and is worth doing as a follow up. It looks like it deserves its own ticket either way.
pull_requestandpushruns now test the latest stable release with 10 plugins instead of nightly with 100. Those runs exist to check that the workflow itself still works, and a real ecosystem fatal against nightly would otherwise sit as a red check on every later change to these two files. Scheduled and manually dispatched runs keep nightly and the full count, which is where the ecosystem signal belongs.How has this been tested
actionlint1.7.12 withshellcheck0.10.0 on PATH reports 0 errors for both new files and 0 across the whole.github/workflowsdirectory. The repo's own actionlint and Zizmor jobs pass. A separatezizmorcode scanning check failed on a newunpinned-imagesalert for the database service image; since the image comes fromdb-typeanddb-versioninputs it cannot be pinned to a digest, so it carries an inline# zizmor: ignore[unpinned-images]with a comment explaining why.zizmor1.24.1 locally reports no findings after that.The matrix builder was run locally against the live API. 10 gave 1 shard of 10, 100 gave 4 shards of 25, 250 gave 10 shards of 25, 300 gave 10 shards of 30 across 2 API pages, and 600 gave 10 shards of 60 across 3 API pages with 600 unique slugs. A count of 1001,
abc, and 0 each exit 1 with a message.The per-plugin loop was extracted from the YAML and run unchanged in a container with WP-CLI, PHP 8.3 and MySQL 8.4, which is as close to the runner as could be managed locally:
instagram-feedandwp-reviews-plugin-for-googlenow PASS,classic-editorstill PASS, job exits 0.template_redirectcaught as HTTP 500 on/, fatal onshutdowncaught at activation, and a fatal inwp_footer(which still returns HTTP 200 because output already started) caught indebug.log. A healthy plugin in the same list still passed and cleanup leftwp-content/pluginsandactive_pluginsclean.latest, matching what apull_requestrun does - all 10 passed.Not tested: multisite, PHP versions other than 8.3, MariaDB, and the Slack notification and failed-workflow jobs, which are copied from
install-testing.ymlunchanged.Types of changes
.github/workflows/plugin-compatibility.yml, the caller, which pages the WordPress.org API and fans out to a matrix sized to the plugin count..github/workflows/reusable-plugin-compatibility.yml, which installs WordPress and tests one shard of plugins in isolation.Requires Pluginsdependency, a WP-CLI redirect, and a failed download as skipped rather than failed.Open questions
.version-support-*.jsonfor the PHP version instead of pinning8.3, or is one current PHP version the right scope for a smoke test?AI Use
Code and description written with 🤖 Claude Code, working from acceptance criteria in the linked issue. I will review and test.