Make WordPress Core

Ticket #23290: 23290-phpunit.patch

File 23290-phpunit.patch, 26.0 KB (added by johnjamesjacoby, 3 months ago)

Unit test file for patches 3 & 4

  • tests/phpunit/tests/multisite/wpCacheSwitchToBlog.php

     
     1<?php
     2
     3/**
     4 * Tests for the wp_cache_switch_to_blog() function.
     5 *
     6 * @group ms-required
     7 * @group ms-site
     8 * @group multisite
     9 * @group cache
     10 *
     11 * @covers ::wp_cache_switch_to_blog
     12 */
     13class Tests_Multisite_WpCacheSwitchToBlogFallback extends WP_UnitTestCase {
     14
     15        /**
     16         * Store the original WP_Object_Cache instance.
     17         *
     18         * @var WP_Object_Cache
     19         */
     20        private $original_wp_object_cache;
     21
     22        /**
     23         * Set up before each test.
     24         */
     25        public function set_up() {
     26                global $wp_object_cache;
     27
     28                parent::set_up();
     29
     30                // Store the original cache object to restore later.
     31                $this->original_wp_object_cache = $wp_object_cache;
     32        }
     33
     34        /**
     35         * Tear down after each test.
     36         */
     37        public function tear_down() {
     38                global $wp_object_cache;
     39
     40                // Restore the original cache object.
     41                $wp_object_cache = $this->original_wp_object_cache;
     42
     43                parent::tear_down();
     44        }
     45
     46        /**
     47         * Helper method to add custom global groups for testing.
     48         *
     49         * @param array $groups Group names to add.
     50         */
     51        private function add_test_global_groups( $groups ) {
     52                wp_cache_add_global_groups( $groups );
     53        }
     54
     55        /**
     56         * Test that wp_cache_switch_to_blog() reinitializes the cache.
     57         *
     58         * @ticket 23290
     59         * @covers ::wp_cache_switch_to_blog
     60         */
     61        public function test_reinitializes_cache() {
     62
     63                // Set some cache data before switching.
     64                wp_cache_set( 'test_key', 'test_value', 'test_group' );
     65                $this->assertSame( 'test_value', wp_cache_get( 'test_key', 'test_group' ) );
     66
     67                // Call the fallback function.
     68                wp_cache_switch_to_blog();
     69
     70                // Verify the cache has been reinitialized (non-global groups should be cleared).
     71                $this->assertFalse( wp_cache_get( 'test_key', 'test_group' ) );
     72        }
     73
     74        /**
     75         * Test that wp_cache_switch_to_blog() preserves global group configuration.
     76         *
     77         * The fallback clears all cache data (including global groups) because it calls
     78         * wp_cache_init(). However, it restores the global groups configuration afterward.
     79         *
     80         * @ticket 23290
     81         * @covers ::wp_cache_switch_to_blog
     82         */
     83        public function test_preserves_global_groups_configuration() {
     84                global $wp_object_cache;
     85
     86                // Call the fallback function.
     87                wp_cache_switch_to_blog();
     88
     89                // Verify global groups configuration is preserved after fallback.
     90                $this->assertNotEmpty( $wp_object_cache->global_groups );
     91                $this->assertArrayHasKey( 'users', $wp_object_cache->global_groups );
     92                $this->assertArrayHasKey( 'user_meta', $wp_object_cache->global_groups );
     93                $this->assertArrayHasKey( 'site-options', $wp_object_cache->global_groups );
     94        }
     95
     96        /**
     97         * Test that wp_cache_switch_to_blog() restores global groups from cache object.
     98         *
     99         * When the cache object exists with global_groups configuration, the fallback should
     100         * preserve that configuration rather than discarding it.
     101         *
     102         * @ticket 23290
     103         * @covers ::wp_cache_switch_to_blog
     104         */
     105        public function test_restores_global_groups_from_cache_object() {
     106                global $wp_object_cache;
     107
     108                // Verify the global_groups property exists.
     109                $this->assertObjectHasProperty( 'global_groups', $wp_object_cache );
     110
     111                // Store the count of original global groups for comparison.
     112                $original_count = count( $wp_object_cache->global_groups );
     113
     114                // Call the fallback function.
     115                wp_cache_switch_to_blog();
     116
     117                // Verify global groups are restored (same number of groups).
     118                $this->assertGreaterThan( 0, count( $wp_object_cache->global_groups ) );
     119                $this->assertSame( $original_count, count( $wp_object_cache->global_groups ) );
     120
     121                // Verify key global groups are still present.
     122                $this->assertArrayHasKey( 'users', $wp_object_cache->global_groups );
     123                $this->assertArrayHasKey( 'user_meta', $wp_object_cache->global_groups );
     124                $this->assertArrayHasKey( 'site-options', $wp_object_cache->global_groups );
     125        }
     126
     127        /**
     128         * Test that wp_cache_switch_to_blog() uses default global groups when unavailable.
     129         *
     130         * When the cache object doesn't have a global_groups property, the fallback
     131         * should provide the WordPress default global groups rather than failing.
     132         *
     133         * @ticket 23290
     134         * @covers ::wp_cache_switch_to_blog
     135         */
     136        public function test_uses_default_global_groups_when_unavailable() {
     137                global $wp_object_cache;
     138
     139                // Create a mock cache object without global_groups.
     140                $mock_cache = new stdClass();
     141
     142                // Temporarily replace the global cache.
     143                $wp_object_cache = $mock_cache;
     144
     145                // Call the fallback function.
     146                wp_cache_switch_to_blog();
     147
     148                // Verify a new cache object was created and global groups are set.
     149                $this->assertInstanceOf( 'WP_Object_Cache', $wp_object_cache );
     150                $this->assertObjectHasProperty( 'global_groups', $wp_object_cache );
     151
     152                // Verify default global groups are present.
     153                $expected_groups = array(
     154                        'blog-details',
     155                        'blog-id-cache',
     156                        'blog-lookup',
     157                        'blog_meta',
     158                        'global-posts',
     159                        'image_editor',
     160                        'networks',
     161                        'network-queries',
     162                        'sites',
     163                        'site-details',
     164                        'site-options',
     165                        'site-queries',
     166                        'site-transient',
     167                        'theme_files',
     168                        'translation_files',
     169                        'rss',
     170                        'users',
     171                        'user-queries',
     172                        'user_meta',
     173                        'useremail',
     174                        'userlogins',
     175                        'userslugs',
     176                );
     177
     178                foreach ( $expected_groups as $group ) {
     179                        $this->assertContains( $group, array_keys( $wp_object_cache->global_groups ) );
     180                }
     181        }
     182
     183        /**
     184         * Test that wp_cache_switch_to_blog() preserves non-persistent groups from cache.
     185         *
     186         * @ticket 23290
     187         * @covers ::wp_cache_switch_to_blog
     188         */
     189        public function test_preserves_non_persistent_groups_from_cache_object() {
     190                global $wp_object_cache;
     191
     192                // Verify we have access to the cache structure.
     193                $this->assertObjectHasProperty( 'cache', $wp_object_cache );
     194                $this->assertObjectHasProperty( 'global_groups', $wp_object_cache );
     195
     196                // Add some data to non-persistent groups.
     197                wp_cache_set( 'count_key', 42, 'counts' );
     198                wp_cache_set( 'plugin_key', 'plugin_data', 'plugins' );
     199
     200                // Call the fallback function.
     201                wp_cache_switch_to_blog();
     202
     203                // Verify non-persistent groups are re-added.
     204                $this->assertInstanceOf( 'WP_Object_Cache', $wp_object_cache );
     205
     206                // The actual values won't persist (as they're non-persistent), but the group structure should be maintained.
     207                // We can't directly test the internal structure, but we can verify the function completes without error.
     208                $this->assertTrue( true );
     209        }
     210
     211        /**
     212         * Test that wp_cache_switch_to_blog() uses default non-persistent groups when unavailable.
     213         *
     214         * @ticket 23290
     215         * @covers ::wp_cache_switch_to_blog
     216         */
     217        public function test_uses_default_non_persistent_groups_when_unavailable() {
     218                global $wp_object_cache;
     219
     220                // Create a minimal mock cache object.
     221                $mock_cache = new stdClass();
     222
     223                // Temporarily replace the global cache.
     224                $wp_object_cache = $mock_cache;
     225
     226                // Call the fallback function.
     227                wp_cache_switch_to_blog();
     228
     229                // Verify a new cache object was created.
     230                $this->assertInstanceOf( 'WP_Object_Cache', $wp_object_cache );
     231
     232                // The default non-persistent groups should be set up.
     233                // We test this indirectly by verifying the cache works for these groups.
     234                wp_cache_set( 'test_count', 123, 'counts' );
     235                $this->assertSame( 123, wp_cache_get( 'test_count', 'counts' ) );
     236
     237                wp_cache_set( 'test_plugin', 'data', 'plugins' );
     238                $this->assertSame( 'data', wp_cache_get( 'test_plugin', 'plugins' ) );
     239
     240                wp_cache_set( 'test_theme_json', 'theme_data', 'theme_json' );
     241                $this->assertSame( 'theme_data', wp_cache_get( 'test_theme_json', 'theme_json' ) );
     242        }
     243
     244        /**
     245         * Test fallback integration with switch_to_blog().
     246         *
     247         * Verifies the fallback path in switch_to_blog() works correctly
     248         * when the persistent cache drop-in lacks wp_cache_switch_to_blog().
     249         *
     250         * @ticket 23290
     251         * @covers ::wp_cache_switch_to_blog
     252         */
     253        public function test_fallback_integration_with_switch_to_blog() {
     254
     255                self::factory()->blog->create();
     256
     257                // Set cache data in a non-global group.
     258                wp_cache_set( 'test_local', 'local_value', 'posts' );
     259
     260                // Verify it's set.
     261                $this->assertSame( 'local_value', wp_cache_get( 'test_local', 'posts' ) );
     262
     263                // Call the fallback function.
     264                wp_cache_switch_to_blog();
     265
     266                // Non-global groups should be cleared.
     267                $this->assertFalse( wp_cache_get( 'test_local', 'posts' ) );
     268
     269                // Verify the cache is still functional.
     270                wp_cache_set( 'test_new', 'new_value', 'posts' );
     271                $this->assertSame( 'new_value', wp_cache_get( 'test_new', 'posts' ) );
     272        }
     273
     274        /**
     275         * Test that wp_cache_switch_to_blog() handles empty cache gracefully.
     276         *
     277         * @ticket 23290
     278         * @covers ::wp_cache_switch_to_blog
     279         */
     280        public function test_handles_empty_cache_gracefully() {
     281                global $wp_object_cache;
     282
     283                // Call wp_cache_init() first to ensure we have a fresh cache.
     284                wp_cache_init();
     285
     286                // Call the fallback function on an empty cache.
     287                wp_cache_switch_to_blog();
     288
     289                // Verify the cache is functional after the call.
     290                $this->assertInstanceOf( 'WP_Object_Cache', $wp_object_cache );
     291
     292                // Test that we can set and get values.
     293                wp_cache_set( 'test_after_init', 'test_value', 'default' );
     294                $this->assertSame( 'test_value', wp_cache_get( 'test_after_init', 'default' ) );
     295        }
     296
     297        /**
     298         * Test that custom global groups configuration is preserved during fallback.
     299         *
     300         * @ticket 23290
     301         */
     302        public function test_custom_global_groups_configuration_is_preserved() {
     303                global $wp_object_cache;
     304
     305                // Add a custom global group.
     306                wp_cache_add_global_groups( array( 'custom_global_group' ) );
     307
     308                // Verify it was added.
     309                $this->assertArrayHasKey( 'custom_global_group', $wp_object_cache->global_groups );
     310
     311                // Call the fallback function.
     312                wp_cache_switch_to_blog();
     313
     314                // Verify the custom global group configuration is still present.
     315                $this->assertArrayHasKey( 'custom_global_group', $wp_object_cache->global_groups );
     316
     317                // Verify we can use the custom global group after fallback.
     318                wp_cache_set( 'test_key', 'test_value', 'custom_global_group' );
     319                $this->assertSame( 'test_value', wp_cache_get( 'test_key', 'custom_global_group' ) );
     320        }
     321
     322        /**
     323         * Test that multiple calls to wp_cache_switch_to_blog() work correctly.
     324         *
     325         * @ticket 23290
     326         */
     327        public function test_multiple_fallback_calls() {
     328                global $wp_object_cache;
     329
     330                // Set up initial data.
     331                wp_cache_set( 'local_key', 'local_value', 'posts' );
     332
     333                // First fallback call.
     334                wp_cache_switch_to_blog();
     335                $this->assertFalse( wp_cache_get( 'local_key', 'posts' ) );
     336                $this->assertInstanceOf( 'WP_Object_Cache', $wp_object_cache );
     337
     338                // Set new data.
     339                wp_cache_set( 'local_key2', 'local_value2', 'posts' );
     340                $this->assertSame( 'local_value2', wp_cache_get( 'local_key2', 'posts' ) );
     341
     342                // Second fallback call.
     343                wp_cache_switch_to_blog();
     344                $this->assertFalse( wp_cache_get( 'local_key2', 'posts' ) );
     345                $this->assertInstanceOf( 'WP_Object_Cache', $wp_object_cache );
     346
     347                // Verify global groups are still configured after multiple calls.
     348                $this->assertArrayHasKey( 'users', $wp_object_cache->global_groups );
     349        }
     350
     351        /**
     352         * Test that wp_cache_switch_to_blog() maintains cache structure integrity.
     353         *
     354         * @ticket 23290
     355         */
     356        public function test_maintains_cache_structure_integrity() {
     357                global $wp_object_cache;
     358
     359                // Store the initial properties we expect.
     360                $expected_properties = array( 'cache', 'global_groups' );
     361
     362                // Call the fallback function.
     363                wp_cache_switch_to_blog();
     364
     365                // Verify the cache object has the expected structure.
     366                foreach ( $expected_properties as $property ) {
     367                        $this->assertObjectHasProperty( $property, $wp_object_cache );
     368                }
     369
     370                // Verify the cache object is still functional.
     371                $this->assertInstanceOf( 'WP_Object_Cache', $wp_object_cache );
     372        }
     373
     374        /**
     375         * Test wp_cache_switch_to_blog() with default groups present.
     376         *
     377         * @ticket 23290
     378         */
     379        public function test_default_global_groups_are_restored() {
     380                global $wp_object_cache;
     381
     382                // Some key global groups that should always be present.
     383                $key_global_groups = array(
     384                        'users',
     385                        'user_meta',
     386                        'site-options',
     387                        'site-transient',
     388                        'blog-lookup',
     389                        'blog-details',
     390                        'site-details',
     391                );
     392
     393                // Call the fallback function.
     394                wp_cache_switch_to_blog();
     395
     396                // Verify key global groups are present.
     397                foreach ( $key_global_groups as $group ) {
     398                        $this->assertArrayHasKey( $group, $wp_object_cache->global_groups, "Global group '{$group}' should be present after fallback." );
     399                }
     400
     401                // Verify we have a reasonable number of global groups.
     402                $this->assertGreaterThanOrEqual( count( $key_global_groups ), count( $wp_object_cache->global_groups ) );
     403        }
     404
     405        /**
     406         * Test that non-global cache data is properly cleared during fallback.
     407         *
     408         * @ticket 23290
     409         */
     410        public function test_non_global_cache_data_is_cleared() {
     411
     412                // Set cache data in various non-global groups.
     413                wp_cache_set( 'post_key', 'post_value', 'posts' );
     414                wp_cache_set( 'term_key', 'term_value', 'terms' );
     415                wp_cache_set( 'option_key', 'option_value', 'options' );
     416                wp_cache_set( 'default_key', 'default_value', 'default' );
     417
     418                // Verify data is set.
     419                $this->assertSame( 'post_value', wp_cache_get( 'post_key', 'posts' ) );
     420                $this->assertSame( 'term_value', wp_cache_get( 'term_key', 'terms' ) );
     421                $this->assertSame( 'option_value', wp_cache_get( 'option_key', 'options' ) );
     422                $this->assertSame( 'default_value', wp_cache_get( 'default_key', 'default' ) );
     423
     424                // Call the fallback function.
     425                wp_cache_switch_to_blog();
     426
     427                // Verify all non-global cache data is cleared.
     428                $this->assertFalse( wp_cache_get( 'post_key', 'posts' ) );
     429                $this->assertFalse( wp_cache_get( 'term_key', 'terms' ) );
     430                $this->assertFalse( wp_cache_get( 'option_key', 'options' ) );
     431                $this->assertFalse( wp_cache_get( 'default_key', 'default' ) );
     432        }
     433
     434        /**
     435         * Test that fallback preserves many custom global groups.
     436         *
     437         * This tests the scenario where a plugin adds many custom global groups,
     438         * ensuring they're all preserved after the fallback.
     439         *
     440         * @ticket 23290
     441         */
     442        public function test_preserves_many_custom_global_groups() {
     443                global $wp_object_cache;
     444
     445                // Add multiple custom global groups.
     446                $custom_groups = array(
     447                        'my_plugin_cache',
     448                        'my_other_plugin',
     449                        'custom_group_1',
     450                        'custom_group_2',
     451                        'custom_group_3',
     452                );
     453
     454                wp_cache_add_global_groups( $custom_groups );
     455
     456                // Verify they were added.
     457                foreach ( $custom_groups as $group ) {
     458                        $this->assertArrayHasKey( $group, $wp_object_cache->global_groups );
     459                }
     460
     461                // Call the fallback function.
     462                wp_cache_switch_to_blog();
     463
     464                // Verify all custom global groups are still configured.
     465                foreach ( $custom_groups as $group ) {
     466                        $this->assertArrayHasKey( $group, $wp_object_cache->global_groups, "Custom group '{$group}' should be preserved." );
     467                }
     468
     469                // Verify they're functional.
     470                wp_cache_set( 'test', 'value', 'my_plugin_cache' );
     471                $this->assertSame( 'value', wp_cache_get( 'test', 'my_plugin_cache' ) );
     472        }
     473
     474        /**
     475         * Test that fallback handles empty global_groups array.
     476         *
     477         * Edge case where global_groups exists but is empty, which differs from
     478         * the property not existing at all.
     479         *
     480         * @ticket 23290
     481         * @covers ::wp_cache_switch_to_blog
     482         */
     483        public function test_handles_empty_global_groups_array() {
     484                global $wp_object_cache;
     485
     486                // Create a cache object with empty global_groups.
     487                $wp_object_cache->global_groups = array();
     488
     489                // Call the fallback function.
     490                wp_cache_switch_to_blog();
     491
     492                // Should fall back to default global groups.
     493                $this->assertNotEmpty( $wp_object_cache->global_groups );
     494                $this->assertArrayHasKey( 'users', $wp_object_cache->global_groups );
     495        }
     496
     497        /**
     498         * Test preserving both global and non-persistent group configurations.
     499         *
     500         * This is the core issue from ticket #23290 - ensuring non-persistent groups
     501         * aren't lost when using the fallback path in switch_to_blog().
     502         *
     503         * @ticket 23290
     504         * @covers ::wp_cache_switch_to_blog
     505         */
     506        public function test_preserves_both_global_and_non_persistent_groups() {
     507                global $wp_object_cache;
     508
     509                // Add a custom global group.
     510                wp_cache_add_global_groups( array( 'my_global' ) );
     511
     512                // Add data to various groups including the non-persistent ones.
     513                wp_cache_set( 'test1', 'val1', 'counts' );
     514                wp_cache_set( 'test2', 'val2', 'plugins' );
     515                wp_cache_set( 'test3', 'val3', 'my_global' );
     516                wp_cache_set( 'test4', 'val4', 'posts' );
     517
     518                // Call the fallback function.
     519                wp_cache_switch_to_blog();
     520
     521                // Global group configuration should be preserved.
     522                $this->assertArrayHasKey( 'my_global', $wp_object_cache->global_groups );
     523
     524                // All groups should still be functional after fallback.
     525                wp_cache_set( 'after1', 'afterval1', 'counts' );
     526                wp_cache_set( 'after2', 'afterval2', 'plugins' );
     527                wp_cache_set( 'after3', 'afterval3', 'my_global' );
     528                wp_cache_set( 'after4', 'afterval4', 'posts' );
     529
     530                $this->assertSame( 'afterval1', wp_cache_get( 'after1', 'counts' ) );
     531                $this->assertSame( 'afterval2', wp_cache_get( 'after2', 'plugins' ) );
     532                $this->assertSame( 'afterval3', wp_cache_get( 'after3', 'my_global' ) );
     533                $this->assertSame( 'afterval4', wp_cache_get( 'after4', 'posts' ) );
     534        }
     535
     536        /**
     537         * Test fallback in a realistic blog switching scenario.
     538         *
     539         * @ticket 23290
     540         * @covers ::wp_cache_switch_to_blog
     541         */
     542        public function test_realistic_blog_switching_scenario() {
     543                $blog_id_1 = get_current_blog_id();
     544                $blog_id_2 = self::factory()->blog->create();
     545
     546                // Add custom global groups that a plugin might use.
     547                wp_cache_add_global_groups( array( 'my_plugin_users', 'my_plugin_settings' ) );
     548
     549                // Set some cache data on blog 1.
     550                wp_cache_set( 'user_1', 'user_data', 'my_plugin_users' );
     551                wp_cache_set( 'post_1', 'post_data', 'posts' );
     552
     553                // Switch to blog 2 (this may trigger the fallback if wp_cache_switch_to_blog doesn't exist).
     554                switch_to_blog( $blog_id_2 );
     555
     556                // The blog context has changed.
     557                $this->assertSame( $blog_id_2, get_current_blog_id() );
     558
     559                // Set some data on blog 2.
     560                wp_cache_set( 'post_2', 'post_data_2', 'posts' );
     561                $this->assertSame( 'post_data_2', wp_cache_get( 'post_2', 'posts' ) );
     562
     563                // Switch back to blog 1.
     564                restore_current_blog();
     565
     566                // We're back on blog 1.
     567                $this->assertSame( $blog_id_1, get_current_blog_id() );
     568
     569                // Custom global groups should still be configured after switching.
     570                global $wp_object_cache;
     571                $this->assertArrayHasKey( 'my_plugin_users', $wp_object_cache->global_groups );
     572                $this->assertArrayHasKey( 'my_plugin_settings', $wp_object_cache->global_groups );
     573        }
     574
     575        /**
     576         * Test that blog-specific cache keys work correctly after fallback.
     577         *
     578         * Verifies that non-global cache groups maintain blog-specific prefixing
     579         * after the fallback reinitialization.
     580         *
     581         * @ticket 23290
     582         * @covers ::wp_cache_switch_to_blog
     583         */
     584        public function test_blog_specific_cache_keys_after_fallback() {
     585
     586                // Set data in a non-global group (which should be blog-specific).
     587                wp_cache_set( 'my_key', 'value_blog_1', 'posts' );
     588
     589                // Call the fallback.
     590                wp_cache_switch_to_blog();
     591
     592                // After fallback, the cache is reinitialized, so data is cleared.
     593                $this->assertFalse( wp_cache_get( 'my_key', 'posts' ) );
     594
     595                // But we can still set blog-specific data.
     596                wp_cache_set( 'my_key', 'new_value', 'posts' );
     597                $this->assertSame( 'new_value', wp_cache_get( 'my_key', 'posts' ) );
     598
     599                // And global groups should work as expected.
     600                wp_cache_set( 'global_key', 'global_value', 'users' );
     601                $this->assertSame( 'global_value', wp_cache_get( 'global_key', 'users' ) );
     602        }
     603
     604        /**
     605         * Test fallback correctly identifies non-persistent groups from cache structure.
     606         *
     607         * The fallback analyzes the existing cache structure to determine which groups
     608         * are non-persistent (not in global_groups). This test verifies that analysis works.
     609         *
     610         * @ticket 23290
     611         * @covers ::wp_cache_switch_to_blog
     612         */
     613        public function test_identifies_non_persistent_groups_from_cache() {
     614                global $wp_object_cache;
     615
     616                // Set data in various groups to populate the cache structure.
     617                wp_cache_set( 'k1', 'v1', 'posts' );
     618                wp_cache_set( 'k2', 'v2', 'counts' );
     619                wp_cache_set( 'k3', 'v3', 'plugins' );
     620                wp_cache_set( 'k4', 'v4', 'users' ); // Global group.
     621                wp_cache_set( 'k5', 'v5', 'custom_group' );
     622
     623                // Verify we have multiple groups in the cache.
     624                $this->assertGreaterThan( 1, count( $wp_object_cache->cache ) );
     625
     626                // Call the fallback - it should identify non-global groups from the cache structure.
     627                wp_cache_switch_to_blog();
     628
     629                // Cache is functional after fallback.
     630                $this->assertInstanceOf( 'WP_Object_Cache', $wp_object_cache );
     631
     632                // Test that we can use all the groups that existed before.
     633                wp_cache_set( 'new1', 'nv1', 'posts' );
     634                wp_cache_set( 'new2', 'nv2', 'counts' );
     635                wp_cache_set( 'new3', 'nv3', 'plugins' );
     636                wp_cache_set( 'new4', 'nv4', 'users' );
     637                wp_cache_set( 'new5', 'nv5', 'custom_group' );
     638
     639                $this->assertSame( 'nv1', wp_cache_get( 'new1', 'posts' ) );
     640                $this->assertSame( 'nv2', wp_cache_get( 'new2', 'counts' ) );
     641                $this->assertSame( 'nv3', wp_cache_get( 'new3', 'plugins' ) );
     642                $this->assertSame( 'nv4', wp_cache_get( 'new4', 'users' ) );
     643                $this->assertSame( 'nv5', wp_cache_get( 'new5', 'custom_group' ) );
     644        }
     645
     646        /**
     647         * Test the specific bug from ticket #23290.
     648         *
     649         * Before the fix, when a persistent object cache drop-in lacked wp_cache_switch_to_blog()
     650         * support, custom non-persistent groups added by plugins would be lost because the function
     651         * would use hardcoded defaults instead of preserving the existing configuration.
     652         *
     653         * @ticket 23290
     654         * @covers ::wp_cache_switch_to_blog
     655         */
     656        public function test_ticket_23290_non_persistent_groups_are_maintained() {
     657                global $wp_object_cache;
     658
     659                // Simulate a plugin adding a custom non-persistent group by populating the cache.
     660                wp_cache_set( 'plugin_cache_1', 'data1', 'my_plugin_cache' );
     661                wp_cache_set( 'plugin_cache_2', 'data2', 'another_plugin' );
     662
     663                // These groups now exist in the cache structure.
     664                $this->assertArrayHasKey( 'my_plugin_cache', $wp_object_cache->cache );
     665                $this->assertArrayHasKey( 'another_plugin', $wp_object_cache->cache );
     666
     667                // Before fix: calling the fallback would lose these groups.
     668                // After fix: they should be re-added to the cache configuration.
     669                wp_cache_switch_to_blog();
     670
     671                // After the fallback, we can still use these groups without error.
     672                wp_cache_set( 'new_cache_1', 'new_data1', 'my_plugin_cache' );
     673                wp_cache_set( 'new_cache_2', 'new_data2', 'another_plugin' );
     674
     675                // Verify they work.
     676                $this->assertSame( 'new_data1', wp_cache_get( 'new_cache_1', 'my_plugin_cache' ) );
     677                $this->assertSame( 'new_data2', wp_cache_get( 'new_cache_2', 'another_plugin' ) );
     678        }
     679
     680        /**
     681         * Test restore_current_blog() works with the fallback.
     682         *
     683         * Both switch_to_blog() and restore_current_blog() can use the fallback,
     684         * so verify the restore path also works correctly.
     685         *
     686         * @ticket 23290
     687         * @covers ::wp_cache_switch_to_blog
     688         */
     689        public function test_restore_current_blog_with_fallback() {
     690                global $wp_object_cache;
     691
     692                $original_blog_id = get_current_blog_id();
     693                $new_blog_id      = self::factory()->blog->create();
     694
     695                // Add a custom global group.
     696                wp_cache_add_global_groups( array( 'custom_for_restore' ) );
     697
     698                // Store initial state.
     699                $this->assertArrayHasKey( 'custom_for_restore', $wp_object_cache->global_groups );
     700
     701                // Switch to another blog.
     702                switch_to_blog( $new_blog_id );
     703                $this->assertSame( $new_blog_id, get_current_blog_id() );
     704
     705                // Verify custom global group is still there after switch.
     706                $this->assertArrayHasKey( 'custom_for_restore', $wp_object_cache->global_groups );
     707
     708                // Restore to original blog.
     709                restore_current_blog();
     710
     711                // We're back on the original blog.
     712                $this->assertSame( $original_blog_id, get_current_blog_id() );
     713
     714                // Custom global group configuration should still be present after restore.
     715                $this->assertArrayHasKey( 'custom_for_restore', $wp_object_cache->global_groups );
     716        }
     717
     718        /**
     719         * Test multiple nested blog switches and restores.
     720         *
     721         * @ticket 23290
     722         * @covers ::wp_cache_switch_to_blog
     723         */
     724        public function test_nested_blog_switches_with_fallback() {
     725                global $wp_object_cache;
     726
     727                $original_blog_id = get_current_blog_id();
     728                $blog_id_1        = self::factory()->blog->create();
     729                $blog_id_2        = self::factory()->blog->create();
     730
     731                // Add custom groups.
     732                wp_cache_add_global_groups( array( 'nested_test_group' ) );
     733
     734                // Level 1: Switch to blog 1.
     735                switch_to_blog( $blog_id_1 );
     736                $this->assertSame( $blog_id_1, get_current_blog_id() );
     737                $this->assertArrayHasKey( 'nested_test_group', $wp_object_cache->global_groups );
     738
     739                // Level 2: Switch to blog 2 from blog 1.
     740                switch_to_blog( $blog_id_2 );
     741                $this->assertSame( $blog_id_2, get_current_blog_id() );
     742                $this->assertArrayHasKey( 'nested_test_group', $wp_object_cache->global_groups );
     743
     744                // Restore from level 2 to level 1.
     745                restore_current_blog();
     746                $this->assertSame( $blog_id_1, get_current_blog_id() );
     747                $this->assertArrayHasKey( 'nested_test_group', $wp_object_cache->global_groups );
     748
     749                // Restore from level 1 to original.
     750                restore_current_blog();
     751                $this->assertSame( $original_blog_id, get_current_blog_id() );
     752                $this->assertArrayHasKey( 'nested_test_group', $wp_object_cache->global_groups );
     753        }
     754
     755        /**
     756         * Test consistency across multiple rapid fallback calls.
     757         *
     758         * Stress test verifying the fallback maintains consistency when called
     759         * multiple times in quick succession.
     760         *
     761         * @ticket 23290
     762         * @covers ::wp_cache_switch_to_blog
     763         */
     764        public function test_consistency_across_rapid_fallback_calls() {
     765                global $wp_object_cache;
     766
     767                // Add custom groups.
     768                wp_cache_add_global_groups( array( 'rapid_test' ) );
     769                $initial_group_count = count( $wp_object_cache->global_groups );
     770
     771                // Call fallback multiple times rapidly (simulating rapid blog switches).
     772                for ( $i = 0; $i < 5; $i++ ) {
     773                        wp_cache_switch_to_blog();
     774
     775                        // Verify the group is still there after each call.
     776                        $this->assertArrayHasKey( 'rapid_test', $wp_object_cache->global_groups );
     777
     778                        // The number of global groups should remain consistent.
     779                        $this->assertSame( $initial_group_count, count( $wp_object_cache->global_groups ) );
     780                }
     781        }
     782}