From 01dfaba8ec6418393d685633eb7759fc554dbb47 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 20 Aug 2026 16:31:09 +0400 Subject: [PATCH 1/4] Build/Test Tools: Reset the metadata lazyload queue before each test. `tear_down()` already resets the queue after every test, but fixtures created in `wpSetUpBeforeClass()` run outside of any test's `tear_down()`. Anything queued while building class fixtures therefore leaks into the first test of that class. See #65893. --- tests/phpunit/includes/abstract-testcase.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/phpunit/includes/abstract-testcase.php b/tests/phpunit/includes/abstract-testcase.php index 98b3456935716..20b14f5e680ba 100644 --- a/tests/phpunit/includes/abstract-testcase.php +++ b/tests/phpunit/includes/abstract-testcase.php @@ -122,6 +122,16 @@ public function set_up() { $this->clean_up_global_scope(); + /* + * Reset the metadata lazyload queue before each test. + * + * `tear_down()` resets the queue after every test, but fixtures created + * in `wpSetUpBeforeClass()` run outside of any test's `tear_down()`. + * Without this reset, anything queued while building class fixtures + * leaks into the first test of the class. + */ + $this->reset_lazyload_queue(); + /* * When running core tests, ensure that post types and taxonomies * are reset for each test. We skip this step for non-core tests, From aff1a3f3de67e8f0d9e14d219c2e89c2ccd1c0a8 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 20 Aug 2026 16:31:09 +0400 Subject: [PATCH 2/4] Build/Test Tools: Reset the global `$comment` between tests. `tear_down()` resets `comment_alt`, `comment_depth` and `comment_thread_alt`, but not `comment` itself, so a test that assigns `$GLOBALS['comment']` leaks it into every later test. `get_comment()` falls back to that global, so consumers silently observe the previous test's comment. See #65893. --- tests/phpunit/includes/abstract-testcase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/includes/abstract-testcase.php b/tests/phpunit/includes/abstract-testcase.php index 20b14f5e680ba..8467e9be9fe07 100644 --- a/tests/phpunit/includes/abstract-testcase.php +++ b/tests/phpunit/includes/abstract-testcase.php @@ -217,7 +217,7 @@ public function tear_down() { } // Reset comment globals. - $comment_globals = array( 'comment_alt', 'comment_depth', 'comment_thread_alt' ); + $comment_globals = array( 'comment', 'comment_alt', 'comment_depth', 'comment_thread_alt' ); foreach ( $comment_globals as $global ) { $GLOBALS[ $global ] = null; } From 9610f3450aa7b7197b6a1909b11cfcf82af963ba Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 20 Aug 2026 16:31:09 +0400 Subject: [PATCH 3/4] Build/Test Tools: Reset `$_SERVER` after each test. `set_up()` already resets `$_SERVER` before each test, but class fixtures created in `wpSetUpBeforeClass()` run before the first `set_up()` of a class. Values such as `HTTPS` left behind by a previous class are therefore still in place while those fixtures are built. See #65893. --- tests/phpunit/includes/abstract-testcase.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/phpunit/includes/abstract-testcase.php b/tests/phpunit/includes/abstract-testcase.php index 8467e9be9fe07..e57b901714d0c 100644 --- a/tests/phpunit/includes/abstract-testcase.php +++ b/tests/phpunit/includes/abstract-testcase.php @@ -240,6 +240,18 @@ public function tear_down() { $this->_restore_hooks(); wp_set_current_user( 0 ); + /* + * When running core tests, reset `$_SERVER` after each test. + * + * `set_up()` performs the same reset before each test, but class fixtures + * created in `wpSetUpBeforeClass()` run before the first `set_up()` of a + * class. Without this reset, `$_SERVER` values left behind by a previous + * class are still in place while those fixtures are created. + */ + if ( defined( 'WP_RUN_CORE_TESTS' ) && WP_RUN_CORE_TESTS ) { + $this->reset__SERVER(); + } + $this->reset_lazyload_queue(); WP_Style_Engine_CSS_Rules_Store::remove_all_stores(); From f646cd18ee72355032911d7d857b3a9478b75dbe Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 20 Aug 2026 16:31:10 +0400 Subject: [PATCH 4/4] Build/Test Tools: Capture `$ignore_files` before class fixtures run. `$ignore_files` records the uploads directory contents so that `remove_added_uploads()` can tell pre-existing files from files added by tests. It was captured on the first `set_up()`, which runs after the first class's `wpSetUpBeforeClass()`. Files uploaded by those fixtures were treated as pre-existing and permanently exempted from cleanup. Capture it once in `set_up_before_class()` instead, before any fixture runs. The check is against `null` rather than a falsy value so that an empty uploads directory still counts as captured; otherwise the scan repeats at every class boundary and pins files left behind by earlier classes. See #65893. --- tests/phpunit/includes/abstract-testcase.php | 31 +++++++++++++------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/tests/phpunit/includes/abstract-testcase.php b/tests/phpunit/includes/abstract-testcase.php index e57b901714d0c..a55b2e48aa2ed 100644 --- a/tests/phpunit/includes/abstract-testcase.php +++ b/tests/phpunit/includes/abstract-testcase.php @@ -75,6 +75,20 @@ public static function set_up_before_class() { $class = get_called_class(); + /* + * Record the contents of the uploads directory before any class fixtures + * are created, so that `remove_added_uploads()` can tell pre-existing + * files apart from files added by the tests. + * + * This must happen before `wpSetUpBeforeClass()` runs. Capturing it later + * would treat files uploaded by the first class's fixtures as + * pre-existing, permanently exempting them from cleanup and leaving them + * on disk for the rest of the run. + */ + if ( null === self::$ignore_files ) { + self::$ignore_files = self::scan_user_uploads(); + } + if ( method_exists( $class, 'wpSetUpBeforeClass' ) ) { call_user_func( array( $class, 'wpSetUpBeforeClass' ), static::factory() ); } @@ -112,10 +126,6 @@ public function set_up() { $this->factory = static::factory(); - if ( ! self::$ignore_files ) { - self::$ignore_files = $this->scan_user_uploads(); - } - if ( ! self::$hooks_saved ) { $this->_backup_hooks(); } @@ -1552,9 +1562,10 @@ public function rmdir( $path ) { /** * Deletes files added to the `uploads` directory during tests. * - * This method works in tandem with the `set_up()` and `rmdir()` methods: - * - `set_up()` scans the `uploads` directory before every test, and stores - * its contents inside of the `$ignore_files` property. + * This method works in tandem with the `set_up_before_class()` and `rmdir()` methods: + * - `set_up_before_class()` scans the `uploads` directory once, before any + * test or class fixture has run, and stores its contents inside of the + * `$ignore_files` property. * - `rmdir()` and its helper methods only delete files that are not listed * in the `$ignore_files` property. If called during `tear_down()` in tests, * this will only delete files added during the previously run test. @@ -1572,7 +1583,7 @@ public function remove_added_uploads() { * @param string $dir Path to the directory to scan. * @return string[] List of file paths. */ - public function files_in_dir( $dir ) { + public static function files_in_dir( $dir ) { $files = array(); $iterator = new RecursiveDirectoryIterator( $dir ); @@ -1593,14 +1604,14 @@ public function files_in_dir( $dir ) { * * @return string[] List of file paths. */ - public function scan_user_uploads() { + public static function scan_user_uploads() { static $files = array(); if ( ! empty( $files ) ) { return $files; } $uploads = wp_upload_dir(); - $files = $this->files_in_dir( $uploads['basedir'] ); + $files = self::files_in_dir( $uploads['basedir'] ); return $files; }