use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\IMaintainableDatabase;
use Wikimedia\Rdbms\Database;
-use Wikimedia\Rdbms\IResultWrapper;
use Wikimedia\Rdbms\LBFactory;
use Wikimedia\TestingAccessWrapper;
use PHPUnit4And6Compat;
/**
- * 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.
+ * The original service locator. This is overridden during setUp().
*
* @var MediaWikiServices|null
*/
- private static $serviceLocator = null;
+ private static $originalServices;
+
+ /**
+ * The local service locator, created during setUp().
+ */
+ private $localServices;
/**
* $called tracks whether the setUp and tearDown method has been called.
*/
private $mwGlobalsToUnset = [];
- /**
- * Holds original contents of interwiki table
- * @var IResultWrapper
- */
- private $interwikiTable = null;
-
/**
* Holds original loggers which have been replaced by setLogger()
* @var LoggerInterface[]
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() {
public static function setUpBeforeClass() {
parent::setUpBeforeClass();
- // Get the service locator, and reset services if it's not done already
- self::$serviceLocator = self::prepareServices( new GlobalVarConfig() );
+ // Get the original service locator
+ if ( !self::$originalServices ) {
+ self::$originalServices = MediaWikiServices::getInstance();
+ }
}
/**
}
/**
- * 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.
- * @return MediaWikiServices
+ * @deprecated since 1.32
*/
public static function prepareServices( Config $bootstrapConfig ) {
- static $services = null;
-
- if ( !$services ) {
- $services = self::resetGlobalServices( $bootstrapConfig );
- }
- return $services;
- }
-
- /**
- * 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
- * running unit tests. Use MediaWikiTestCase::overrideMwServices() for that.
- *
- * @see MediaWikiServices::resetGlobalInstance()
- * @see prepareServices()
- * @see MediaWikiTestCase::overrideMwServices()
- *
- * @param Config|null $bootstrapConfig The bootstrap config to use with the new
- * MediaWikiServices.
- * @return MediaWikiServices
- */
- private static function resetGlobalServices( Config $bootstrapConfig = null ) {
- $oldServices = MediaWikiServices::getInstance();
- $oldConfigFactory = $oldServices->getConfigFactory();
- $oldLoadBalancerFactory = $oldServices->getDBLoadBalancerFactory();
-
- $testConfig = self::makeTestConfig( $bootstrapConfig );
-
- MediaWikiServices::resetGlobalInstance( $testConfig );
-
- $serviceLocator = MediaWikiServices::getInstance();
- self::installTestServices(
- $oldConfigFactory,
- $oldLoadBalancerFactory,
- $serviceLocator
- );
- return $serviceLocator;
}
/**
$defaultOverrides = new HashConfig();
if ( !$baseConfig ) {
- $baseConfig = MediaWikiServices::getInstance()->getBootstrapConfig();
+ $baseConfig = self::$originalServices->getBootstrapConfig();
}
/* Some functions require some kind of caching, and will end up using the db,
}
/**
- * 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()
+ * Resets some non-service singleton instances and other static caches. It's not necessary to
+ * reset services here.
*/
- private function doLightweightServiceReset() {
+ private function resetNonServiceCaches() {
global $wgRequest, $wgJobClasses;
foreach ( $wgJobClasses as $type => $class ) {
JobQueueGroup::destroySingletons();
ObjectCache::clear();
- $services = MediaWikiServices::getInstance();
- $services->resetServiceForTesting( 'MainObjectStash' );
- $services->resetServiceForTesting( 'LocalServerObjectCache' );
- $services->getMainWANObjectCache()->clearProcessCache();
FileBackendGroup::destroySingleton();
DeferredUpdates::clearPendingUpdates();
}
public function run( PHPUnit_Framework_TestResult $result = null ) {
- $needsResetDB = false;
+ if ( $result instanceof MediaWikiTestResult ) {
+ $this->cliArgs = $result->getMediaWikiCliArgs();
+ }
+ $this->overrideMwServices();
+ $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();
+ $this->localServices = null;
+ MediaWikiServices::forceGlobalInstance( self::$originalServices );
}
/**
}
}
- // Store contents of interwiki table in case it changes. Unfortunately, we seem to have no
- // way to do this only when needed, because tablesUsed can be changed mid-test.
- if ( $this->db ) {
- $this->interwikiTable = $this->db->select( 'interwiki', '*', '', __METHOD__ );
- }
-
// Reset all caches between tests.
- $this->doLightweightServiceReset();
+ $this->resetNonServiceCaches();
// XXX: reset maintenance triggers
// Hook into period lag checks which often happen in long-running scripts
- $services = MediaWikiServices::getInstance();
- $lbFactory = $services->getDBLoadBalancerFactory();
- Maintenance::setLBFactoryTriggers( $lbFactory, $services->getMainConfig() );
+ $lbFactory = $this->localServices->getDBLoadBalancerFactory();
+ Maintenance::setLBFactoryTriggers( $lbFactory, $this->localServices->getMainConfig() );
ob_start( 'MediaWikiTestCase::wfResetOutputBuffersBarrier' );
}
$this->mwGlobalsToUnset = [];
$this->restoreLoggers();
- if ( self::$serviceLocator && MediaWikiServices::getInstance() !== self::$serviceLocator ) {
- MediaWikiServices::forceGlobalInstance( self::$serviceLocator );
- }
-
// TODO: move global state into MediaWikiServices
RequestContext::resetMain();
if ( session_id() !== '' ) {
* @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();
+ if ( !$this->localServices ) {
+ throw new Exception( __METHOD__ . ' must be called after MediaWikiTestCase::run()' );
}
- MediaWikiServices::getInstance()->disableService( $name );
- MediaWikiServices::getInstance()->redefineService(
+ $this->localServices->disableService( $name );
+ $this->localServices->redefineService(
$name,
function () use ( $object ) {
return $object;
* Otherwise old namespace data will lurk and cause bugs.
*/
private function resetNamespaces() {
+ if ( !$this->localServices ) {
+ throw new Exception( __METHOD__ . ' must be called after MediaWikiTestCase::run()' );
+ }
MWNamespace::clearCaches();
Language::clearCaches();
// We can't have the TitleFormatter holding on to an old Language object either
// @todo We shouldn't need to reset all the aliases here.
- $services = MediaWikiServices::getInstance();
- $services->resetServiceForTesting( 'TitleFormatter' );
- $services->resetServiceForTesting( 'TitleParser' );
- $services->resetServiceForTesting( '_MediaWikiTitleCodec' );
+ $this->localServices->resetServiceForTesting( 'TitleFormatter' );
+ $this->localServices->resetServiceForTesting( 'TitleParser' );
+ $this->localServices->resetServiceForTesting( '_MediaWikiTitleCodec' );
}
/**
* @return MediaWikiServices
* @throws MWException
*/
- protected static function overrideMwServices(
+ protected function overrideMwServices(
Config $configOverrides = null, array $services = []
) {
if ( !$configOverrides ) {
$configOverrides = new HashConfig();
}
- $oldInstance = MediaWikiServices::getInstance();
- $oldConfigFactory = $oldInstance->getConfigFactory();
- $oldLoadBalancerFactory = $oldInstance->getDBLoadBalancerFactory();
+ $oldConfigFactory = self::$originalServices->getConfigFactory();
+ $oldLoadBalancerFactory = self::$originalServices->getDBLoadBalancerFactory();
$testConfig = self::makeTestConfig( null, $configOverrides );
$newInstance = new MediaWikiServices( $testConfig );
$oldLoadBalancerFactory,
$newInstance
);
+
+ if ( $this->localServices ) {
+ $this->localServices->destroy();
+ }
+
MediaWikiServices::forceGlobalInstance( $newInstance );
+ $this->localServices = $newInstance;
return $newInstance;
}
* @since 1.32
*/
protected function addCoreDBData() {
- $this->testLogger->info( __METHOD__ );
if ( $this->db->getType() == 'oracle' ) {
# Insert 0 user to prevent FK violations
# Anonymous user
foreach ( $tables as $tbl ) {
$tbl = $db->tableName( $tbl );
$db->query( "DROP TABLE IF EXISTS $tbl", __METHOD__ );
-
- if ( $tbl === 'page' ) {
- // Forget about the pages since they don't
- // exist in the DB.
- MediaWikiServices::getInstance()->getLinkCache()->clear();
- }
}
}
$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 );
*/
private function resetDB( $db, $tablesUsed ) {
if ( $db ) {
- // NOTE: Do not reset the slot_roles and content_models tables, but let them
- // leak across tests. Resetting them would require to reset all NamedTableStore
- // instances for these tables, of which there may be several beyond the ones
- // known to MediaWikiServices. See T202641.
$userTables = [ 'user', 'user_groups', 'user_properties', 'actor' ];
$pageTables = [
'page', 'revision', 'ip_changes', 'revision_comment_temp', 'comment', 'archive',
- 'revision_actor_temp', 'slots', 'content',
+ 'revision_actor_temp', 'slots', 'content', 'content_models', 'slot_roles',
];
$coreDBDataTables = array_merge( $userTables, $pageTables );
}
if ( array_intersect( $tablesUsed, $coreDBDataTables ) ) {
+ // Reset services that may contain information relating to the truncated tables
+ $this->overrideMwServices();
// Re-add core DB data that was deleted
$this->addCoreDBData();
}
$db->delete( $tableName, '*', __METHOD__ );
}
- if ( in_array( $db->getType(), [ 'postgres', 'sqlite' ], true ) ) {
+ if ( $db instanceof DatabasePostgres || $db instanceof DatabaseSqlite ) {
// Reset the table's sequence too.
$db->resetSequenceForTable( $tableName, __METHOD__ );
}
- if ( $tableName === 'interwiki' ) {
- if ( !$this->interwikiTable ) {
- // @todo We should probably throw here, but this causes test failures that I
- // can't figure out, so for now we silently continue.
- return;
- }
- $db->insert(
- 'interwiki',
- array_values( array_map( 'get_object_vars', iterator_to_array( $this->interwikiTable ) ) ),
- __METHOD__
- );
- }
-
- if ( $tableName === 'page' ) {
- // Forget about the pages since they don't
- // exist in the DB.
- MediaWikiServices::getInstance()->getLinkCache()->clear();
+ // re-initialize site_stats table
+ if ( $tableName === 'site_stats' ) {
+ SiteStatsInit::doPlaceholderInit();
}
}
* @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 ) {