return self::getTestUser( [ 'sysop', 'bureaucrat' ] );
}
+ /**
+ * Returns a WikiPage representing an existing page.
+ *
+ * @since 1.32
+ *
+ * @param Title|string|null $title
+ * @return WikiPage
+ * @throws MWException
+ */
+ protected function getExistingTestPage( $title = null ) {
+ $title = ( $title === null ) ? 'UTPage' : $title;
+ $title = is_string( $title ) ? Title::newFromText( $title ) : $title;
+ $page = WikiPage::factory( $title );
+
+ if ( !$page->exists() ) {
+ $user = self::getTestSysop()->getUser();
+ $page->doEditContent(
+ new WikitextContent( 'UTContent' ),
+ 'UTPageSummary',
+ EDIT_NEW | EDIT_SUPPRESS_RC,
+ false,
+ $user
+ );
+ }
+
+ return $page;
+ }
+
+ /**
+ * Returns a WikiPage representing a non-existing page.
+ *
+ * @since 1.32
+ *
+ * @param Title|string|null $title
+ * @return WikiPage
+ * @throws MWException
+ */
+ protected function getNonexistingTestPage( $title = null ) {
+ $title = ( $title === null ) ? 'UTPage-' . rand( 0, 100000 ) : $title;
+ $title = is_string( $title ) ? Title::newFromText( $title ) : $title;
+ $page = WikiPage::factory( $title );
+
+ if ( $page->exists() ) {
+ $page->doDeleteArticle( 'Testing' );
+ }
+
+ return $page;
+ }
+
/**
* Prepare service configuration for unit testing.
*
*
* @param array|string $pairs Key to the global variable, or an array
* of key/value pairs.
- * @param mixed $value Value to set the global to (ignored
+ * @param mixed|null $value Value to set the global to (ignored
* if an array is given as first argument).
*
* @note To allow changes to global variables to take effect on global service instances,
*
* @since 1.27
*
- * @param Config $configOverrides Configuration overrides for the new MediaWikiServices instance.
+ * @param Config|null $configOverrides Configuration overrides for the new MediaWikiServices
+ * instance.
* @param callable[] $services An associative array of services to re-define. Keys are service
* names, values are callables.
*
* in which case the next two parameters are ignored; or a single string
* identifying a group, to use with the next two parameters.
* @param string|null $newKey
- * @param mixed $newValue
+ * @param mixed|null $newValue
*/
public function setGroupPermissions( $newPerms, $newKey = null, $newValue = null ) {
global $wgGroupPermissions;
$singletons = $wrappedProvider->singletons;
if ( $provider instanceof MonologSpi ) {
if ( !isset( $this->loggers[$channel] ) ) {
- $this->loggers[$channel] = isset( $singletons['loggers'][$channel] )
- ? $singletons['loggers'][$channel] : null;
+ $this->loggers[$channel] = $singletons['loggers'][$channel] ?? null;
}
$singletons['loggers'][$channel] = $logger;
} elseif ( $provider instanceof LegacySpi ) {
if ( !isset( $this->loggers[$channel] ) ) {
- $this->loggers[$channel] = isset( $singletons[$channel] ) ? $singletons[$channel] : null;
+ $this->loggers[$channel] = $singletons[$channel] ?? null;
}
$singletons[$channel] = $logger;
} else {
* @since 1.18
*/
public function dbPrefix() {
- return $this->db->getType() == 'oracle' ? self::ORA_DB_PREFIX : self::DB_PREFIX;
+ return self::getTestPrefixFor( $this->db );
+ }
+
+ /**
+ * @param IDatabase $db
+ * @return string
+ * @since 1.32
+ */
+ public static function getTestPrefixFor( IDatabase $db ) {
+ return $db->getType() == 'oracle' ? self::ORA_DB_PREFIX : self::DB_PREFIX;
}
/**
* Should be called from addDBData().
*
* @since 1.25 ($namespace in 1.28)
- * @param string|title $pageName Page name or title
+ * @param string|Title $pageName Page name or title
* @param string $text Page's content
- * @param int $namespace Namespace id (name cannot already contain namespace)
+ * @param int|null $namespace Namespace id (name cannot already contain namespace)
+ * @param User|null $user If null, static::getTestSysop()->getUser() is used.
* @return array Title object and page id
*/
protected function insertPage(
$pageName,
$text = 'Sample page for unit test.',
- $namespace = null
+ $namespace = null,
+ User $user = null
) {
if ( is_string( $pageName ) ) {
$title = Title::newFromText( $pageName, $namespace );
$title = $pageName;
}
- $user = static::getTestSysop()->getUser();
+ if ( !$user ) {
+ $user = static::getTestSysop()->getUser();
+ }
$comment = __METHOD__ . ': Sample page for unit test.';
$page = WikiPage::factory( $title );
public function addDBData() {
}
- private function addCoreDBData() {
+ /**
+ * @since 1.32
+ */
+ protected function addCoreDBData() {
if ( $this->db->getType() == 'oracle' ) {
# Insert 0 user to prevent FK violations
# Anonymous user
}
/**
- * Setups a database with the given prefix.
+ * 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.
*
* If reuseDB is true and certain conditions apply, it will just change the prefix.
* Otherwise, it will clone the tables and change the prefix.
*
- * Clones all tables in the given database (whatever database that connection has
- * open), to versions with the test prefix.
- *
* @param IMaintainableDatabase $db Database to use
- * @param string $prefix Prefix to use for test tables
+ * @param string|null $prefix Prefix to use for test tables. If not given, the prefix is determined
+ * automatically for $db.
* @return bool True if tables were cloned, false if only the prefix was changed
*/
- protected static function setupDatabaseWithTestPrefix( IMaintainableDatabase $db, $prefix ) {
- $tablesCloned = self::listTables( $db );
- $dbClone = new CloneDatabase( $db, $tablesCloned, $prefix );
- $dbClone->useTemporaryTables( self::$useTemporaryTables );
-
- $db->_originalTablePrefix = $db->tablePrefix();
+ protected static function setupDatabaseWithTestPrefix(
+ IMaintainableDatabase $db,
+ $prefix = null
+ ) {
+ if ( $prefix === null ) {
+ $prefix = self::getTestPrefixFor( $db );
+ }
if ( ( $db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) {
- CloneDatabase::changePrefix( $prefix );
-
+ $db->tablePrefix( $prefix );
return false;
- } else {
+ }
+
+ if ( !isset( $db->_originalTablePrefix ) ) {
+ $oldPrefix = $db->tablePrefix();
+
+ if ( $oldPrefix === $prefix ) {
+ // table already has the correct prefix, but presumably no cloned tables
+ $oldPrefix = self::$oldTablePrefix;
+ }
+
+ $db->tablePrefix( $oldPrefix );
+ $tablesCloned = self::listTables( $db );
+ $dbClone = new CloneDatabase( $db, $tablesCloned, $prefix, $oldPrefix );
+ $dbClone->useTemporaryTables( self::$useTemporaryTables );
+
$dbClone->cloneTableStructure();
- return true;
+
+ $db->tablePrefix( $prefix );
+ $db->_originalTablePrefix = $oldPrefix;
}
+
+ return true;
}
/**
if ( self::isUsingExternalStoreDB() ) {
self::setupExternalStoreTestDBs( $testPrefix );
}
+
+ // NOTE: Change the prefix in the LBFactory and $wgDBprefix, to prevent
+ // *any* database connections to operate on live data.
+ CloneDatabase::changePrefix( $testPrefix );
}
/**
/**
* Clones the External Store database(s) for testing
*
- * @param string $testPrefix Prefix for test tables
+ * @param string|null $testPrefix Prefix for test tables. Will be determined automatically
+ * if not given.
*/
- protected static function setupExternalStoreTestDBs( $testPrefix ) {
+ protected static function setupExternalStoreTestDBs( $testPrefix = null ) {
$connections = self::getExternalStoreDatabaseConnections();
foreach ( $connections as $dbw ) {
- // Hack: cloneTableStructure sets $wgDBprefix to the unit test
- // prefix,. Even though listTables now uses tablePrefix, that
- // itself is populated from $wgDBprefix by default.
-
- // We have to set it back, or we won't find the original 'blobs'
- // table to copy.
-
- $dbw->tablePrefix( self::$oldTablePrefix );
self::setupDatabaseWithTestPrefix( $dbw, $testPrefix );
}
}
/**
* @throws LogicException if the given database connection is not a set up to use
* mock tables.
+ *
+ * @since 1.31 this is no longer private.
*/
- private function ensureMockDatabaseConnection( IDatabase $db ) {
+ protected function ensureMockDatabaseConnection( IDatabase $db ) {
if ( $db->tablePrefix() !== $this->dbPrefix() ) {
throw new LogicException(
'Trying to delete mock tables, but table prefix does not indicate a mock database.'
}
/**
- * Undoes the dpecified schema overrides..
+ * Undoes the specified schema overrides..
* Called once per test class, just before addDataOnce().
*
* @param IMaintainableDatabase $db
$this->ensureMockDatabaseConnection( $db );
$oldOverrides = $oldOverrides + self::$schemaOverrideDefaults;
- $originalTables = $this->listOriginalTables( $db );
+ $originalTables = $this->listOriginalTables( $db, 'unprefixed' );
// Drop tables that need to be restored or removed.
$tablesToDrop = array_merge( $oldOverrides['create'], $oldOverrides['alter'] );
*/
private function setUpSchema( IMaintainableDatabase $db ) {
// Undo any active overrides.
- $oldOverrides = isset( $db->_schemaOverrides ) ? $db->_schemaOverrides
- : self::$schemaOverrideDefaults;
+ $oldOverrides = $db->_schemaOverrides ?? self::$schemaOverrideDefaults;
if ( $oldOverrides['alter'] || $oldOverrides['create'] || $oldOverrides['drop'] ) {
$this->undoSchemaOverrides( $db, $oldOverrides );
$this->ensureMockDatabaseConnection( $db );
// Drop the tables that will be created by the schema scripts.
- $originalTables = $this->listOriginalTables( $db );
+ $originalTables = $this->listOriginalTables( $db, 'unprefixed' );
$tablesToDrop = array_intersect( $originalTables, $overrides['create'] );
if ( $tablesToDrop ) {
if ( $tbl === 'page' ) {
// Forget about the pages since they don't
// exist in the DB.
- LinkCache::singleton()->clear();
+ MediaWikiServices::getInstance()->getLinkCache()->clear();
}
}
}
* Lists all tables in the live database schema.
*
* @param IMaintainableDatabase $db
+ * @param string $prefix Either 'prefixed' or 'unprefixed'
* @return array
*/
- private function listOriginalTables( IMaintainableDatabase $db ) {
+ private function listOriginalTables( IMaintainableDatabase $db, $prefix = 'prefixed' ) {
if ( !isset( $db->_originalTablePrefix ) ) {
throw new LogicException( 'No original table prefix know, cannot list tables!' );
}
$originalTables = $db->listTables( $db->_originalTablePrefix, __METHOD__ );
+ if ( $prefix === 'unprefixed' ) {
+ $originalPrefixRegex = '/^' . preg_quote( $db->_originalTablePrefix ) . '/';
+ $originalTables = array_map(
+ function ( $pt ) use ( $originalPrefixRegex ) {
+ return preg_replace( $originalPrefixRegex, '', $pt );
+ },
+ $originalTables
+ );
+ }
+
return $originalTables;
}
throw new LogicException( 'No original table prefix know, cannot restore tables!' );
}
- $originalTables = $this->listOriginalTables( $db );
+ $originalTables = $this->listOriginalTables( $db, 'unprefixed' );
$tables = array_intersect( $tables, $originalTables );
$dbClone = new CloneDatabase( $db, $tables, $db->tablePrefix(), $db->_originalTablePrefix );
private function resetDB( $db, $tablesUsed ) {
if ( $db ) {
$userTables = [ 'user', 'user_groups', 'user_properties', 'actor' ];
- $pageTables = [ 'page', 'revision', 'ip_changes', 'revision_comment_temp',
- 'revision_actor_temp', 'comment' ];
+ $pageTables = [
+ 'page', 'revision', 'ip_changes', 'revision_comment_temp', 'comment', 'archive',
+ 'revision_actor_temp', 'slots', 'content', 'content_models', 'slot_roles',
+ ];
$coreDBDataTables = array_merge( $userTables, $pageTables );
// If any of the user or page tables were marked as used, we should clear all of them.
$tablesUsed = array_unique( array_merge( $tablesUsed, $pageTables ) );
}
+ // Postgres, Oracle, and MSSQL all use mwuser/pagecontent
+ // instead of user/text. But Postgres does not remap the
+ // table name in tableExists(), so we mark the real table
+ // names as being used.
+ if ( $db->getType() === 'postgres' ) {
+ if ( in_array( 'user', $tablesUsed ) ) {
+ $tablesUsed[] = 'mwuser';
+ }
+ if ( in_array( 'text', $tablesUsed ) ) {
+ $tablesUsed[] = 'pagecontent';
+ }
+ }
+
$truncate = in_array( $db->getType(), [ 'oracle', 'mysql' ] );
foreach ( $tablesUsed as $tbl ) {
// TODO: reset interwiki table to its original content.
if ( $tbl === 'page' ) {
// Forget about the pages since they don't
// exist in the DB.
- LinkCache::singleton()->clear();
+ MediaWikiServices::getInstance()->getLinkCache()->clear();
}
}
return $tables;
}
+ /**
+ * Copy test data from one database connection to another.
+ *
+ * This should only be used for small data sets.
+ *
+ * @param IDatabase $source
+ * @param IDatabase $target
+ */
+ public function copyTestData( IDatabase $source, IDatabase $target ) {
+ $tables = self::listOriginalTables( $source, 'unprefixed' );
+
+ foreach ( $tables as $table ) {
+ $res = $source->select( $table, '*', [], __METHOD__ );
+ $allRows = [];
+
+ foreach ( $res as $row ) {
+ $allRows[] = (array)$row;
+ }
+
+ $target->insert( $table, $allRows, __METHOD__, [ 'IGNORE' ] );
+ }
+ }
+
/**
* @throws MWException
* @since 1.18
uasort(
$array,
function ( $a, $b ) {
- return serialize( $a ) > serialize( $b ) ? 1 : -1;
+ return serialize( $a ) <=> serialize( $b );
}
);
}