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;
private $loggers = [];
/**
- * @var LoggerInterface
+ * The CLI arguments passed through from phpunit.php
+ * @var array
*/
- private $testLogger;
+ private $cliArgs = [];
/**
* Table name prefixes. Oracle likes it shorter.
$this->backupGlobals = false;
$this->backupStaticAttributes = false;
- $this->testLogger = self::getTestLogger();
- }
-
- private static function getTestLogger() {
- return LoggerFactory::getInstance( 'tests-phpunit' );
}
public function __destruct() {
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 ) {
}
public function run( PHPUnit_Framework_TestResult $result = null ) {
+ if ( $result instanceof MediaWikiTestResult ) {
+ $this->cliArgs = $result->getMediaWikiCliArgs();
+ }
$this->overrideMwServices();
- $needsResetDB = false;
+ 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->testLogger->info( "Setting up DB for " . $this->toString() );
self::$useTemporaryTables = !$this->getCliArg( 'use-normal-tables' );
self::$reuseDB = $this->getCliArg( 'reuse-db' );
$needsResetDB = true;
}
- $this->testLogger->info( "Starting test " . $this->toString() );
parent::run( $result );
- $this->testLogger->info( "Finished test " . $this->toString() );
if ( $needsResetDB ) {
- $this->testLogger->info( "Resetting DB" );
$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() );
}
/**
* @since 1.32
*/
protected function addCoreDBData() {
- $this->testLogger->info( __METHOD__ );
if ( $this->db->getType() == 'oracle' ) {
# Insert 0 user to prevent FK violations
# Anonymous user
$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 );
* @return mixed
*/
public function getCliArg( $offset ) {
- if ( isset( PHPUnitMaintClass::$additionalOptions[$offset] ) ) {
- return PHPUnitMaintClass::$additionalOptions[$offset];
- }
-
- return null;
+ return $this->cliArgs[$offset] ?? null;
}
/**
* @param mixed $value
*/
public function setCliArg( $offset, $value ) {
- PHPUnitMaintClass::$additionalOptions[$offset] = $value;
+ $this->cliArgs[$offset] = $value;
}
/**
protected function assertFileContains(
$fileName,
$actualData,
- $createIfMissing = true,
+ $createIfMissing = false,
$msg = ''
) {
if ( $createIfMissing ) {