X-Git-Url: https://git.cyclocoop.org/%242?a=blobdiff_plain;f=tests%2Fphpunit%2FMediaWikiTestCase.php;h=a99b4b926b4befdb7593bf655bd5fe9a340ec2a5;hb=d1c4eafef54f638ef025816b17502250a5be9e73;hp=d5192ace381f922f1953aa91d5424b1e7532a0a1;hpb=9da08fc66a5b8819f71ec8544fcc284c72bc23cf;p=lhc%2Fweb%2Fwiklou.git diff --git a/tests/phpunit/MediaWikiTestCase.php b/tests/phpunit/MediaWikiTestCase.php index d5192ace38..a99b4b926b 100644 --- a/tests/phpunit/MediaWikiTestCase.php +++ b/tests/phpunit/MediaWikiTestCase.php @@ -29,6 +29,8 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { public static $users; /** + * Primary database + * * @var DatabaseBase * @since 1.18 */ @@ -132,14 +134,21 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { $this->checkDbIsSupported(); if ( !self::$dbSetup ) { - // switch to a temporary clone of the database - self::setupTestDB( $this->db, $this->dbPrefix() ); + $this->setupAllTestDBs(); + $this->addCoreDBData(); if ( ( $this->db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) { - $this->resetDB(); + $this->resetDB( $this->db, $this->tablesUsed ); } } - $this->addCoreDBData(); + + // TODO: the DB setup should be done in setUpBeforeClass(), so the test DB + // is available in subclass's setUpBeforeClass() and setUp() methods. + // This would also remove the need for the HACK that is oncePerClass(). + if ( $this->oncePerClass() ) { + $this->addDBDataOnce(); + } + $this->addDBData(); $needsResetDB = true; } @@ -147,10 +156,26 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { parent::run( $result ); if ( $needsResetDB ) { - $this->resetDB(); + $this->resetDB( $this->db, $this->tablesUsed ); } } + /** + * @return bool + */ + private function oncePerClass() { + // Remember current test class in the database connection, + // so we know when we need to run addData. + + $class = static::class; + + $first = !isset( $this->db->_hasDataForTestClass ) + || $this->db->_hasDataForTestClass !== $class; + + $this->db->_hasDataForTestClass = $class; + return $first; + } + /** * @since 1.21 * @@ -222,6 +247,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { } DeferredUpdates::clearPendingUpdates(); + ObjectCache::getMainWANInstance()->clearProcessCache(); ob_start( 'MediaWikiTestCase::wfResetOutputBuffersBarrier' ); } @@ -415,6 +441,33 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { $this->setMwGlobals( $name, $merged ); } + /** + * @since 1.27 + * @param string|Language $lang + */ + public function setUserLang( $lang ) { + RequestContext::getMain()->setLanguage( $lang ); + $this->setMwGlobals( 'wgLang', RequestContext::getMain()->getLanguage() ); + } + + /** + * @since 1.27 + * @param string|Language $lang + */ + public function setContentLang( $lang ) { + if ( $lang instanceof Language ) { + $langCode = $lang->getCode(); + $langObj = $lang; + } else { + $langCode = $lang; + $langObj = Language::factory( $langCode ); + } + $this->setMwGlobals( [ + 'wgLanguageCode' => $langCode, + 'wgContLang' => $langObj, + ] ); + } + /** * Sets the logger for a specified channel, for the duration of the test. * @since 1.27 @@ -513,10 +566,6 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { $user = User::newFromName( 'UTSysop' ); $comment = __METHOD__ . ': Sample page for unit test.'; - // Avoid memory leak...? - // LinkCache::singleton()->clear(); - // Maybe. But doing this absolutely breaks $title->isRedirect() when called during unit tests.... - $page = WikiPage::factory( $title ); $page->doEditContent( ContentHandler::makeContent( $text, $title ), $comment, 0, false, $user ); @@ -527,8 +576,29 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { } /** - * Stub. If a test needs to add additional data to the database, it should - * implement this method and do so + * Stub. If a test suite needs to add additional data to the database, it should + * implement this method and do so. This method is called once per test suite + * (i.e. once per class). + * + * Note data added by this method may be removed by resetDB() depending on + * the contents of $tablesUsed. + * + * To add additional data between test function runs, override prepareDB(). + * + * @see addDBData() + * @see resetDB() + * + * @since 1.27 + */ + public function addDBDataOnce() { + } + + /** + * Stub. Subclasses may override this to prepare the database. + * Called before every test run (test function or data set). + * + * @see addDBDataOnce() + * @see resetDB() * * @since 1.18 */ @@ -618,6 +688,52 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { self::$dbSetup = false; } + /** + * Setups a database with 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 DatabaseBase $db Database to use + * @param string $prefix Prefix to use for test tables + * @return bool True if tables were cloned, false if only the prefix was changed + */ + protected static function setupDatabaseWithTestPrefix( DatabaseBase $db, $prefix ) { + $tablesCloned = self::listTables( $db ); + $dbClone = new CloneDatabase( $db, $tablesCloned, $prefix ); + $dbClone->useTemporaryTables( self::$useTemporaryTables ); + + if ( ( $db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) { + CloneDatabase::changePrefix( $prefix ); + + return false; + } else { + $dbClone->cloneTableStructure(); + return true; + } + } + + /** + * Set up all test DBs + */ + public function setupAllTestDBs() { + global $wgDBprefix; + + self::$oldTablePrefix = $wgDBprefix; + + $testPrefix = $this->dbPrefix(); + + // switch to a temporary clone of the database + self::setupTestDB( $this->db, $testPrefix ); + + if ( self::isUsingExternalStoreDB() ) { + self::setupExternalStoreTestDBs( $testPrefix ); + } + } + /** * Creates an empty skeleton of the wiki database by cloning its structure * to equivalent tables using the given $prefix. Then sets MediaWiki to @@ -640,8 +756,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { * @throws MWException If the database table prefix is already $prefix */ public static function setupTestDB( DatabaseBase $db, $prefix ) { - global $wgDBprefix; - if ( $wgDBprefix === $prefix ) { + if ( $db->tablePrefix() === $prefix ) { throw new MWException( 'Cannot run unit tests, the database prefix is already "' . $prefix . '"' ); } @@ -650,49 +765,110 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { return; } - $tablesCloned = self::listTables( $db ); - $dbClone = new CloneDatabase( $db, $tablesCloned, $prefix ); - $dbClone->useTemporaryTables( self::$useTemporaryTables ); - self::$dbSetup = true; - self::$oldTablePrefix = $wgDBprefix; - - if ( ( $db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) { - CloneDatabase::changePrefix( $prefix ); + if ( !self::setupDatabaseWithTestPrefix( $db, $prefix ) ) { return; - } else { - $dbClone->cloneTableStructure(); } + // Assuming this isn't needed for External Store database, and not sure if the procedure + // would be available there. if ( $db->getType() == 'oracle' ) { $db->query( 'BEGIN FILL_WIKI_INFO; END;' ); } } + /** + * Clones the External Store database(s) for testing + * + * @param string $testPrefix Prefix for test tables + */ + protected static function setupExternalStoreTestDBs( $testPrefix ) { + $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 ); + } + } + + /** + * Gets master database connections for all of the ExternalStoreDB + * stores configured in $wgDefaultExternalStore. + * + * @return array Array of DatabaseBase master connections + */ + + protected static function getExternalStoreDatabaseConnections() { + global $wgDefaultExternalStore; + + $externalStoreDB = ExternalStore::getStoreObject( 'DB' ); + $defaultArray = (array) $wgDefaultExternalStore; + $dbws = []; + foreach ( $defaultArray as $url ) { + if ( strpos( $url, 'DB://' ) === 0 ) { + list( $proto, $cluster ) = explode( '://', $url, 2 ); + $dbw = $externalStoreDB->getMaster( $cluster ); + $dbws[] = $dbw; + } + } + + return $dbws; + } + + /** + * Check whether ExternalStoreDB is being used + * + * @return bool True if it's being used + */ + protected static function isUsingExternalStoreDB() { + global $wgDefaultExternalStore; + if ( !$wgDefaultExternalStore ) { + return false; + } + + $defaultArray = (array) $wgDefaultExternalStore; + foreach ( $defaultArray as $url ) { + if ( strpos( $url, 'DB://' ) === 0 ) { + return true; + } + } + + return false; + } + /** * Empty all tables so they can be repopulated for tests + * + * @param DatabaseBase $db|null Database to reset + * @param array $tablesUsed Tables to reset */ - private function resetDB() { - if ( $this->db ) { - if ( $this->db->getType() == 'oracle' ) { - if ( self::$useTemporaryTables ) { - wfGetLB()->closeAll(); - $this->db = wfGetDB( DB_MASTER ); + private function resetDB( $db, $tablesUsed ) { + if ( $db ) { + $truncate = in_array( $db->getType(), [ 'oracle', 'mysql' ] ); + foreach ( $tablesUsed as $tbl ) { + // TODO: reset interwiki and user tables to their original content. + if ( $tbl == 'interwiki' || $tbl == 'user' ) { + continue; + } + + if ( $truncate ) { + $db->query( 'TRUNCATE TABLE ' . $db->tableName( $tbl ), __METHOD__ ); } else { - foreach ( $this->tablesUsed as $tbl ) { - if ( $tbl == 'interwiki' ) { - continue; - } - $this->db->query( 'TRUNCATE TABLE ' . $this->db->tableName( $tbl ), __METHOD__ ); - } + + $db->delete( $tbl, '*', __METHOD__ ); } - } else { - foreach ( $this->tablesUsed as $tbl ) { - if ( $tbl == 'interwiki' || $tbl == 'user' ) { - continue; - } - $this->db->delete( $tbl, '*', __METHOD__ ); + + if ( $tbl === 'page' ) { + // Forget about the pages since they don't + // exist in the DB. + LinkCache::singleton()->clear(); } } } @@ -729,10 +905,8 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { $this->assertTrue( $value == '', $msg ); } - private static function unprefixTable( $tableName ) { - global $wgDBprefix; - - return substr( $tableName, strlen( $wgDBprefix ) ); + private static function unprefixTable( &$tableName, $ind, $prefix ) { + $tableName = substr( $tableName, strlen( $prefix ) ); } private static function isNotUnittest( $table ) { @@ -747,16 +921,15 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { * @return array */ public static function listTables( $db ) { - global $wgDBprefix; - - $tables = $db->listTables( $wgDBprefix, __METHOD__ ); + $prefix = $db->tablePrefix(); + $tables = $db->listTables( $prefix, __METHOD__ ); if ( $db->getType() === 'mysql' ) { # bug 43571: cannot clone VIEWs under MySQL - $views = $db->listViews( $wgDBprefix, __METHOD__ ); + $views = $db->listViews( $prefix, __METHOD__ ); $tables = array_diff( $tables, $views ); } - $tables = array_map( [ __CLASS__, 'unprefixTable' ], $tables ); + array_walk( $tables, [ __CLASS__, 'unprefixTable' ], $prefix ); // Don't duplicate test tables from the previous fataled run $tables = array_filter( $tables, [ __CLASS__, 'isNotUnittest' ] ); @@ -1149,33 +1322,6 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { return $loaded; } - /** - * Asserts that an exception of the specified type occurs when running - * the provided code. - * - * @since 1.21 - * @deprecated since 1.22 Use setExpectedException - * - * @param callable $code - * @param string $expected - * @param string $message - */ - protected function assertException( $code, $expected = 'Exception', $message = '' ) { - $pokemons = null; - - try { - call_user_func( $code ); - } catch ( Exception $pokemons ) { - // Gotta Catch 'Em All! - } - - if ( $message === '' ) { - $message = 'An exception of type "' . $expected . '" should have been thrown'; - } - - $this->assertInstanceOf( $expected, $pokemons, $message ); - } - /** * Asserts that the given string is a valid HTML snippet. * Wraps the given string in the required top level tags and @@ -1283,4 +1429,5 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { public static function wfResetOutputBuffersBarrier( $buffer ) { return $buffer; } + }