Make WordPress Core

Opened 10 hours ago

Last modified 10 hours ago

#65972 new defect (bug)

Test installer writes stale role data back into freshly created tables

Reported by: ekamran Owned by:
Priority: normal Milestone: Awaiting Review
Component: Build/Test Tools Version:
Severity: normal Keywords: has-patch has-unit-tests
Cc: Focuses:

Description (last modified by ekamran)

tests/phpunit/includes/install.php loads wp-settings.php before it drops and recreates the test tables. wp-settings.php instantiates the global WP_Roles object, which reads the roles option from the tables of the previous run. After the tables are dropped, wp_install() calls populate_roles(), which uses that same object. Every add_role() call returns early because the default roles already exist on the stale object, and populate_roles() then writes the stale role set back into the fresh tables with update_option().

The result is that role data from a previous run survives reinstallation. A capability that should not exist can keep coming back on every run until the database is dropped by hand. This affects the Core test installer only, not production WordPress installs.

Steps to reproduce on trunk:

  1. Run the test suite once so the tables exist.
  2. Add one extra capability to the administrator role directly in the database, for example a zzz_stale_cap key inside the wptests_user_roles option.
  3. Run Tests_User_Capabilities::test_all_caps_of_users_are_being_tested normally, with the installer running.

The test fails with "User with administrator role has capabilities that aren't being tested" listing the extra capability, even though the installer dropped and recreated every table. This is the same failure reported against the test runner in https://github.com/WordPress/phpunit-test-runner/issues/110, where hosts have hit it since 2020.

The patch discards the global roles object right after the tables are dropped. wp_roles() recreates it lazily inside populate_roles(), which runs after wp_install() has created the fresh tables, so every run starts from the default roles.

Verified locally on trunk:

  • Poisoned role data plus a normal run currently fails; with the patch the same run passes and the extra capability is gone from the database.
  • Same result through the multisite install path.
  • Fresh databases and repeated runs stay green: full Tests_User_Capabilities class passes single site (753 tests), and the user and capabilities groups pass on multisite except Tests_User_CountUserPosts::test_count_user_posts_for_user_created_after_being_assigned_posts, which fails identically without the patch.
  • WP_TESTS_SKIP_INSTALL=1 keeps its current behaviour, since the installer does not run at all.
  • PHPCS clean with the repository ruleset.
  • Reproduced end to end a second time on a completely fresh clone with a new database and a config created from wp-tests-config-sample.php. The patch applied to that clone heals the same poisoned database.

Attachments (1)

test-install-stale-roles.diff (936 bytes ) - added by ekamran 10 hours ago.

Download all attachments as: .zip

Change History (3)

#1 @ekamran
10 hours ago

  • Description modified (diff)

This ticket was mentioned in PR #13286 on WordPress/wordpress-develop by @ekamran.


10 hours ago
#2

  • Keywords has-unit-tests added

The test installer loads wp-settings.php before it drops and recreates the test tables, so the global WP_Roles object is created from the previous run's data. populate_roles() then sees the default roles as already existing on that object and writes the stale capabilities back into the fresh tables. Role data from a previous run therefore survives reinstallation until the database is dropped by hand. This affects the Core test installer only, not production WordPress installs.

This change discards the roles object right after the tables are dropped. wp_roles() recreates it lazily inside populate_roles(), which runs after wp_install() has created the fresh tables, so every run starts from the default roles.

Reproduction steps, the full verification list and the same patch as a diff are on the ticket. Verified on single site and multisite install paths, on repeated runs, and end to end on a fresh clone with a new database. WP_TESTS_SKIP_INSTALL=1 behaviour is unchanged.

Trac ticket: https://core.trac.wordpress.org/ticket/65972

## Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code, Codex (testing and review)
The investigation, verification and final decisions are mine, and I take responsibility for the change.

Note: See TracTickets for help on using tickets.