From c472b7ca50a42147f4e32d187c7163e6d62922f9 Mon Sep 17 00:00:00 2001 From: daniel Date: Tue, 11 Sep 2018 19:01:08 +0200 Subject: [PATCH] Reset services in ParserTestTopLevelSuite. This is intended to isolate parser tests from other tests by resetting the service locator between test runs, just like MediaWikiTestCase does. NOTE: this has no effect on parser tests run via parserTest.php, it's only for tests run via PHPUnit. Bug: T204065 Bug: T204072 Change-Id: I772b3b4a2d4d98948a249603b1cdb0933427b01c --- tests/phpunit/MediaWikiTestCase.php | 151 ++++++++++++------ .../suites/ParserTestTopLevelSuite.php | 13 +- 2 files changed, 111 insertions(+), 53 deletions(-) diff --git a/tests/phpunit/MediaWikiTestCase.php b/tests/phpunit/MediaWikiTestCase.php index de01b478b3..114ad7e2ae 100644 --- a/tests/phpunit/MediaWikiTestCase.php +++ b/tests/phpunit/MediaWikiTestCase.php @@ -8,7 +8,6 @@ use Psr\Log\LoggerInterface; use Wikimedia\Rdbms\IDatabase; use Wikimedia\Rdbms\IMaintainableDatabase; use Wikimedia\Rdbms\Database; -use Wikimedia\Rdbms\LBFactory; use Wikimedia\TestingAccessWrapper; /** @@ -28,6 +27,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { /** * The local service locator, created during setUp(). + * @var MediaWikiServices */ private $localServices; @@ -291,38 +291,6 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { return $testConfig; } - /** - * @param ConfigFactory $oldConfigFactory - * @param LBFactory $oldLoadBalancerFactory - * @param MediaWikiServices $newServices - * - * @throws MWException - */ - private static function installTestServices( - ConfigFactory $oldConfigFactory, - LBFactory $oldLoadBalancerFactory, - 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 ] - ) - ); - $newServices->resetServiceForTesting( 'DBLoadBalancerFactory' ); - $newServices->redefineService( - 'DBLoadBalancerFactory', - function ( MediaWikiServices $services ) use ( $oldLoadBalancerFactory ) { - return $oldLoadBalancerFactory; - } - ); - } - /** * @param ConfigFactory $oldFactory * @param Config[] $configurations @@ -358,7 +326,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { * Resets some non-service singleton instances and other static caches. It's not necessary to * reset services here. */ - private function resetNonServiceCaches() { + public static function resetNonServiceCaches() { global $wgRequest, $wgJobClasses; foreach ( $wgJobClasses as $type => $class ) { @@ -428,9 +396,8 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { $this->resetDB( $this->db, $this->tablesUsed ); } - $this->localServices->destroy(); + self::restoreMwServices(); $this->localServices = null; - MediaWikiServices::forceGlobalInstance( self::$originalServices ); } /** @@ -524,7 +491,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { } // Reset all caches between tests. - $this->resetNonServiceCaches(); + self::resetNonServiceCaches(); // XXX: reset maintenance triggers // Hook into period lag checks which often happen in long-running scripts @@ -640,6 +607,11 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { throw new Exception( __METHOD__ . ' must be called after MediaWikiTestCase::run()' ); } + if ( $this->localServices !== MediaWikiServices::getInstance() ) { + throw new Exception( __METHOD__ . ' will not work because the global MediaWikiServices ' + . 'instance has been replaced by test code.' ); + } + $this->localServices->disableService( $name ); $this->localServices->redefineService( $name, @@ -726,6 +698,12 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { if ( !$this->localServices ) { throw new Exception( __METHOD__ . ' must be called after MediaWikiTestCase::run()' ); } + + if ( $this->localServices !== MediaWikiServices::getInstance() ) { + throw new Exception( __METHOD__ . ' will not work because the global MediaWikiServices ' + . 'instance has been replaced by test code.' ); + } + MWNamespace::clearCaches(); Language::clearCaches(); @@ -895,6 +873,44 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { protected function overrideMwServices( Config $configOverrides = null, array $services = [] ) { + $newInstance = self::installMockMwServices( $configOverrides ); + + if ( $this->localServices ) { + $this->localServices->destroy(); + } + + $this->localServices = $newInstance; + + foreach ( $services as $name => $callback ) { + $newInstance->redefineService( $name, $callback ); + } + + return $newInstance; + } + + /** + * Creates a new "mock" MediaWikiServices instance, and installs it. + * This effectively resets all cached states in services, with the exception of + * the ConfigFactory and the DBLoadBalancerFactory service, which are inherited from + * the original MediaWikiServices. + * + * @note The new original MediaWikiServices instance can later be restored by calling + * restoreMwServices(). That original is determined by the first call to this method, or + * by setUpBeforeClass, whichever is called first. The caller is responsible for managing + * and, when appropriate, destroying any other MediaWikiServices instances that may get + * replaced when calling this method. + * + * @param Config|null $configOverrides Configuration overrides for the new MediaWikiServices + * instance. + * + * @return MediaWikiServices the new mock service locator. + */ + public static function installMockMwServices( Config $configOverrides = null ) { + // Make sure we have the original service locator + if ( !self::$originalServices ) { + self::$originalServices = MediaWikiServices::getInstance(); + } + if ( !$configOverrides ) { $configOverrides = new HashConfig(); } @@ -903,34 +919,65 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { $oldLoadBalancerFactory = self::$originalServices->getDBLoadBalancerFactory(); $testConfig = self::makeTestConfig( null, $configOverrides ); - $newInstance = new MediaWikiServices( $testConfig ); + $newServices = 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 ); + $newServices->loadWiringFiles( $wiringFiles ); // Provide a traditional hook point to allow extensions to configure services. - Hooks::run( 'MediaWikiServices', [ $newInstance ] ); + Hooks::run( 'MediaWikiServices', [ $newServices ] ); - foreach ( $services as $name => $callback ) { - $newInstance->redefineService( $name, $callback ); + // 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 ] + ) + ); + $newServices->resetServiceForTesting( 'DBLoadBalancerFactory' ); + $newServices->redefineService( + 'DBLoadBalancerFactory', + function ( MediaWikiServices $services ) use ( $oldLoadBalancerFactory ) { + return $oldLoadBalancerFactory; + } + ); + + MediaWikiServices::forceGlobalInstance( $newServices ); + return $newServices; + } + + /** + * Restores the original, non-mock MediaWikiServices instance. + * The previously active MediaWikiServices instance is destroyed, + * if it is different from the original that is to be restored. + * + * @note this if for internal use by test framework code. It should never be + * called from inside a test case, a data provider, or a setUp or tearDown method. + * + * @return bool true if the original service locator was restored, + * false if there was nothing too do. + */ + public static function restoreMwServices() { + if ( !self::$originalServices ) { + return false; } - self::installTestServices( - $oldConfigFactory, - $oldLoadBalancerFactory, - $newInstance - ); + $currentServices = MediaWikiServices::getInstance(); - if ( $this->localServices ) { - $this->localServices->destroy(); + if ( self::$originalServices === $currentServices ) { + return false; } - MediaWikiServices::forceGlobalInstance( $newInstance ); - $this->localServices = $newInstance; + MediaWikiServices::forceGlobalInstance( self::$originalServices ); + $currentServices->destroy(); - return $newInstance; + return true; } /** diff --git a/tests/phpunit/suites/ParserTestTopLevelSuite.php b/tests/phpunit/suites/ParserTestTopLevelSuite.php index 1ea5853f9d..cc0f26369e 100644 --- a/tests/phpunit/suites/ParserTestTopLevelSuite.php +++ b/tests/phpunit/suites/ParserTestTopLevelSuite.php @@ -1,4 +1,5 @@ getType(); $prefix = $type === 'oracle' ? @@ -142,7 +144,16 @@ class ParserTestTopLevelSuite extends PHPUnit_Framework_TestSuite { $this->oldTablePrefix = $db->tablePrefix(); MediaWikiTestCase::setupTestDB( $db, $prefix ); CloneDatabase::changePrefix( $prefix ); - $teardown = $this->ptRunner->setDatabase( $db ); + + $this->ptRunner->setDatabase( $db ); + + MediaWikiTestCase::resetNonServiceCaches(); + + MediaWikiTestCase::installMockMwServices(); + $teardown = new ScopedCallback( function () { + MediaWikiTestCase::restoreMwServices(); + } ); + $teardown = $this->ptRunner->setupUploads( $teardown ); $this->ptTeardownScope = $teardown; } -- 2.20.1