use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\IMaintainableDatabase;
use Wikimedia\Rdbms\Database;
-use Wikimedia\Rdbms\LBFactory;
use Wikimedia\TestingAccessWrapper;
/**
/**
* The local service locator, created during setUp().
+ * @var MediaWikiServices
*/
private $localServices;
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
* 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 ) {
}
$this->overrideMwServices();
+ if ( $this->needsDB() && !$this->isTestInDatabaseGroup() ) {
+ throw new Exception(
+ get_class( $this ) . ' apparently needsDB but is not in the Database group'
+ );
+ }
+
$needsResetDB = false;
if ( !self::$dbSetup || $this->needsDB() ) {
// set up a DB connection for this test to use
$this->resetDB( $this->db, $this->tablesUsed );
}
- $this->localServices->destroy();
+ self::restoreMwServices();
$this->localServices = null;
- MediaWikiServices::forceGlobalInstance( self::$originalServices );
}
/**
}
// 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
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,
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();
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();
}
$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;
}
/**
*/
public function needsDB() {
// If the test says it uses database tables, it needs the database
- if ( $this->tablesUsed ) {
- return true;
- }
+ return $this->tablesUsed || $this->isTestInDatabaseGroup();
+ }
+ /**
+ * @return bool
+ * @since 1.32
+ */
+ protected function isTestInDatabaseGroup() {
// If the test class says it belongs to the Database group, it needs the database.
// NOTE: This ONLY checks for the group in the class level doc comment.
$rc = new ReflectionClass( $this );
- if ( preg_match( '/@group +Database/im', $rc->getDocComment() ) ) {
- return true;
- }
-
- return false;
+ return (bool)preg_match( '/@group +Database/im', $rc->getDocComment() );
}
/**
$originalTables = $db->listTables( $db->_originalTablePrefix, __METHOD__ );
if ( $prefix === 'unprefixed' ) {
- $originalPrefixRegex = '/^' . preg_quote( $db->_originalTablePrefix ) . '/';
+ $originalPrefixRegex = '/^' . preg_quote( $db->_originalTablePrefix, '/' ) . '/';
$originalTables = array_map(
function ( $pt ) use ( $originalPrefixRegex ) {
return preg_replace( $originalPrefixRegex, '', $pt );
protected function assertFileContains(
$fileName,
$actualData,
- $createIfMissing = true,
+ $createIfMissing = false,
$msg = ''
) {
if ( $createIfMissing ) {