use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\IMaintainableDatabase;
use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\IResultWrapper;
use Wikimedia\Rdbms\LBFactory;
use Wikimedia\TestingAccessWrapper;
*/
private $mwGlobalsToUnset = [];
+ /**
+ * Holds original contents of interwiki table
+ * @var IResultWrapper
+ */
+ private $interwikiTable = null;
+
/**
* Holds original loggers which have been replaced by setLogger()
* @var LoggerInterface[]
* MediaWikiServices.
* @return MediaWikiServices
*/
- protected static function resetGlobalServices( Config $bootstrapConfig = null ) {
+ private static function resetGlobalServices( Config $bootstrapConfig = null ) {
$oldServices = MediaWikiServices::getInstance();
$oldConfigFactory = $oldServices->getConfigFactory();
$oldLoadBalancerFactory = $oldServices->getDBLoadBalancerFactory();
}
}
+ // 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();
foreach ( $this->mwGlobalsToUnset as $value ) {
unset( $GLOBALS[$value] );
}
+ if (
+ array_key_exists( 'wgExtraNamespaces', $this->mwGlobals ) ||
+ in_array( 'wgExtraNamespaces', $this->mwGlobalsToUnset )
+ ) {
+ $this->resetNamespaces();
+ }
$this->mwGlobals = [];
$this->mwGlobalsToUnset = [];
$this->restoreLoggers();
return $object;
}
);
+
+ if ( $name === 'ContentLanguage' ) {
+ $this->doSetMwGlobals( [ 'wgContLang' => $object ] );
+ }
}
/**
$pairs = [ $pairs => $value ];
}
+ if ( isset( $pairs['wgContLang'] ) ) {
+ throw new MWException(
+ 'No setting $wgContLang, use setContentLang() or setService( \'ContentLanguage\' )'
+ );
+ }
+
+ $this->doSetMwGlobals( $pairs, $value );
+ }
+
+ /**
+ * An internal method that allows setService() to set globals that tests are not supposed to
+ * touch.
+ */
+ private function doSetMwGlobals( $pairs, $value = null ) {
$this->stashMwGlobals( array_keys( $pairs ) );
foreach ( $pairs as $key => $value ) {
$GLOBALS[$key] = $value;
}
+
+ if ( array_key_exists( 'wgExtraNamespaces', $pairs ) ) {
+ $this->resetNamespaces();
+ }
+ }
+
+ /**
+ * Must be called whenever namespaces are changed, e.g., $wgExtraNamespaces is altered.
+ * Otherwise old namespace data will lurk and cause bugs.
+ */
+ private function resetNamespaces() {
+ 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' );
}
/**
* @return MediaWikiServices
* @throws MWException
*/
- protected function overrideMwServices( Config $configOverrides = null, array $services = [] ) {
+ protected static function overrideMwServices(
+ Config $configOverrides = null, array $services = []
+ ) {
if ( !$configOverrides ) {
$configOverrides = new HashConfig();
}
$langCode = $lang;
$langObj = Language::factory( $langCode );
}
- $this->setMwGlobals( [
- 'wgLanguageCode' => $langCode,
- 'wgContLang' => $langObj,
- ] );
+ $this->setMwGlobals( 'wgLanguageCode', $langCode );
+ $this->setService( 'ContentLanguage', $langObj );
}
/**
$user
);
// an edit always attempt to purge backlink links such as history
- // pages. That is unneccessary.
+ // pages. That is unnecessary.
JobQueueGroup::singleton()->get( 'htmlCacheUpdate' )->delete();
// WikiPages::doEditUpdates randomly adds RC purges
JobQueueGroup::singleton()->get( 'recentChangesUpdate' )->delete();
self::$dbSetup = false;
}
- /**
- * Prepares the given database connection for usage in the context of usage tests.
- * This sets up clones database tables and changes the table prefix as appropriate.
- * If the database connection already has cloned tables, calling this method has no
- * effect. The tables are not re-cloned or reset in that case.
- *
- * @param IMaintainableDatabase $db
- */
- protected function prepareConnectionForTesting( IMaintainableDatabase $db ) {
- if ( !self::$dbSetup ) {
- throw new LogicException(
- 'Cannot use prepareConnectionForTesting()'
- . ' if the test case is not defined to use the database!'
- );
- }
-
- if ( isset( $db->_originalTablePrefix ) ) {
- // The DB connection was already prepared for testing.
- return;
- }
-
- $testPrefix = self::getTestPrefixFor( $db );
- $oldPrefix = $db->tablePrefix();
-
- $tablesCloned = self::listTables( $db );
-
- if ( $oldPrefix === $testPrefix ) {
- // The database connection already has the test prefix, but presumably not
- // the cloned tables. This is the typical case, since the LBFactory will
- // have the prefix set during testing, but LoadBalancers will still return
- // connections that don't have the cloned table structure.
- $oldPrefix = self::$oldTablePrefix;
- }
-
- $dbClone = new CloneDatabase( $db, $tablesCloned, $testPrefix, $oldPrefix );
- $dbClone->useTemporaryTables( self::$useTemporaryTables );
-
- $db->_originalTablePrefix = $oldPrefix;
-
- if ( ( $db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) {
- throw new LogicException( 'Cannot clone database tables' );
- } else {
- $dbClone->cloneTableStructure();
- }
- }
-
/**
* Setups a database with cloned tables using the given prefix.
*
*/
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', 'content_models', 'slot_roles',
+ 'revision_actor_temp', 'slots', 'content',
];
$coreDBDataTables = array_merge( $userTables, $pageTables );
}
}
- $truncate = in_array( $db->getType(), [ 'oracle', 'mysql' ] );
foreach ( $tablesUsed as $tbl ) {
- // TODO: reset interwiki table to its original content.
- if ( $tbl == 'interwiki' ) {
- continue;
- }
-
- if ( !$db->tableExists( $tbl ) ) {
- continue;
- }
-
- if ( $truncate ) {
- $db->query( 'TRUNCATE TABLE ' . $db->tableName( $tbl ), __METHOD__ );
- } else {
- $db->delete( $tbl, '*', __METHOD__ );
- }
-
- if ( in_array( $db->getType(), [ 'postgres', 'sqlite' ], true ) ) {
- // Reset the table's sequence too.
- $db->resetSequenceForTable( $tbl, __METHOD__ );
- }
-
- if ( $tbl === 'page' ) {
- // Forget about the pages since they don't
- // exist in the DB.
- MediaWikiServices::getInstance()->getLinkCache()->clear();
- }
+ $this->truncateTable( $tbl, $db );
}
if ( array_intersect( $tablesUsed, $coreDBDataTables ) ) {
}
}
+ /**
+ * Empties the given table and resets any auto-increment counters.
+ * Will also purge caches associated with some well known tables.
+ * If the table is not know, this method just returns.
+ *
+ * @param string $tableName
+ * @param IDatabase|null $db
+ */
+ protected function truncateTable( $tableName, IDatabase $db = null ) {
+ if ( !$db ) {
+ $db = $this->db;
+ }
+
+ if ( !$db->tableExists( $tableName ) ) {
+ return;
+ }
+
+ $truncate = in_array( $db->getType(), [ 'oracle', 'mysql' ] );
+
+ if ( $truncate ) {
+ $db->query( 'TRUNCATE TABLE ' . $db->tableName( $tableName ), __METHOD__ );
+ } else {
+ $db->delete( $tableName, '*', __METHOD__ );
+ }
+
+ if ( in_array( $db->getType(), [ 'postgres', 'sqlite' ], true ) ) {
+ // 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();
+ }
+ }
+
private static function unprefixTable( &$tableName, $ind, $prefix ) {
$tableName = substr( $tableName, strlen( $prefix ) );
}