X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=tests%2Fphpunit%2FMediaWikiTestCase.php;h=d675e8518b27044e4fe2316a62ae94e0ee2614d5;hb=8025629395cc13bc8f6682c007ca51a5d962d7d2;hp=17147eb732e2cd26f288ed32f4d02e79893c9f7d;hpb=532210719184db1d0449b09bfded13cb1dfc1759;p=lhc%2Fweb%2Fwiklou.git diff --git a/tests/phpunit/MediaWikiTestCase.php b/tests/phpunit/MediaWikiTestCase.php index 17147eb732..d675e8518b 100644 --- a/tests/phpunit/MediaWikiTestCase.php +++ b/tests/phpunit/MediaWikiTestCase.php @@ -8,8 +8,6 @@ use Psr\Log\LoggerInterface; use Wikimedia\Rdbms\IDatabase; use Wikimedia\Rdbms\IMaintainableDatabase; use Wikimedia\Rdbms\Database; -use Wikimedia\Rdbms\IResultWrapper; -use Wikimedia\Rdbms\LBFactory; use Wikimedia\TestingAccessWrapper; /** @@ -29,6 +27,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { /** * The local service locator, created during setUp(). + * @var MediaWikiServices */ private $localServices; @@ -101,12 +100,6 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { */ private $mwGlobalsToUnset = []; - /** - * Holds original contents of interwiki table - * @var IResultWrapper - */ - private $interwikiTable = null; - /** * Holds original loggers which have been replaced by setLogger() * @var LoggerInterface[] @@ -114,9 +107,10 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { 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. @@ -140,11 +134,6 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { $this->backupGlobals = false; $this->backupStaticAttributes = false; - $this->testLogger = self::getTestLogger(); - } - - private static function getTestLogger() { - return LoggerFactory::getInstance( 'tests-phpunit' ); } public function __destruct() { @@ -207,9 +196,17 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { * * @param Title|string|null $title * @return WikiPage - * @throws MWException + * @throws MWException If this test cases's needsDB() method doesn't return true. + * Test cases can use "@group Database" to enable database test support, + * or list the tables under testing in $this->tablesUsed, or override the + * needsDB() method. */ protected function getExistingTestPage( $title = null ) { + if ( !$this->needsDB() ) { + throw new MWException( 'When testing which pages, the test cases\'s needsDB()' . + ' method should return true. Use @group Database or $this->tablesUsed.' ); + } + $title = ( $title === null ) ? 'UTPage' : $title; $title = is_string( $title ) ? Title::newFromText( $title ) : $title; $page = WikiPage::factory( $title ); @@ -235,9 +232,17 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { * * @param Title|string|null $title * @return WikiPage - * @throws MWException + * @throws MWException If this test cases's needsDB() method doesn't return true. + * Test cases can use "@group Database" to enable database test support, + * or list the tables under testing in $this->tablesUsed, or override the + * needsDB() method. */ protected function getNonexistingTestPage( $title = null ) { + if ( !$this->needsDB() ) { + throw new MWException( 'When testing which pages, the test cases\'s needsDB()' . + ' method should return true. Use @group Database or $this->tablesUsed.' ); + } + $title = ( $title === null ) ? 'UTPage-' . rand( 0, 100000 ) : $title; $title = is_string( $title ) ? Title::newFromText( $title ) : $title; $page = WikiPage::factory( $title ); @@ -302,38 +307,6 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { return $testConfig; } - /** - * @param ConfigFactory $oldConfigFactory - * @param LBFactory $oldLoadBalancerFactory - * @param MediaWikiServices $newServices - * - * @throws MWException - */ - private static function installTestServices( - ConfigFactory $oldConfigFactory, - LBFactory $oldLoadBalancerFactory, - MediaWikiServices $newServices - ) { - // Use bootstrap config for all configuration. - // This allows config overrides via global variables to take effect. - $bootstrapConfig = $newServices->getBootstrapConfig(); - $newServices->resetServiceForTesting( 'ConfigFactory' ); - $newServices->redefineService( - 'ConfigFactory', - self::makeTestConfigFactoryInstantiator( - $oldConfigFactory, - [ 'main' => $bootstrapConfig ] - ) - ); - $newServices->resetServiceForTesting( 'DBLoadBalancerFactory' ); - $newServices->redefineService( - 'DBLoadBalancerFactory', - function ( MediaWikiServices $services ) use ( $oldLoadBalancerFactory ) { - return $oldLoadBalancerFactory; - } - ); - } - /** * @param ConfigFactory $oldFactory * @param Config[] $configurations @@ -369,7 +342,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { * Resets some non-service singleton instances and other static caches. It's not necessary to * reset services here. */ - private function resetNonServiceCaches() { + public static function resetNonServiceCaches() { global $wgRequest, $wgJobClasses; foreach ( $wgJobClasses as $type => $class ) { @@ -393,13 +366,20 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { } public function run( PHPUnit_Framework_TestResult $result = null ) { + if ( $result instanceof MediaWikiTestResult ) { + $this->cliArgs = $result->getMediaWikiCliArgs(); + } $this->overrideMwServices(); - $needsResetDB = false; + if ( $this->needsDB() && !$this->isTestInDatabaseGroup() ) { + throw new Exception( + get_class( $this ) . ' apparently needsDB but is not in the Database group' + ); + } + $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' ); @@ -426,18 +406,14 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { $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(); + self::restoreMwServices(); $this->localServices = null; - MediaWikiServices::forceGlobalInstance( self::$originalServices ); } /** @@ -530,14 +506,8 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { } } - // 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->resetNonServiceCaches(); + self::resetNonServiceCaches(); // XXX: reset maintenance triggers // Hook into period lag checks which often happen in long-running scripts @@ -582,6 +552,8 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { } } + // Re-enable any disabled deprecation warnings + MWDebug::clearLog(); // Restore mw globals foreach ( $this->mwGlobals as $key => $value ) { $GLOBALS[$key] = $value; @@ -653,6 +625,11 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { throw new Exception( __METHOD__ . ' must be called after MediaWikiTestCase::run()' ); } + if ( $this->localServices !== MediaWikiServices::getInstance() ) { + throw new Exception( __METHOD__ . ' will not work because the global MediaWikiServices ' + . 'instance has been replaced by test code.' ); + } + $this->localServices->disableService( $name ); $this->localServices->redefineService( $name, @@ -739,6 +716,12 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { if ( !$this->localServices ) { throw new Exception( __METHOD__ . ' must be called after MediaWikiTestCase::run()' ); } + + if ( $this->localServices !== MediaWikiServices::getInstance() ) { + throw new Exception( __METHOD__ . ' will not work because the global MediaWikiServices ' + . 'instance has been replaced by test code.' ); + } + MWNamespace::clearCaches(); Language::clearCaches(); @@ -908,6 +891,44 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { protected function overrideMwServices( Config $configOverrides = null, array $services = [] ) { + $newInstance = self::installMockMwServices( $configOverrides ); + + if ( $this->localServices ) { + $this->localServices->destroy(); + } + + $this->localServices = $newInstance; + + foreach ( $services as $name => $callback ) { + $newInstance->redefineService( $name, $callback ); + } + + return $newInstance; + } + + /** + * Creates a new "mock" MediaWikiServices instance, and installs it. + * This effectively resets all cached states in services, with the exception of + * the ConfigFactory and the DBLoadBalancerFactory service, which are inherited from + * the original MediaWikiServices. + * + * @note The new original MediaWikiServices instance can later be restored by calling + * restoreMwServices(). That original is determined by the first call to this method, or + * by setUpBeforeClass, whichever is called first. The caller is responsible for managing + * and, when appropriate, destroying any other MediaWikiServices instances that may get + * replaced when calling this method. + * + * @param Config|null $configOverrides Configuration overrides for the new MediaWikiServices + * instance. + * + * @return MediaWikiServices the new mock service locator. + */ + public static function installMockMwServices( Config $configOverrides = null ) { + // Make sure we have the original service locator + if ( !self::$originalServices ) { + self::$originalServices = MediaWikiServices::getInstance(); + } + if ( !$configOverrides ) { $configOverrides = new HashConfig(); } @@ -916,34 +937,65 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { $oldLoadBalancerFactory = self::$originalServices->getDBLoadBalancerFactory(); $testConfig = self::makeTestConfig( null, $configOverrides ); - $newInstance = new MediaWikiServices( $testConfig ); + $newServices = new MediaWikiServices( $testConfig ); // Load the default wiring from the specified files. // NOTE: this logic mirrors the logic in MediaWikiServices::newInstance. $wiringFiles = $testConfig->get( 'ServiceWiringFiles' ); - $newInstance->loadWiringFiles( $wiringFiles ); + $newServices->loadWiringFiles( $wiringFiles ); // Provide a traditional hook point to allow extensions to configure services. - Hooks::run( 'MediaWikiServices', [ $newInstance ] ); + Hooks::run( 'MediaWikiServices', [ $newServices ] ); - foreach ( $services as $name => $callback ) { - $newInstance->redefineService( $name, $callback ); + // Use bootstrap config for all configuration. + // This allows config overrides via global variables to take effect. + $bootstrapConfig = $newServices->getBootstrapConfig(); + $newServices->resetServiceForTesting( 'ConfigFactory' ); + $newServices->redefineService( + 'ConfigFactory', + self::makeTestConfigFactoryInstantiator( + $oldConfigFactory, + [ 'main' => $bootstrapConfig ] + ) + ); + $newServices->resetServiceForTesting( 'DBLoadBalancerFactory' ); + $newServices->redefineService( + 'DBLoadBalancerFactory', + function ( MediaWikiServices $services ) use ( $oldLoadBalancerFactory ) { + return $oldLoadBalancerFactory; + } + ); + + MediaWikiServices::forceGlobalInstance( $newServices ); + return $newServices; + } + + /** + * Restores the original, non-mock MediaWikiServices instance. + * The previously active MediaWikiServices instance is destroyed, + * if it is different from the original that is to be restored. + * + * @note this if for internal use by test framework code. It should never be + * called from inside a test case, a data provider, or a setUp or tearDown method. + * + * @return bool true if the original service locator was restored, + * false if there was nothing too do. + */ + public static function restoreMwServices() { + if ( !self::$originalServices ) { + return false; } - self::installTestServices( - $oldConfigFactory, - $oldLoadBalancerFactory, - $newInstance - ); + $currentServices = MediaWikiServices::getInstance(); - if ( $this->localServices ) { - $this->localServices->destroy(); + if ( self::$originalServices === $currentServices ) { + return false; } - MediaWikiServices::forceGlobalInstance( $newInstance ); - $this->localServices = $newInstance; + MediaWikiServices::forceGlobalInstance( self::$originalServices ); + $currentServices->destroy(); - return $newInstance; + return true; } /** @@ -1081,18 +1133,18 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { */ public function needsDB() { // If the test says it uses database tables, it needs the database - if ( $this->tablesUsed ) { - return true; - } + return $this->tablesUsed || $this->isTestInDatabaseGroup(); + } + /** + * @return bool + * @since 1.32 + */ + protected function isTestInDatabaseGroup() { // If the test class says it belongs to the Database group, it needs the database. // NOTE: This ONLY checks for the group in the class level doc comment. $rc = new ReflectionClass( $this ); - if ( preg_match( '/@group +Database/im', $rc->getDocComment() ) ) { - return true; - } - - return false; + return (bool)preg_match( '/@group +Database/im', $rc->getDocComment() ); } /** @@ -1106,6 +1158,10 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { * @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 + * @throws MWException If this test cases's needsDB() method doesn't return true. + * Test cases can use "@group Database" to enable database test support, + * or list the tables under testing in $this->tablesUsed, or override the + * needsDB() method. */ protected function insertPage( $pageName, @@ -1113,6 +1169,11 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { $namespace = null, User $user = null ) { + if ( !$this->needsDB() ) { + throw new MWException( 'When testing which pages, the test cases\'s needsDB()' . + ' method should return true. Use @group Database or $this->tablesUsed.' ); + } + if ( is_string( $pageName ) ) { $title = Title::newFromText( $pageName, $namespace ); } else { @@ -1167,7 +1228,6 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { * @since 1.32 */ protected function addCoreDBData() { - $this->testLogger->info( __METHOD__ ); if ( $this->db->getType() == 'oracle' ) { # Insert 0 user to prevent FK violations # Anonymous user @@ -1255,52 +1315,6 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { 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. * @@ -1646,7 +1660,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { $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 ); @@ -1690,14 +1704,10 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { */ 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 ); @@ -1728,6 +1738,8 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { } 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(); } @@ -1759,22 +1771,14 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { $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__ - ); + // re-initialize site_stats table + if ( $tableName === 'site_stats' ) { + SiteStatsInit::doPlaceholderInit(); } } @@ -1861,11 +1865,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { * @return mixed */ public function getCliArg( $offset ) { - if ( isset( PHPUnitMaintClass::$additionalOptions[$offset] ) ) { - return PHPUnitMaintClass::$additionalOptions[$offset]; - } - - return null; + return $this->cliArgs[$offset] ?? null; } /** @@ -1874,7 +1874,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { * @param mixed $value */ public function setCliArg( $offset, $value ) { - PHPUnitMaintClass::$additionalOptions[$offset] = $value; + $this->cliArgs[$offset] = $value; } /** @@ -2251,7 +2251,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { protected function assertFileContains( $fileName, $actualData, - $createIfMissing = true, + $createIfMissing = false, $msg = '' ) { if ( $createIfMissing ) { @@ -2272,8 +2272,17 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { * @param string $summary Optional summary string for the revision * @param int $defaultNs Optional namespace id * @return array Array as returned by WikiPage::doEditContent() + * @throws MWException If this test cases's needsDB() method doesn't return true. + * Test cases can use "@group Database" to enable database test support, + * or list the tables under testing in $this->tablesUsed, or override the + * needsDB() method. */ protected function editPage( $pageName, $text, $summary = '', $defaultNs = NS_MAIN ) { + if ( !$this->needsDB() ) { + throw new MWException( 'When testing which pages, the test cases\'s needsDB()' . + ' method should return true. Use @group Database or $this->tablesUsed.' ); + } + $title = Title::newFromText( $pageName, $defaultNs ); $page = WikiPage::factory( $title );