Fix core DB data in unit testing
[lhc/web/wiklou.git] / tests / phpunit / MediaWikiTestCase.php
index 08785ea..d846b57 100644 (file)
@@ -2,23 +2,12 @@
 use MediaWiki\Logger\LegacySpi;
 use MediaWiki\Logger\LoggerFactory;
 use MediaWiki\Logger\MonologSpi;
-use MediaWiki\MediaWikiServices;
 use Psr\Log\LoggerInterface;
 
 /**
  * @since 1.18
  */
 abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
-
-       /**
-        * The service locator created by prepareServices(). This service locator will
-        * be restored after each test. Tests that pollute the global service locator
-        * instance should use overrideMwServices() to isolate the test.
-        *
-        * @var MediaWikiServices|null
-        */
-       private static $serviceLocator = null;
-
        /**
         * $called tracks whether the setUp and tearDown method has been called.
         * class extending MediaWikiTestCase usually override setUp and tearDown
@@ -119,204 +108,18 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                }
        }
 
-       public static function setUpBeforeClass() {
-               parent::setUpBeforeClass();
-
-               // NOTE: Usually, PHPUnitMaintClass::finalSetup already called this,
-               // but let's make doubly sure.
-               self::prepareServices( new GlobalVarConfig() );
-       }
-
-       /**
-        * Prepare service configuration for unit testing.
-        *
-        * This calls MediaWikiServices::resetGlobalInstance() to allow some critical services
-        * to be overridden for testing.
-        *
-        * prepareServices() only needs to be called once, but should be called as early as possible,
-        * before any class has a chance to grab a reference to any of the global services
-        * instances that get discarded by prepareServices(). Only the first call has any effect,
-        * later calls are ignored.
-        *
-        * @note This is called by PHPUnitMaintClass::finalSetup.
-        *
-        * @see MediaWikiServices::resetGlobalInstance()
-        *
-        * @param Config $bootstrapConfig The bootstrap config to use with the new
-        *        MediaWikiServices. Only used for the first call to this method.
-        */
-       public static function prepareServices( Config $bootstrapConfig ) {
-               static $servicesPrepared = false;
-
-               if ( $servicesPrepared ) {
-                       return;
-               } else {
-                       $servicesPrepared = true;
-               }
-
-               self::resetGlobalServices( $bootstrapConfig );
-       }
-
-       /**
-        * Reset global services, and install testing environment.
-        * This is the testing equivalent of MediaWikiServices::resetGlobalInstance().
-        * This should only be used to set up the testing environment, not when
-        * runnnig unit tests. Use overrideMwServices() for that.
-        *
-        * @see MediaWikiServices::resetGlobalInstance()
-        * @see prepareServices()
-        * @see overrideMwServices()
-        *
-        * @param Config|null $bootstrapConfig The bootstrap config to use with the new
-        *        MediaWikiServices.
-        */
-       protected static function resetGlobalServices( Config $bootstrapConfig = null ) {
-               $oldServices = MediaWikiServices::getInstance();
-               $oldConfigFactory = $oldServices->getConfigFactory();
-
-               $testConfig = self::makeTestConfig( $bootstrapConfig );
-
-               MediaWikiServices::resetGlobalInstance( $testConfig );
-
-               self::$serviceLocator = MediaWikiServices::getInstance();
-               self::installTestServices(
-                       $oldConfigFactory,
-                       self::$serviceLocator
-               );
-       }
-
-       /**
-        * Create a config suitable for testing, based on a base config, default overrides,
-        * and custom overrdies.
-        *
-        * @param Config|null $baseConfig
-        * @param Config|null $customOverrides
-        *
-        * @return Config
-        */
-       private static function makeTestConfig(
-               Config $baseConfig = null,
-               Config $customOverrides = null
-       ) {
-               $defaultOverrides = new HashConfig();
-
-               if ( !$baseConfig ) {
-                       $baseConfig = MediaWikiServices::getInstance()->getBootstrapConfig();
-               }
-
+       public function run( PHPUnit_Framework_TestResult $result = null ) {
                /* Some functions require some kind of caching, and will end up using the db,
                 * which we can't allow, as that would open a new connection for mysql.
                 * Replace with a HashBag. They would not be going to persist anyway.
                 */
-               $hashCache = [ 'class' => 'HashBagOStuff' ];
-               $objectCaches = [
-                               CACHE_DB => $hashCache,
-                               CACHE_ACCEL => $hashCache,
-                               CACHE_MEMCACHED => $hashCache,
-                               'apc' => $hashCache,
-                               'xcache' => $hashCache,
-                               'wincache' => $hashCache,
-                       ] + $baseConfig->get( 'ObjectCaches' );
-
-               $defaultOverrides->set( 'ObjectCaches', $objectCaches );
-               $defaultOverrides->set( 'MainCacheType', CACHE_NONE );
-
-               $testConfig = $customOverrides
-                       ? new MultiConfig( [ $customOverrides, $defaultOverrides, $baseConfig ] )
-                       : new MultiConfig( [ $defaultOverrides, $baseConfig ] );
-
-               return $testConfig;
-       }
-
-       /**
-        * @param ConfigFactory $oldConfigFactory
-        * @param MediaWikiServices $newServices
-        *
-        * @throws MWException
-        */
-       private static function installTestServices(
-               ConfigFactory $oldConfigFactory,
-               MediaWikiServices $newServices
-       ) {
-               // Use bootstrap config for all configuration.
-               // This allows config overrides via global variables to take effect.
-               $bootstrapConfig = $newServices->getBootstrapConfig();
-               $newServices->resetServiceForTesting( 'ConfigFactory' );
-               $newServices->redefineService(
-                       'ConfigFactory',
-                       self::makeTestConfigFactoryInstantiator(
-                               $oldConfigFactory,
-                               [ 'main' =>  $bootstrapConfig ]
-                       )
-               );
-       }
-
-       /**
-        * @param ConfigFactory $oldFactory
-        * @param Config[] $config
-        *
-        * @return Closure
-        */
-       private static function makeTestConfigFactoryInstantiator(
-               ConfigFactory $oldFactory,
-               array $configurations
-       ) {
-               return function( MediaWikiServices $services ) use ( $oldFactory, $configurations ) {
-                       $factory = new ConfigFactory();
-
-                       // clone configurations from $oldFactory that are not overwritten by $configurations
-                       $namesToClone = array_diff(
-                               $oldFactory->getConfigNames(),
-                               array_keys( $configurations )
-                       );
-
-                       foreach ( $namesToClone as $name ) {
-                               $factory->register( $name, $oldFactory->makeConfig( $name ) );
-                       }
-
-                       foreach ( $configurations as $name => $config ) {
-                               $factory->register( $name, $config );
-                       }
-
-                       return $factory;
-               };
-       }
-
-       /**
-        * Resets some well known services that typically have state that may interfere with unit tests.
-        * This is a lightweight alternative to resetGlobalServices().
-        *
-        * @note There is no guarantee that no references remain to stale service instances destroyed
-        * by a call to doLightweightServiceReset().
-        *
-        * @throws MWException if called outside of PHPUnit tests.
-        *
-        * @see resetGlobalServices()
-        */
-       private function doLightweightServiceReset() {
-               global $wgRequest;
-
-               $services = MediaWikiServices::getInstance();
+               ObjectCache::$instances[CACHE_DB] = new HashBagOStuff;
 
-               JobQueueGroup::destroySingletons();
-               ObjectCache::clear();
-               FileBackendGroup::destroySingleton();
-
-               // TODO: move global state into MediaWikiServices
-               RequestContext::resetMain();
-               MediaHandler::resetCache();
-               if ( session_id() !== '' ) {
-                       session_write_close();
-                       session_id( '' );
-               }
-
-               $wgRequest = new FauxRequest();
-               MediaWiki\Session\SessionManager::resetCache();
-       }
-
-       public function run( PHPUnit_Framework_TestResult $result = null ) {
-               // Reset all caches between tests.
-               $this->doLightweightServiceReset();
+               // Sandbox APC by replacing with in-process hash instead.
+               // Ensures values are removed between tests.
+               ObjectCache::$instances['apc'] =
+               ObjectCache::$instances['xcache'] =
+               ObjectCache::$instances['wincache'] = new HashBagOStuff;
 
                $needsResetDB = false;
 
@@ -486,12 +289,6 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                }
                $this->mwGlobals = [];
                $this->restoreLoggers();
-
-               if ( self::$serviceLocator && MediaWikiServices::getInstance() !== self::$serviceLocator ) {
-                       MediaWikiServices::forceGlobalInstance( self::$serviceLocator );
-               }
-
-               // TODO: move global state into MediaWikiServices
                RequestContext::resetMain();
                MediaHandler::resetCache();
                if ( session_id() !== '' ) {
@@ -527,28 +324,6 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                );
        }
 
-       /**
-        * Sets a service, maintaining a stashed version of the previous service to be
-        * restored in tearDown
-        *
-        * @param string $name
-        * @param object $object
-        */
-       protected function setService( $name, $object ) {
-               // If we did not yet override the service locator, so so now.
-               if ( MediaWikiServices::getInstance() === self::$serviceLocator ) {
-                       $this->overrideMwServices();
-               }
-
-               MediaWikiServices::getInstance()->disableService( $name );
-               MediaWikiServices::getInstance()->redefineService(
-                       $name,
-                       function () use ( $object ) {
-                               return $object;
-                       }
-               );
-       }
-
        /**
         * Sets a global, maintaining a stashed version of the previous global to be
         * restored in tearDown
@@ -579,9 +354,6 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
         * @param mixed $value Value to set the global to (ignored
         *  if an array is given as first argument).
         *
-        * @note To allow changes to global variables to take effect on global service instances,
-        *       call overrideMwServices().
-        *
         * @since 1.21
         */
        protected function setMwGlobals( $pairs, $value = null ) {
@@ -609,10 +381,6 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
         * @param array|string $globalKeys Key to the global variable, or an array of keys.
         *
         * @throws Exception When trying to stash an unset global
-        *
-        * @note To allow changes to global variables to take effect on global service instances,
-        *       call overrideMwServices().
-        *
         * @since 1.23
         */
        protected function stashMwGlobals( $globalKeys ) {
@@ -653,9 +421,6 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
         *
         * @throws MWException If the designated global is not an array.
         *
-        * @note To allow changes to global variables to take effect on global service instances,
-        *       call overrideMwServices().
-        *
         * @since 1.21
         */
        protected function mergeMwGlobalArrayValue( $name, $values ) {
@@ -676,52 +441,6 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                $this->setMwGlobals( $name, $merged );
        }
 
-       /**
-        * Stashes the global instance of MediaWikiServices, and installs a new one,
-        * allowing test cases to override settings and services.
-        * The previous instance of MediaWikiServices will be restored on tearDown.
-        *
-        * @since 1.27
-        *
-        * @param Config $configOverrides Configuration overrides for the new MediaWikiServices instance.
-        * @param callable[] $services An associative array of services to re-define. Keys are service
-        *        names, values are callables.
-        *
-        * @return MediaWikiServices
-        * @throws MWException
-        */
-       protected function overrideMwServices( Config $configOverrides = null, array $services = [] ) {
-               if ( !$configOverrides ) {
-                       $configOverrides = new HashConfig();
-               }
-
-               $oldInstance = MediaWikiServices::getInstance();
-               $oldConfigFactory = $oldInstance->getConfigFactory();
-
-               $testConfig = self::makeTestConfig( null, $configOverrides );
-               $newInstance = new MediaWikiServices( $testConfig );
-
-               // Load the default wiring from the specified files.
-               // NOTE: this logic mirrors the logic in MediaWikiServices::newInstance.
-               $wiringFiles = $testConfig->get( 'ServiceWiringFiles' );
-               $newInstance->loadWiringFiles( $wiringFiles );
-
-               // Provide a traditional hook point to allow extensions to configure services.
-               Hooks::run( 'MediaWikiServices', [ $newInstance ] );
-
-               foreach ( $services as $name => $callback ) {
-                       $newInstance->redefineService( $name, $callback );
-               }
-
-               self::installTestServices(
-                       $oldConfigFactory,
-                       $newInstance
-               );
-               MediaWikiServices::forceGlobalInstance( $newInstance );
-
-               return $newInstance;
-       }
-
        /**
         * @since 1.27
         * @param string|Language $lang
@@ -756,9 +475,6 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
         * @param LoggerInterface $logger
         */
        protected function setLogger( $channel, LoggerInterface $logger ) {
-               // TODO: Once loggers are managed by MediaWikiServices, use
-               //       overrideMwServices() to set loggers.
-
                $provider = LoggerFactory::getProvider();
                $wrappedProvider = TestingAccessWrapper::newFromObject( $provider );
                $singletons = $wrappedProvider->singletons;
@@ -850,10 +566,6 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                $user = User::newFromName( 'UTSysop' );
                $comment = __METHOD__ . ': Sample page for unit test.';
 
-               // Avoid memory leak...?
-               // LinkCache::singleton()->clear();
-               // Maybe.  But doing this absolutely breaks $title->isRedirect() when called during unit tests....
-
                $page = WikiPage::factory( $title );
                $page->doEditContent( ContentHandler::makeContent( $text, $title ), $comment, 0, false, $user );
 
@@ -925,12 +637,10 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                if ( $user->idForName() == 0 ) {
                        $user->addToDatabase();
                        TestUser::setPasswordForUser( $user, 'UTSysopPassword' );
+                       $user->addGroup( 'sysop' );
+                       $user->addGroup( 'bureaucrat' );
                }
 
-               // Always set groups, because $this->resetDB() wipes them out
-               $user->addGroup( 'sysop' );
-               $user->addGroup( 'bureaucrat' );
-
                // Make 1 page with 1 revision
                $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
                if ( $page->getId() == 0 ) {
@@ -1053,9 +763,6 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                        return;
                }
 
-               // TODO: the below should be re-written as soon as LBFactory, LoadBalancer,
-               // and DatabaseBase no longer use global state.
-
                self::$dbSetup = true;
 
                if ( !self::setupDatabaseWithTestPrefix( $db, $prefix ) ) {
@@ -1142,17 +849,29 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
         */
        private function resetDB( $db, $tablesUsed ) {
                if ( $db ) {
+                       $userTables = [ 'user', 'user_groups', 'user_properties' ];
+                       $coreDBDataTables = array_merge( $userTables, [ 'page', 'revision' ] );
+
+                       // If any of the user tables were marked as used, we should clear all of them.
+                       if ( array_intersect( $tablesUsed, $userTables ) ) {
+                               $tablesUsed = array_unique( array_merge( $tablesUsed, $userTables ) );
+
+                               // Totally clear User class in-process cache to avoid CAS errors
+                               TestingAccessWrapper::newFromClass( 'User' )
+                                       ->getInProcessCache()
+                                       ->clear();
+                       }
+
                        $truncate = in_array( $db->getType(), [ 'oracle', 'mysql' ] );
                        foreach ( $tablesUsed as $tbl ) {
-                               // TODO: reset interwiki and user tables to their original content.
-                               if ( $tbl == 'interwiki' || $tbl == 'user' ) {
+                               // TODO: reset interwiki table to its original content.
+                               if ( $tbl == 'interwiki' ) {
                                        continue;
                                }
 
                                if ( $truncate ) {
                                        $db->query( 'TRUNCATE TABLE ' . $db->tableName( $tbl ), __METHOD__ );
                                } else {
-
                                        $db->delete( $tbl, '*', __METHOD__ );
                                }
 
@@ -1162,6 +881,11 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                                        LinkCache::singleton()->clear();
                                }
                        }
+
+                       if ( array_intersect( $tablesUsed, $coreDBDataTables ) ) {
+                               // Re-add core DB data that was deleted
+                               $this->addCoreDBData();
+                       }
                }
        }