Fix PhanTypeMismatchDeclaredParam
[lhc/web/wiklou.git] / tests / phpunit / MediaWikiTestCase.php
index 4c6caa3..82739a7 100644 (file)
@@ -178,6 +178,55 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                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.
         *
@@ -666,7 +715,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
         *
         * @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,
@@ -834,7 +883,8 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
         *
         * @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.
         *
@@ -914,7 +964,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
         *   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;
@@ -947,13 +997,12 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                $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 {
@@ -995,7 +1044,16 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
         * @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;
        }
 
        /**
@@ -1024,15 +1082,17 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
         * 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 );
@@ -1040,7 +1100,9 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                        $title = $pageName;
                }
 
-               $user = static::getTestSysop()->getUser();
+               if ( !$user ) {
+                       $user = static::getTestSysop()->getUser();
+               }
                $comment = __METHOD__ . ': Sample page for unit test.';
 
                $page = WikiPage::factory( $title );
@@ -1082,7 +1144,10 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
        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
@@ -1171,33 +1236,95 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
        }
 
        /**
-        * 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;
        }
 
        /**
@@ -1216,6 +1343,10 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                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 );
        }
 
        /**
@@ -1270,19 +1401,12 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
        /**
         * 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 );
                }
        }
@@ -1336,8 +1460,10 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
        /**
         * @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.'
@@ -1371,7 +1497,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
        }
 
        /**
-        * Undoes the dpecified schema overrides..
+        * Undoes the specified schema overrides..
         * Called once per test class, just before addDataOnce().
         *
         * @param IMaintainableDatabase $db
@@ -1381,7 +1507,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                $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'] );
@@ -1407,8 +1533,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
         */
        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 );
@@ -1443,7 +1568,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                $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 ) {
@@ -1488,7 +1613,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                        if ( $tbl === 'page' ) {
                                // Forget about the pages since they don't
                                // exist in the DB.
-                               LinkCache::singleton()->clear();
+                               MediaWikiServices::getInstance()->getLinkCache()->clear();
                        }
                }
        }
@@ -1497,14 +1622,25 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
         * 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;
        }
 
@@ -1523,7 +1659,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                        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 );
@@ -1541,8 +1677,10 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
        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.
@@ -1554,6 +1692,19 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                                $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.
@@ -1579,7 +1730,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                                if ( $tbl === 'page' ) {
                                        // Forget about the pages since they don't
                                        // exist in the DB.
-                                       LinkCache::singleton()->clear();
+                                       MediaWikiServices::getInstance()->getLinkCache()->clear();
                                }
                        }
 
@@ -1634,6 +1785,29 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                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
@@ -1818,7 +1992,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                uasort(
                        $array,
                        function ( $a, $b ) {
-                               return serialize( $a ) > serialize( $b ) ? 1 : -1;
+                               return serialize( $a ) <=> serialize( $b );
                        }
                );
        }