Merge "resourceloader: Remove unused string[] module logic in register()"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 4 Sep 2018 13:17:07 +0000 (13:17 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 4 Sep 2018 13:17:07 +0000 (13:17 +0000)
47 files changed:
.eslintrc.json
includes/GlobalFunctions.php
includes/MediaWikiServices.php
includes/ServiceWiring.php
includes/Storage/NameTableStoreFactory.php [new file with mode: 0644]
includes/Storage/RevisionStoreFactory.php
includes/installer/DatabaseUpdater.php
includes/installer/MysqlUpdater.php
includes/jobqueue/JobRunner.php
includes/jobqueue/jobs/RecentChangesUpdateJob.php
includes/jobqueue/jobs/RefreshLinksJob.php
includes/libs/rdbms/exception/DBReplicationWaitError.php
includes/libs/rdbms/lbfactory/ILBFactory.php
includes/libs/rdbms/lbfactory/LBFactory.php
includes/registration/ExtensionRegistry.php
includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/services/ServiceContainer.php
maintenance/Maintenance.php
maintenance/updateSpecialPages.php
resources/src/mediawiki.base/mediawiki.base.js
resources/src/mediawiki.base/mediawiki.errorLogger.js
resources/src/startup/mediawiki.js
resources/src/startup/mediawiki.log.js
resources/src/startup/mediawiki.requestIdleCallback.js
resources/src/startup/profiler.js
resources/src/startup/startup.js
tests/common/TestSetup.php
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/includes/PrefixSearchTest.php
tests/phpunit/includes/RevisionDbTestBase.php
tests/phpunit/includes/Storage/DerivedPageDataUpdaterTest.php
tests/phpunit/includes/Storage/NameTableStoreFactoryTest.php [new file with mode: 0644]
tests/phpunit/includes/Storage/PageUpdaterTest.php
tests/phpunit/includes/Storage/RevisionRecordTests.php
tests/phpunit/includes/Storage/RevisionStoreFactoryTest.php
tests/phpunit/includes/Storage/RevisionStoreTest.php
tests/phpunit/includes/api/ApiQuerySearchTest.php
tests/phpunit/includes/changetags/ChangeTagsTest.php
tests/phpunit/includes/diff/DifferenceEngineTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php
tests/phpunit/includes/search/SearchEnginePrefixTest.php
tests/phpunit/mocks/search/MockSearchEngine.php
tests/phpunit/mocks/search/MockSearchResultSet.php
tests/phpunit/phpunit.php
tests/phpunit/structure/SpecialPageFatalTest.php
tests/phpunit/tests/MediaWikiTestCaseTest.php

index fbf2a5a..f7a79ac 100644 (file)
@@ -1,13 +1,15 @@
 {
        "extends": "wikimedia",
        "env": {
-               "browser": true,
-               "jquery": true
+               "browser": true
        },
        "globals": {
                "require": false,
                "module": false,
+               "mw": false,
+               "$": false,
                "mediaWiki": false,
+               "jQuery": false,
                "OO": false
        },
        "rules": {
index d215e9f..4f12110 100644 (file)
@@ -30,7 +30,6 @@ use MediaWiki\Session\SessionManager;
 use MediaWiki\MediaWikiServices;
 use MediaWiki\Shell\Shell;
 use Wikimedia\ScopedCallback;
-use Wikimedia\Rdbms\DBReplicationWaitError;
 use Wikimedia\WrappedString;
 
 /**
@@ -2922,17 +2921,13 @@ function wfGetNull() {
  * @param float|null $ifWritesSince Only wait if writes were done since this UNIX timestamp
  * @param string|bool $wiki Wiki identifier accepted by wfGetLB
  * @param string|bool $cluster Cluster name accepted by LBFactory. Default: false.
- * @param int|null $timeout Max wait time. Default: 1 day (cli), ~10 seconds (web)
+ * @param int|null $timeout Max wait time. Default: 60 seconds (cli), 1 second (web)
  * @return bool Success (able to connect and no timeouts reached)
  * @deprecated since 1.27 Use LBFactory::waitForReplication
  */
 function wfWaitForSlaves(
        $ifWritesSince = null, $wiki = false, $cluster = false, $timeout = null
 ) {
-       if ( $timeout === null ) {
-               $timeout = wfIsCLI() ? 60 : 10;
-       }
-
        if ( $cluster === '*' ) {
                $cluster = false;
                $wiki = false;
@@ -2940,20 +2935,18 @@ function wfWaitForSlaves(
                $wiki = wfWikiID();
        }
 
-       try {
-               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
-               $lbFactory->waitForReplication( [
-                       'wiki' => $wiki,
-                       'cluster' => $cluster,
-                       'timeout' => $timeout,
-                       // B/C: first argument used to be "max seconds of lag"; ignore such values
-                       'ifWritesSince' => ( $ifWritesSince > 1e9 ) ? $ifWritesSince : null
-               ] );
-       } catch ( DBReplicationWaitError $e ) {
-               return false;
+       $opts = [
+               'wiki' => $wiki,
+               'cluster' => $cluster,
+               // B/C: first argument used to be "max seconds of lag"; ignore such values
+               'ifWritesSince' => ( $ifWritesSince > 1e9 ) ? $ifWritesSince : null
+       ];
+       if ( $timeout !== null ) {
+               $opts['timeout'] = $timeout;
        }
 
-       return true;
+       $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+       return $lbFactory->waitForReplication( $opts );
 }
 
 /**
index 5b53ad1..b236ca1 100644 (file)
@@ -21,6 +21,7 @@ use MediaWiki\Special\SpecialPageFactory;
 use MediaWiki\Storage\BlobStore;
 use MediaWiki\Storage\BlobStoreFactory;
 use MediaWiki\Storage\NameTableStore;
+use MediaWiki\Storage\NameTableStoreFactory;
 use MediaWiki\Storage\RevisionFactory;
 use MediaWiki\Storage\RevisionLookup;
 use MediaWiki\Storage\RevisionStore;
@@ -452,7 +453,7 @@ class MediaWikiServices extends ServiceContainer {
         * @return NameTableStore
         */
        public function getChangeTagDefStore() {
-               return $this->getService( 'ChangeTagDefStore' );
+               return $this->getService( 'NameTableStoreFactory' )->getChangeTagDef();
        }
 
        /**
@@ -500,7 +501,7 @@ class MediaWikiServices extends ServiceContainer {
         * @return NameTableStore
         */
        public function getContentModelStore() {
-               return $this->getService( 'ContentModelStore' );
+               return $this->getService( 'NameTableStoreFactory' )->getContentModels();
        }
 
        /**
@@ -664,6 +665,13 @@ class MediaWikiServices extends ServiceContainer {
 
        /**
         * @since 1.32
+        * @return NameTableStoreFactory
+        */
+       public function getNameTableStoreFactory() {
+               return $this->getService( 'NameTableStoreFactory' );
+       }
+
+       /**
         * @return OldRevisionImporter
         */
        public function getOldRevisionImporter() {
@@ -836,7 +844,7 @@ class MediaWikiServices extends ServiceContainer {
         * @return NameTableStore
         */
        public function getSlotRoleStore() {
-               return $this->getService( 'SlotRoleStore' );
+               return $this->getService( 'NameTableStoreFactory' )->getSlotRoles();
        }
 
        /**
index 59cdec9..b8bd5d2 100644 (file)
@@ -53,7 +53,7 @@ use MediaWiki\Special\SpecialPageFactory;
 use MediaWiki\Storage\BlobStore;
 use MediaWiki\Revision\RevisionRenderer;
 use MediaWiki\Storage\BlobStoreFactory;
-use MediaWiki\Storage\NameTableStore;
+use MediaWiki\Storage\NameTableStoreFactory;
 use MediaWiki\Storage\RevisionFactory;
 use MediaWiki\Storage\RevisionLookup;
 use MediaWiki\Storage\RevisionStore;
@@ -80,24 +80,6 @@ return [
                );
        },
 
-       'ChangeTagDefStore' => function ( MediaWikiServices $services ) : NameTableStore {
-               return new NameTableStore(
-                       $services->getDBLoadBalancer(),
-                       $services->getMainWANObjectCache(),
-                       LoggerFactory::getInstance( 'NameTableSqlStore' ),
-                       'change_tag_def',
-                       'ctd_id',
-                       'ctd_name',
-                       null,
-                       false,
-                       function ( $insertFields ) {
-                               $insertFields['ctd_user_defined'] = 0;
-                               $insertFields['ctd_count'] = 0;
-                               return $insertFields;
-                       }
-               );
-       },
-
        'CommentStore' => function ( MediaWikiServices $services ) : CommentStore {
                return new CommentStore(
                        $services->getContentLanguage(),
@@ -128,23 +110,6 @@ return [
                return Language::factory( $services->getMainConfig()->get( 'LanguageCode' ) );
        },
 
-       'ContentModelStore' => function ( MediaWikiServices $services ) : NameTableStore {
-               return new NameTableStore(
-                       $services->getDBLoadBalancer(),
-                       $services->getMainWANObjectCache(),
-                       LoggerFactory::getInstance( 'NameTableSqlStore' ),
-                       'content_models',
-                       'model_id',
-                       'model_name'
-                       /**
-                        * No strtolower normalization is added to the service as there are examples of
-                        * extensions that do not stick to this assumption.
-                        * - extensions/examples/DataPages define( 'CONTENT_MODEL_XML_DATA','XML_DATA' );
-                        * - extensions/Scribunto define( 'CONTENT_MODEL_SCRIBUNTO', 'Scribunto' );
-                        */
-               );
-       },
-
        'CryptHKDF' => function ( MediaWikiServices $services ) : CryptHKDF {
                $config = $services->getMainConfig();
 
@@ -360,6 +325,14 @@ return [
                return new MimeMagic( $params );
        },
 
+       'NameTableStoreFactory' => function ( MediaWikiServices $services ) : NameTableStoreFactory {
+               return new NameTableStoreFactory(
+                       $services->getDBLoadBalancerFactory(),
+                       $services->getMainWANObjectCache(),
+                       LoggerFactory::getInstance( 'NameTableSqlStore' )
+               );
+       },
+
        'OldRevisionImporter' => function ( MediaWikiServices $services ) : OldRevisionImporter {
                return new ImportableOldRevisionImporter(
                        true,
@@ -459,6 +432,7 @@ return [
                $store = new RevisionStoreFactory(
                        $services->getDBLoadBalancerFactory(),
                        $services->getBlobStoreFactory(),
+                       $services->getNameTableStoreFactory(),
                        $services->getMainWANObjectCache(),
                        $services->getCommentStore(),
                        $services->getActorMigration(),
@@ -542,18 +516,6 @@ return [
                return $factory;
        },
 
-       'SlotRoleStore' => function ( MediaWikiServices $services ) : NameTableStore {
-               return new NameTableStore(
-                       $services->getDBLoadBalancer(),
-                       $services->getMainWANObjectCache(),
-                       LoggerFactory::getInstance( 'NameTableSqlStore' ),
-                       'slot_roles',
-                       'role_id',
-                       'role_name',
-                       'strtolower'
-               );
-       },
-
        'SpecialPageFactory' => function ( MediaWikiServices $services ) : SpecialPageFactory {
                return new SpecialPageFactory(
                        $services->getMainConfig(),
diff --git a/includes/Storage/NameTableStoreFactory.php b/includes/Storage/NameTableStoreFactory.php
new file mode 100644 (file)
index 0000000..02ea9a7
--- /dev/null
@@ -0,0 +1,149 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ * @file
+ */
+
+namespace MediaWiki\Storage;
+
+use Wikimedia\Rdbms\ILBFactory;
+use WANObjectCache;
+use Psr\Log\LoggerInterface;
+
+class NameTableStoreFactory {
+       private static $info;
+       private $stores = [];
+
+       /** @var ILBFactory */
+       private $lbFactory;
+
+       /** @var WANObjectCache */
+       private $cache;
+
+       /** @var LoggerInterface */
+       private $logger;
+
+       private static function getTableInfo() {
+               if ( self::$info ) {
+                       return self::$info;
+               }
+               self::$info = [
+                       'change_tag_def' => [
+                               'idField' => 'ctd_id',
+                               'nameField' => 'ctd_name',
+                               'normalizationCallback' => null,
+                               'insertCallback' => function ( $insertFields ) {
+                                       $insertFields['ctd_user_defined'] = 0;
+                                       $insertFields['ctd_count'] = 0;
+                                       return $insertFields;
+                               }
+                       ],
+
+                       'content_models' => [
+                               'idField' => 'model_id',
+                               'nameField' => 'model_name',
+                               /**
+                                * No strtolower normalization is added to the service as there are examples of
+                                * extensions that do not stick to this assumption.
+                                * - extensions/examples/DataPages define( 'CONTENT_MODEL_XML_DATA','XML_DATA' );
+                                * - extensions/Scribunto define( 'CONTENT_MODEL_SCRIBUNTO', 'Scribunto' );
+                                */
+                       ],
+
+                       'slot_roles' => [
+                               'idField' => 'role_id',
+                               'nameField' => 'role_name',
+                               'normalizationCallback' => 'strtolower',
+                       ],
+               ];
+               return self::$info;
+       }
+
+       public function __construct(
+               ILBFactory $lbFactory,
+               WANObjectCache $cache,
+               LoggerInterface $logger
+       ) {
+               $this->lbFactory = $lbFactory;
+               $this->cache = $cache;
+               $this->logger = $logger;
+       }
+
+       /**
+        * Get a NameTableStore for a specific table
+        *
+        * @param string $tableName The table name
+        * @param string|false $wiki The target wiki ID, or false for the current wiki
+        * @return NameTableStore
+        */
+       public function get( $tableName, $wiki = false ) : NameTableStore {
+               $infos = self::getTableInfo();
+               if ( !isset( $infos[$tableName] ) ) {
+                       throw new \InvalidArgumentException( "Invalid table name \$tableName" );
+               }
+               if ( $wiki === wfWikiID() ) {
+                       $wiki = false;
+               }
+               if ( isset( $this->stores[$tableName][$wiki] ) ) {
+                       return $this->stores[$tableName][$wiki];
+               }
+
+               $info = $infos[$tableName];
+               $store = new NameTableStore(
+                       $this->lbFactory->getMainLB( $wiki ),
+                       $this->cache,
+                       $this->logger,
+                       $tableName,
+                       $info['idField'],
+                       $info['nameField'],
+                       $info['normalizationCallback'] ?? null,
+                       $wiki,
+                       $info['insertCallback'] ?? null
+               );
+               $this->stores[$tableName][$wiki] = $store;
+               return $store;
+       }
+
+       /**
+        * Get a NameTableStore for the change_tag_def table
+        *
+        * @param string|bool $wiki
+        * @return NameTableStore
+        */
+       public function getChangeTagDef( $wiki = false ) : NameTableStore {
+               return $this->get( 'change_tag_def', $wiki );
+       }
+
+       /**
+        * Get a NameTableStore for the content_models table
+        *
+        * @param string|bool $wiki
+        * @return NameTableStore
+        */
+       public function getContentModels( $wiki = false ) : NameTableStore {
+               return $this->get( 'content_models', $wiki );
+       }
+
+       /**
+        * Get a NameTableStore for the slot_roles table
+        *
+        * @param string|bool $wiki
+        * @return NameTableStore
+        */
+       public function getSlotRoles( $wiki = false ) : NameTableStore {
+               return $this->get( 'slot_roles', $wiki );
+       }
+}
index 9419b40..aaaafc1 100644 (file)
@@ -67,9 +67,13 @@ class RevisionStoreFactory {
         */
        private $contentHandlerUseDB;
 
+       /** @var NameTableStoreFactory */
+       private $nameTables;
+
        /**
         * @param ILBFactory $dbLoadBalancerFactory
         * @param BlobStoreFactory $blobStoreFactory
+        * @param NameTableStoreFactory $nameTables
         * @param WANObjectCache $cache
         * @param CommentStore $commentStore
         * @param ActorMigration $actorMigration
@@ -81,6 +85,7 @@ class RevisionStoreFactory {
        public function __construct(
                ILBFactory $dbLoadBalancerFactory,
                BlobStoreFactory $blobStoreFactory,
+               NameTableStoreFactory $nameTables,
                WANObjectCache $cache,
                CommentStore $commentStore,
                ActorMigration $actorMigration,
@@ -91,6 +96,7 @@ class RevisionStoreFactory {
                Assert::parameterType( 'integer', $migrationStage, '$migrationStage' );
                $this->dbLoadBalancerFactory = $dbLoadBalancerFactory;
                $this->blobStoreFactory = $blobStoreFactory;
+               $this->nameTables = $nameTables;
                $this->cache = $cache;
                $this->commentStore = $commentStore;
                $this->actorMigration = $actorMigration;
@@ -98,7 +104,6 @@ class RevisionStoreFactory {
                $this->loggerProvider = $loggerProvider;
                $this->contentHandlerUseDB = $contentHandlerUseDB;
        }
-       /**
 
        /**
         * @since 1.32
@@ -115,8 +120,8 @@ class RevisionStoreFactory {
                        $this->blobStoreFactory->newSqlBlobStore( $wikiId ),
                        $this->cache, // Pass local cache instance; Leave cache sharing to RevisionStore.
                        $this->commentStore,
-                       $this->getContentModelStore( $wikiId ),
-                       $this->getSlotRoleStore( $wikiId ),
+                       $this->nameTables->getContentModels( $wikiId ),
+                       $this->nameTables->getSlotRoles( $wikiId ),
                        $this->mcrMigrationStage,
                        $this->actorMigration,
                        $wikiId
@@ -127,43 +132,4 @@ class RevisionStoreFactory {
 
                return $store;
        }
-
-       /**
-        * @param string $wikiId
-        * @return NameTableStore
-        */
-       private function getContentModelStore( $wikiId ) {
-               // XXX: a dedicated ContentModelStore subclass would avoid hard-coding
-               // knowledge about the schema here.
-               return new NameTableStore(
-                       $this->dbLoadBalancerFactory->getMainLB( $wikiId ),
-                       $this->cache, // Pass local cache instance; Leave cache sharing to NameTableStore.
-                       $this->loggerProvider->getLogger( 'NameTableSqlStore' ),
-                       'content_models',
-                       'model_id',
-                       'model_name',
-                       null,
-                       $wikiId
-               );
-       }
-
-       /**
-        * @param string $wikiId
-        * @return NameTableStore
-        */
-       private function getSlotRoleStore( $wikiId ) {
-               // XXX: a dedicated ContentModelStore subclass would avoid hard-coding
-               // knowledge about the schema here.
-               return new NameTableStore(
-                       $this->dbLoadBalancerFactory->getMainLB( $wikiId ),
-                       $this->cache, // Pass local cache instance; Leave cache sharing to NameTableStore.
-                       $this->loggerProvider->getLogger( 'NameTableSqlStore' ),
-                       'slot_roles',
-                       'role_id',
-                       'role_name',
-                       'strtolower',
-                       $wikiId
-               );
-       }
-
 }
index e17d163..82ccce2 100644 (file)
@@ -34,6 +34,8 @@ require_once __DIR__ . '/../../maintenance/Maintenance.php';
  * @since 1.17
  */
 abstract class DatabaseUpdater {
+       const REPLICATION_WAIT_TIMEOUT = 300;
+
        /**
         * Array of updates to perform on the database
         *
@@ -484,7 +486,7 @@ abstract class DatabaseUpdater {
                        flush();
                        if ( $ret !== false ) {
                                $updatesDone[] = $origParams;
-                               $lbFactory->waitForReplication();
+                               $lbFactory->waitForReplication( [ 'timeout' => self::REPLICATION_WAIT_TIMEOUT ] );
                        } else {
                                $updatesSkipped[] = [ $func, $params, $origParams ];
                        }
index 9469cf2..c33103c 100644 (file)
@@ -924,7 +924,8 @@ class MysqlUpdater extends DatabaseUpdater {
                                $count = ( $count + 1 ) % 100;
                                if ( $count == 0 ) {
                                        $lbFactory = $services->getDBLoadBalancerFactory();
-                                       $lbFactory->waitForReplication( [ 'wiki' => wfWikiID() ] );
+                                       $lbFactory->waitForReplication( [
+                                               'wiki' => wfWikiID(), 'timeout' => self::REPLICATION_WAIT_TIMEOUT ] );
                                }
                                $this->db->insert( 'templatelinks',
                                        [
index 7c5d0ae..dab9b14 100644 (file)
@@ -29,7 +29,6 @@ use Psr\Log\LoggerInterface;
 use Wikimedia\ScopedCallback;
 use Wikimedia\Rdbms\LBFactory;
 use Wikimedia\Rdbms\DBError;
-use Wikimedia\Rdbms\DBReplicationWaitError;
 
 /**
  * Job queue runner utility methods
@@ -225,21 +224,16 @@ class JobRunner implements LoggerAwareInterface {
                                // other wikis in the farm (on different masters) get a chance.
                                $timePassed = microtime( true ) - $lastCheckTime;
                                if ( $timePassed >= self::LAG_CHECK_PERIOD || $timePassed < 0 ) {
-                                       try {
-                                               $lbFactory->waitForReplication( [
-                                                       'ifWritesSince' => $lastCheckTime,
-                                                       'timeout' => self::MAX_ALLOWED_LAG
-                                               ] );
-                                       } catch ( DBReplicationWaitError $e ) {
+                                       $success = $lbFactory->waitForReplication( [
+                                               'ifWritesSince' => $lastCheckTime,
+                                               'timeout' => self::MAX_ALLOWED_LAG,
+                                       ] );
+                                       if ( !$success ) {
                                                $response['reached'] = 'replica-lag-limit';
                                                break;
                                        }
                                        $lastCheckTime = microtime( true );
                                }
-                               // Don't let any queue replica DBs/backups fall behind
-                               if ( $jobsPopped > 0 && ( $jobsPopped % 100 ) == 0 ) {
-                                       $group->waitForBackups();
-                               }
 
                                // Bail if near-OOM instead of in a job
                                if ( !$this->checkMemoryOK() ) {
index 8f50828..223ae32 100644 (file)
@@ -19,7 +19,6 @@
  * @ingroup JobQueue
  */
 use MediaWiki\MediaWikiServices;
-use Wikimedia\Rdbms\DBReplicationWaitError;
 
 /**
  * Job for pruning recent changes
@@ -105,11 +104,9 @@ class RecentChangesUpdateJob extends Job {
                                $dbw->delete( 'recentchanges', [ 'rc_id' => $rcIds ], __METHOD__ );
                                Hooks::run( 'RecentChangesPurgeRows', [ $rows ] );
                                // There might be more, so try waiting for replica DBs
-                               try {
-                                       $factory->commitAndWaitForReplication(
-                                               __METHOD__, $ticket, [ 'timeout' => 3 ]
-                                       );
-                               } catch ( DBReplicationWaitError $e ) {
+                               if ( !$factory->commitAndWaitForReplication(
+                                       __METHOD__, $ticket, [ 'timeout' => 3 ]
+                               ) ) {
                                        // Another job will continue anyway
                                        break;
                                }
index aa8e121..5c92874 100644 (file)
@@ -21,7 +21,6 @@
  * @ingroup JobQueue
  */
 use MediaWiki\MediaWikiServices;
-use Wikimedia\Rdbms\DBReplicationWaitError;
 
 /**
  * Job to update link tables for pages
@@ -89,13 +88,11 @@ class RefreshLinksJob extends Job {
                        // From then on, we know that any template changes at the time the base job was
                        // enqueued will be reflected in backlink page parses when the leaf jobs run.
                        if ( !isset( $this->params['range'] ) ) {
-                               try {
-                                       $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
-                                       $lbFactory->waitForReplication( [
+                               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+                               if ( !$lbFactory->waitForReplication( [
                                                'wiki'    => wfWikiID(),
                                                'timeout' => self::LAG_WAIT_TIMEOUT
-                                       ] );
-                               } catch ( DBReplicationWaitError $e ) { // only try so hard
+                               ] ) ) { // only try so hard
                                        $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
                                        $stats->increment( 'refreshlinks.lag_wait_failed' );
                                }
index c5dd8ae..74abace 100644 (file)
@@ -23,6 +23,7 @@ namespace Wikimedia\Rdbms;
 
 /**
  * Exception class for replica DB wait timeouts
+ * @deprecated since 1.32
  * @ingroup Database
  */
 class DBReplicationWaitError extends DBExpectedError {
index 23699c7..8e35456 100644 (file)
@@ -269,9 +269,9 @@ interface ILBFactory {
         * @param array $opts Optional fields that include:
         *   - domain : wait on the load balancer DBs that handles the given domain ID
         *   - cluster : wait on the given external load balancer DBs
-        *   - timeout : Max wait time. Default: ~60 seconds
+        *   - timeout : Max wait time. Default: 60 seconds for CLI, 1 second for web.
         *   - ifWritesSince: Only wait if writes were done since this UNIX timestamp
-        * @throws DBReplicationWaitError If a timeout or error occurred waiting on a DB cluster
+        * @return bool True on success, false if a timeout or error occurred while waiting
         */
        public function waitForReplication( array $opts = [] );
 
@@ -301,7 +301,7 @@ interface ILBFactory {
         * @param string $fname Caller name (e.g. __METHOD__)
         * @param mixed $ticket Result of getEmptyTransactionTicket()
         * @param array $opts Options to waitForReplication()
-        * @throws DBReplicationWaitError
+        * @return bool True if the wait was successful, false on timeout
         */
        public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] );
 
index be1a851..e736ab9 100644 (file)
@@ -375,7 +375,7 @@ abstract class LBFactory implements ILBFactory {
                $opts += [
                        'domain' => false,
                        'cluster' => false,
-                       'timeout' => $this->cliMode ? 60 : 10,
+                       'timeout' => $this->cliMode ? 60 : 1,
                        'ifWritesSince' => null
                ];
 
@@ -432,13 +432,7 @@ abstract class LBFactory implements ILBFactory {
                        }
                }
 
-               if ( $failed ) {
-                       throw new DBReplicationWaitError(
-                               null,
-                               "Could not wait for replica DBs to catch up to " .
-                               implode( ', ', $failed )
-                       );
-               }
+               return !$failed;
        }
 
        public function setWaitForReplicationListener( $name, callable $callback = null ) {
@@ -478,12 +472,13 @@ abstract class LBFactory implements ILBFactory {
                }
 
                $this->commitMasterChanges( $fnameEffective );
-               $this->waitForReplication( $opts );
+               $waitSucceeded = $this->waitForReplication( $opts );
                // If a nested caller committed on behalf of $fname, start another empty $fname
                // transaction, leaving the caller with the same empty transaction state as before.
                if ( $fnameEffective !== $fname ) {
                        $this->beginMasterChanges( $fnameEffective );
                }
+               return $waitSucceeded;
        }
 
        public function getChronologyProtectorTouched( $dbName ) {
index 8429afa..1f8a27e 100644 (file)
@@ -408,11 +408,7 @@ class ExtensionRegistry {
         * @return array
         */
        public function getAttribute( $name ) {
-               if ( isset( $this->attributes[$name] ) ) {
-                       return $this->attributes[$name];
-               } else {
-                       return [];
-               }
+               return $this->attributes[$name] ?? [];
        }
 
        /**
index fc0ca1d..8f5d083 100644 (file)
@@ -627,14 +627,13 @@ class ResourceLoader implements LoggerAwareInterface {
        /**
         * Add an error to the 'errors' array and log it.
         *
-        * Should only be called from within respond().
-        *
+        * @private For internal use by ResourceLoader and ResourceLoaderStartUpModule.
         * @since 1.29
         * @param Exception $e
         * @param string $msg
         * @param array $context
         */
-       protected function outputErrorAndLog( Exception $e, $msg, array $context = [] ) {
+       public function outputErrorAndLog( Exception $e, $msg, array $context = [] ) {
                MWExceptionHandler::logException( $e );
                $this->logger->warning(
                        $msg,
@@ -659,9 +658,8 @@ class ResourceLoader implements LoggerAwareInterface {
                        try {
                                return $this->getModule( $module )->getVersionHash( $context );
                        } catch ( Exception $e ) {
-                               // If modules fail to compute a version, do still consider the versions
-                               // of other modules - don't set an empty string E-Tag for the whole request.
-                               // See also T152266 and StartupModule::getModuleRegistrations().
+                               // If modules fail to compute a version, don't fail the request (T152266)
+                               // and still compute versions of other modules.
                                $this->outputErrorAndLog( $e,
                                        'Calculating version for "{module}" failed: {exception}',
                                        [
index 18cc4d5..8140c2c 100644 (file)
@@ -40,21 +40,13 @@ use MediaWiki\MediaWikiServices;
  *   See also: OutputPage::disallowUserJs()
  */
 class ResourceLoaderStartUpModule extends ResourceLoaderModule {
-
-       // Cache for getConfigSettings() as it's called by multiple methods
-       protected $configVars = [];
        protected $targets = [ 'desktop', 'mobile' ];
 
        /**
         * @param ResourceLoaderContext $context
         * @return array
         */
-       protected function getConfigSettings( $context ) {
-               $hash = $context->getHash();
-               if ( isset( $this->configVars[$hash] ) ) {
-                       return $this->configVars[$hash];
-               }
-
+       private function getConfigSettings( $context ) {
                $conf = $this->getConfig();
 
                // We can't use Title::newMainPage() if 'mainpage' is in
@@ -136,8 +128,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
 
                Hooks::run( 'ResourceLoaderGetConfigVars', [ &$vars ] );
 
-               $this->configVars[$hash] = $vars;
-               return $this->configVars[$hash];
+               return $vars;
        }
 
        /**
@@ -222,9 +213,23 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                $out = '';
                $states = [];
                $registryData = [];
+               $moduleNames = $resourceLoader->getModuleNames();
+
+               // Preload with a batch so that the below calls to getVersionHash() for each module
+               // don't require on-demand loading of more information.
+               try {
+                       $resourceLoader->preloadModuleInfo( $moduleNames, $context );
+               } catch ( Exception $e ) {
+                       // Don't fail the request (T152266)
+                       // Also print the error in the main output
+                       $resourceLoader->outputErrorAndLog( $e,
+                               'Preloading module info from startup failed: {exception}',
+                               [ 'exception' => $e ]
+                       );
+               }
 
                // Get registry data
-               foreach ( $resourceLoader->getModuleNames() as $name ) {
+               foreach ( $moduleNames as $name ) {
                        $module = $resourceLoader->getModule( $name );
                        $moduleTargets = $module->getTargets();
                        if (
@@ -235,18 +240,25 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                        }
 
                        if ( $module->isRaw() ) {
-                               // Don't register "raw" modules (like 'jquery' and 'mediawiki') client-side because
-                               // depending on them is illegal anyway and would only lead to them being reloaded
-                               // causing any state to be lost (like jQuery plugins, mw.config etc.)
+                               // Don't register "raw" modules (like 'startup') client-side because depending on them
+                               // is illegal anyway and would only lead to them being loaded a second time,
+                               // causing any state to be lost.
+
+                               // ATTENTION: Because of the line below, this is not going to cause infinite recursion.
+                               // Think carefully before making changes to this code!
+                               // The below code is going to call ResourceLoaderModule::getVersionHash() for every module.
+                               // For StartUpModule (this module) the hash is computed based on the manifest content,
+                               // which is the very thing we are computing right here. As such, this must skip iterating
+                               // over 'startup' itself.
                                continue;
                        }
 
                        try {
                                $versionHash = $module->getVersionHash( $context );
                        } catch ( Exception $e ) {
-                               // See also T152266 and ResourceLoader::getCombinedVersion()
-                               MWExceptionHandler::logException( $e );
-                               $context->getLogger()->warning(
+                               // Don't fail the request (T152266)
+                               // Also print the error in the main output
+                               $resourceLoader->outputErrorAndLog( $e,
                                        'Calculating version for "{module}" failed: {exception}',
                                        [
                                                'module' => $name,
@@ -445,59 +457,12 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
        }
 
        /**
-        * Get the definition summary for this module.
-        *
-        * @param ResourceLoaderContext $context
-        * @return array
-        */
-       public function getDefinitionSummary( ResourceLoaderContext $context ) {
-               global $IP;
-               $summary = parent::getDefinitionSummary( $context );
-               $startup = [
-                       // getScript() exposes these variables to mw.config (T30899).
-                       'vars' => $this->getConfigSettings( $context ),
-                       // getScript() uses this to decide how configure mw.Map for mw.config.
-                       'wgLegacyJavaScriptGlobals' => $this->getConfig()->get( 'LegacyJavaScriptGlobals' ),
-                       // Detect changes to the module registrations output by getScript().
-                       'moduleHashes' => $this->getAllModuleHashes( $context ),
-                       // Detect changes to base modules listed by getScript().
-                       'baseModules' => $this->getBaseModules(),
-
-                       'fileHashes' => [
-                               $this->safeFileHash( "$IP/resources/src/startup/startup.js" ),
-                               $this->safeFileHash( "$IP/resources/src/startup/mediawiki.js" ),
-                               $this->safeFileHash( "$IP/resources/src/startup/mediawiki.requestIdleCallback.js" ),
-                       ],
-               ];
-               if ( $context->getDebug() ) {
-                       $startup['fileHashes'][] = $this->safeFileHash( "$IP/resources/src/startup/mediawiki.log.js" );
-               }
-               if ( $this->getConfig()->get( 'ResourceLoaderEnableJSProfiler' ) ) {
-                       $startup['fileHashes'][] = $this->safeFileHash( "$IP/resources/src/startup/profiling.js" );
-               }
-               $summary[] = $startup;
-               return $summary;
-       }
-
-       /**
-        * Helper method for getDefinitionSummary().
-        *
-        * @param ResourceLoaderContext $context
-        * @return string SHA-1
+        * @return bool
         */
-       protected function getAllModuleHashes( ResourceLoaderContext $context ) {
-               $rl = $context->getResourceLoader();
-               // Preload for getCombinedVersion()
-               $rl->preloadModuleInfo( $rl->getModuleNames(), $context );
-
-               // ATTENTION: Because of the line below, this is not going to cause infinite recursion.
-               // Think carefully before making changes to this code!
-               // Pre-populate versionHash with something because the loop over all modules below includes
-               // the startup module (this module).
-               // See ResourceLoaderModule::getVersionHash() for usage of this cache.
-               $this->versionHash[$context->getHash()] = null;
-
-               return $rl->getCombinedVersion( $context, $rl->getModuleNames() );
+       public function enableModuleContentVersion() {
+               // Enabling this means that ResourceLoader::getVersionHash will simply call getScript()
+               // and hash it to determine the version (as used by E-Tag HTTP response header).
+               return true;
        }
 
        /**
index 30f8295..d934d27 100644 (file)
@@ -100,6 +100,12 @@ class ServiceContainer implements DestructibleService {
                        }
                }
 
+               // Break circular references due to the $this reference in closures, by
+               // erasing the instantiator array. This allows the ServiceContainer to
+               // be deleted when it goes out of scope.
+               $this->serviceInstantiators = [];
+               // Also remove the services themselves, to avoid confusion.
+               $this->services = [];
                $this->destroyed = true;
        }
 
index 6377734..b446cc1 100644 (file)
@@ -26,7 +26,6 @@ require_once __DIR__ . '/../includes/PHPVersionCheck.php';
 wfEntryPointCheck( 'cli' );
 
 use MediaWiki\Shell\Shell;
-use Wikimedia\Rdbms\DBReplicationWaitError;
 
 /**
  * @defgroup MaintenanceArchive Maintenance archives
@@ -1393,17 +1392,12 @@ abstract class Maintenance {
         */
        protected function commitTransaction( IDatabase $dbw, $fname ) {
                $dbw->commit( $fname );
-               try {
-                       $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
-                       $lbFactory->waitForReplication(
-                               [ 'timeout' => 30, 'ifWritesSince' => $this->lastReplicationWait ]
-                       );
-                       $this->lastReplicationWait = microtime( true );
-
-                       return true;
-               } catch ( DBReplicationWaitError $e ) {
-                       return false;
-               }
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+               $waitSucceeded = $lbFactory->waitForReplication(
+                       [ 'timeout' => 30, 'ifWritesSince' => $this->lastReplicationWait ]
+               );
+               $this->lastReplicationWait = microtime( true );
+               return $waitSucceeded;
        }
 
        /**
index 12a454a..3b28b65 100644 (file)
@@ -25,7 +25,6 @@
 require_once __DIR__ . '/Maintenance.php';
 
 use MediaWiki\MediaWikiServices;
-use Wikimedia\Rdbms\DBReplicationWaitError;
 
 /**
  * Maintenance script to update cached special pages.
@@ -134,11 +133,7 @@ class UpdateSpecialPages extends Maintenance {
                        $this->output( "Reconnected\n\n" );
                }
                // Wait for the replica DB to catch up
-               try {
-                       $lbFactory->waitForReplication();
-               } catch ( DBReplicationWaitError $e ) {
-                       // ignore
-               }
+               $lbFactory->waitForReplication();
        }
 
        public function doSpecialPageCacheUpdates( $dbw ) {
index 284b21a..16994f5 100644 (file)
@@ -14,7 +14,6 @@
  *   "mediawiki" module, and will remain a default/implicit dependency for all
  *   regular modules, just like jquery and wikibits already are.
  */
-/* globals mw */
 ( function () {
        'use strict';
 
index e86aff6..d1fbff0 100644 (file)
@@ -4,7 +4,7 @@
  * @class mw.errorLogger
  * @singleton
  */
-( function ( mw ) {
+( function () {
        'use strict';
 
        mw.errorLogger = {
@@ -55,4 +55,4 @@
        };
 
        mw.errorLogger.installGlobalHandler( window );
-}( mediaWiki ) );
+}() );
index 2f196de..3082603 100644 (file)
@@ -1,7 +1,7 @@
 /**
  * Base library for MediaWiki.
  *
- * Exposed globally as `mediaWiki` with `mw` as shortcut.
+ * Exposed globally as `mw`, with `mediaWiki` as alias.
  *
  * @class mw
  * @alternateClassName mediaWiki
                                                mw.loader.register( name );
                                        }
                                        // Check for duplicate implementation
-                                       if ( hasOwn.call( registry, name ) && registry[ name ].script !== undefined ) {
+                                       if ( registry[ name ].script !== undefined ) {
                                                throw new Error( 'module already implemented: ' + name );
                                        }
                                        if ( version ) {
index a3f3c68..af59c7f 100644 (file)
@@ -6,7 +6,7 @@
  * @author Michael Dale <mdale@wikimedia.org>
  * @author Trevor Parscal <tparscal@wikimedia.org>
  */
-( function ( mw ) {
+( function () {
        /* global console */
        /* eslint-disable no-console */
        var original = mw.log;
@@ -22,4 +22,4 @@
                mw.log.error = original.error;
                mw.log.deprecate = original.deprecate;
        }
-}( mediaWiki ) );
+}() );
index 650092b..9f8c598 100644 (file)
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function () {
        var maxBusy = 50;
 
        mw.requestIdleCallbackInternal = function ( callback ) {
@@ -49,4 +49,4 @@
        // Note: Polyfill was previously disabled due to
        // https://bugs.chromium.org/p/chromium/issues/detail?id=647870
        // See also <http://codepen.io/Krinkle/full/XNGEvv>
-}( mediaWiki ) );
+}() );
index 5e9b6ab..588750c 100644 (file)
@@ -4,7 +4,6 @@
  * @author Timo Tijhof
  * @since 1.32
  */
-/* global mw */
 ( function () {
        'use strict';
 
index 03141b9..8c79c94 100644 (file)
@@ -4,7 +4,7 @@
  * - Beware: This file MUST parse without errors on even the most ancient of browsers!
  */
 /* eslint-disable vars-on-top, no-unmodified-loop-condition */
-/* global mw, isCompatible, $VARS, $CODE */
+/* global isCompatible, $VARS, $CODE */
 
 /**
  * See <https://www.mediawiki.org/wiki/Compatibility#Browsers>
index c176a67..2feb438 100644 (file)
@@ -104,10 +104,6 @@ class TestSetup {
                // may break testing against floating point values
                // treated with PHP's serialize()
                ini_set( 'serialize_precision', 17 );
-
-               // TODO: we should call MediaWikiTestCase::prepareServices( new GlobalVarConfig() ) here.
-               // But PHPUnit may not be loaded yet, so we have to wait until just
-               // before PHPUnit_TextUI_Command::main() is executed.
        }
 
 }
index 5cc45f5..78636b1 100644 (file)
@@ -8,7 +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;
 
@@ -21,13 +20,16 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
        use PHPUnit4And6Compat;
 
        /**
-        * The service locator created by prepareServices(). This service locator will
-        * be restored after each test. Tests that pollute the global service locator
-        * instance should use overrideMwServices() to isolate the test.
+        * The original service locator. This is overridden during setUp().
         *
         * @var MediaWikiServices|null
         */
-       private static $serviceLocator = null;
+       private static $originalServices;
+
+       /**
+        * The local service locator, created during setUp().
+        */
+       private $localServices;
 
        /**
         * $called tracks whether the setUp and tearDown method has been called.
@@ -98,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[]
@@ -155,8 +151,10 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
        public static function setUpBeforeClass() {
                parent::setUpBeforeClass();
 
-               // Get the service locator, and reset services if it's not done already
-               self::$serviceLocator = self::prepareServices( new GlobalVarConfig() );
+               // Get the original service locator
+               if ( !self::$originalServices ) {
+                       self::$originalServices = MediaWikiServices::getInstance();
+               }
        }
 
        /**
@@ -245,63 +243,9 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
        }
 
        /**
-        * Prepare service configuration for unit testing.
-        *
-        * This calls MediaWikiServices::resetGlobalInstance() to allow some critical services
-        * to be overridden for testing.
-        *
-        * prepareServices() only needs to be called once, but should be called as early as possible,
-        * before any class has a chance to grab a reference to any of the global services
-        * instances that get discarded by prepareServices(). Only the first call has any effect,
-        * later calls are ignored.
-        *
-        * @note This is called by PHPUnitMaintClass::finalSetup.
-        *
-        * @see MediaWikiServices::resetGlobalInstance()
-        *
-        * @param Config $bootstrapConfig The bootstrap config to use with the new
-        *        MediaWikiServices. Only used for the first call to this method.
-        * @return MediaWikiServices
+        * @deprecated since 1.32
         */
        public static function prepareServices( Config $bootstrapConfig ) {
-               static $services = null;
-
-               if ( !$services ) {
-                       $services = self::resetGlobalServices( $bootstrapConfig );
-               }
-               return $services;
-       }
-
-       /**
-        * Reset global services, and install testing environment.
-        * This is the testing equivalent of MediaWikiServices::resetGlobalInstance().
-        * This should only be used to set up the testing environment, not when
-        * running unit tests. Use MediaWikiTestCase::overrideMwServices() for that.
-        *
-        * @see MediaWikiServices::resetGlobalInstance()
-        * @see prepareServices()
-        * @see MediaWikiTestCase::overrideMwServices()
-        *
-        * @param Config|null $bootstrapConfig The bootstrap config to use with the new
-        *        MediaWikiServices.
-        * @return MediaWikiServices
-        */
-       private static function resetGlobalServices( Config $bootstrapConfig = null ) {
-               $oldServices = MediaWikiServices::getInstance();
-               $oldConfigFactory = $oldServices->getConfigFactory();
-               $oldLoadBalancerFactory = $oldServices->getDBLoadBalancerFactory();
-
-               $testConfig = self::makeTestConfig( $bootstrapConfig );
-
-               MediaWikiServices::resetGlobalInstance( $testConfig );
-
-               $serviceLocator = MediaWikiServices::getInstance();
-               self::installTestServices(
-                       $oldConfigFactory,
-                       $oldLoadBalancerFactory,
-                       $serviceLocator
-               );
-               return $serviceLocator;
        }
 
        /**
@@ -320,7 +264,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                $defaultOverrides = new HashConfig();
 
                if ( !$baseConfig ) {
-                       $baseConfig = MediaWikiServices::getInstance()->getBootstrapConfig();
+                       $baseConfig = self::$originalServices->getBootstrapConfig();
                }
 
                /* Some functions require some kind of caching, and will end up using the db,
@@ -415,17 +359,10 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
        }
 
        /**
-        * Resets some well known services that typically have state that may interfere with unit tests.
-        * This is a lightweight alternative to resetGlobalServices().
-        *
-        * @note There is no guarantee that no references remain to stale service instances destroyed
-        * by a call to doLightweightServiceReset().
-        *
-        * @throws MWException if called outside of PHPUnit tests.
-        *
-        * @see resetGlobalServices()
+        * Resets some non-service singleton instances and other static caches. It's not necessary to
+        * reset services here.
         */
-       private function doLightweightServiceReset() {
+       private function resetNonServiceCaches() {
                global $wgRequest, $wgJobClasses;
 
                foreach ( $wgJobClasses as $type => $class ) {
@@ -434,10 +371,6 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                JobQueueGroup::destroySingletons();
 
                ObjectCache::clear();
-               $services = MediaWikiServices::getInstance();
-               $services->resetServiceForTesting( 'MainObjectStash' );
-               $services->resetServiceForTesting( 'LocalServerObjectCache' );
-               $services->getMainWANObjectCache()->clearProcessCache();
                FileBackendGroup::destroySingleton();
                DeferredUpdates::clearPendingUpdates();
 
@@ -453,6 +386,8 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
        }
 
        public function run( PHPUnit_Framework_TestResult $result = null ) {
+               $this->overrideMwServices();
+
                $needsResetDB = false;
 
                if ( !self::$dbSetup || $this->needsDB() ) {
@@ -492,6 +427,10 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                        $this->testLogger->info( "Resetting DB" );
                        $this->resetDB( $this->db, $this->tablesUsed );
                }
+
+               $this->localServices->destroy();
+               $this->localServices = null;
+               MediaWikiServices::forceGlobalInstance( self::$originalServices );
        }
 
        /**
@@ -584,20 +523,13 @@ 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->doLightweightServiceReset();
+               $this->resetNonServiceCaches();
 
                // XXX: reset maintenance triggers
                // Hook into period lag checks which often happen in long-running scripts
-               $services = MediaWikiServices::getInstance();
-               $lbFactory = $services->getDBLoadBalancerFactory();
-               Maintenance::setLBFactoryTriggers( $lbFactory, $services->getMainConfig() );
+               $lbFactory = $this->localServices->getDBLoadBalancerFactory();
+               Maintenance::setLBFactoryTriggers( $lbFactory, $this->localServices->getMainConfig() );
 
                ob_start( 'MediaWikiTestCase::wfResetOutputBuffersBarrier' );
        }
@@ -654,10 +586,6 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                $this->mwGlobalsToUnset = [];
                $this->restoreLoggers();
 
-               if ( self::$serviceLocator && MediaWikiServices::getInstance() !== self::$serviceLocator ) {
-                       MediaWikiServices::forceGlobalInstance( self::$serviceLocator );
-               }
-
                // TODO: move global state into MediaWikiServices
                RequestContext::resetMain();
                if ( session_id() !== '' ) {
@@ -708,13 +636,12 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
         * @param object $object
         */
        protected function setService( $name, $object ) {
-               // If we did not yet override the service locator, so so now.
-               if ( MediaWikiServices::getInstance() === self::$serviceLocator ) {
-                       $this->overrideMwServices();
+               if ( !$this->localServices ) {
+                       throw new Exception( __METHOD__ . ' must be called after MediaWikiTestCase::run()' );
                }
 
-               MediaWikiServices::getInstance()->disableService( $name );
-               MediaWikiServices::getInstance()->redefineService(
+               $this->localServices->disableService( $name );
+               $this->localServices->redefineService(
                        $name,
                        function () use ( $object ) {
                                return $object;
@@ -796,15 +723,17 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
         * Otherwise old namespace data will lurk and cause bugs.
         */
        private function resetNamespaces() {
+               if ( !$this->localServices ) {
+                       throw new Exception( __METHOD__ . ' must be called after MediaWikiTestCase::run()' );
+               }
                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' );
+               $this->localServices->resetServiceForTesting( 'TitleFormatter' );
+               $this->localServices->resetServiceForTesting( 'TitleParser' );
+               $this->localServices->resetServiceForTesting( '_MediaWikiTitleCodec' );
        }
 
        /**
@@ -963,16 +892,15 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
         * @return MediaWikiServices
         * @throws MWException
         */
-       protected static function overrideMwServices(
+       protected function overrideMwServices(
                Config $configOverrides = null, array $services = []
        ) {
                if ( !$configOverrides ) {
                        $configOverrides = new HashConfig();
                }
 
-               $oldInstance = MediaWikiServices::getInstance();
-               $oldConfigFactory = $oldInstance->getConfigFactory();
-               $oldLoadBalancerFactory = $oldInstance->getDBLoadBalancerFactory();
+               $oldConfigFactory = self::$originalServices->getConfigFactory();
+               $oldLoadBalancerFactory = self::$originalServices->getDBLoadBalancerFactory();
 
                $testConfig = self::makeTestConfig( null, $configOverrides );
                $newInstance = new MediaWikiServices( $testConfig );
@@ -994,7 +922,13 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                        $oldLoadBalancerFactory,
                        $newInstance
                );
+
+               if ( $this->localServices ) {
+                       $this->localServices->destroy();
+               }
+
                MediaWikiServices::forceGlobalInstance( $newInstance );
+               $this->localServices = $newInstance;
 
                return $newInstance;
        }
@@ -1682,12 +1616,6 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                foreach ( $tables as $tbl ) {
                        $tbl = $db->tableName( $tbl );
                        $db->query( "DROP TABLE IF EXISTS $tbl", __METHOD__ );
-
-                       if ( $tbl === 'page' ) {
-                               // Forget about the pages since they don't
-                               // exist in the DB.
-                               MediaWikiServices::getInstance()->getLinkCache()->clear();
-                       }
                }
        }
 
@@ -1749,14 +1677,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 );
 
@@ -1787,6 +1711,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();
                        }
@@ -1818,28 +1744,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__
-                       );
-               }
-
-               if ( $tableName === 'page' ) {
-                       // Forget about the pages since they don't
-                       // exist in the DB.
-                       MediaWikiServices::getInstance()->getLinkCache()->clear();
+               // re-initialize site_stats table
+               if ( $tableName === 'site_stats' ) {
+                       SiteStatsInit::doPlaceholderInit();
                }
        }
 
index 0e35767..685cb40 100644 (file)
@@ -68,8 +68,6 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
                parent::tearDown();
 
                TestingAccessWrapper::newFromClass( Hooks::class )->handlers = $this->originalHandlers;
-
-               $this->overrideMwServices();
        }
 
        protected function searchProvision( array $results = null ) {
index ff4c198..c760b41 100644 (file)
@@ -1452,14 +1452,14 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        Revision::DELETED_TEXT,
                        Revision::DELETED_TEXT,
                        [ 'sysop' ],
-                       Title::newFromText( __METHOD__ ),
+                       __METHOD__,
                        true,
                ];
                yield [
                        Revision::DELETED_TEXT,
                        Revision::DELETED_TEXT,
                        [],
-                       Title::newFromText( __METHOD__ ),
+                       __METHOD__,
                        false,
                ];
        }
@@ -1469,6 +1469,8 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
         * @covers Revision::userCanBitfield
         */
        public function testUserCanBitfield( $bitField, $field, $userGroups, $title, $expected ) {
+               $title = Title::newFromText( $title );
+
                $this->setMwGlobals(
                        'wgGroupPermissions',
                        [
index 0e0d609..189b79b 100644 (file)
@@ -530,8 +530,26 @@ class DerivedPageDataUpdaterTest extends MediaWikiTestCase {
                return $rev;
        }
 
+       /**
+        * @param int $id
+        * @return Title
+        */
+       private function getMockTitle( $id = 23 ) {
+               $mock = $this->getMockBuilder( Title::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $mock->expects( $this->any() )
+                       ->method( 'getDBkey' )
+                       ->will( $this->returnValue( __CLASS__ ) );
+               $mock->expects( $this->any() )
+                       ->method( 'getArticleID' )
+                       ->will( $this->returnValue( $id ) );
+
+               return $mock;
+       }
+
        public function provideIsReusableFor() {
-               $title = Title::makeTitleSafe( NS_MAIN, __METHOD__ );
+               $title = $this->getMockTitle();
 
                $user1 = User::newFromName( 'Alice' );
                $user2 = User::newFromName( 'Bob' );
diff --git a/tests/phpunit/includes/Storage/NameTableStoreFactoryTest.php b/tests/phpunit/includes/Storage/NameTableStoreFactoryTest.php
new file mode 100644 (file)
index 0000000..f377993
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+
+namespace MediaWiki\Tests\Storage;
+
+use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\NameTableStore;
+use MediaWiki\Storage\NameTableStoreFactory;
+use MediaWikiTestCase;
+use Wikimedia\Rdbms\ILBFactory;
+use Wikimedia\Rdbms\ILoadBalancer;
+
+/**
+ * @covers MediaWiki\Storage\NameTableStoreFactory
+ * @group Database
+ */
+class NameTableStoreFactoryTest extends MediaWikiTestCase {
+       /**
+        * @return \PHPUnit_Framework_MockObject_MockObject|ILoadBalancer
+        */
+       private function getMockLoadBalancer() {
+               return $this->getMockBuilder( ILoadBalancer::class )
+                       ->disableOriginalConstructor()->getMock();
+       }
+
+       /**
+        * @return \PHPUnit_Framework_MockObject_MockObject|ILBFactory
+        */
+       private function getMockLoadBalancerFactory( $expectedWiki ) {
+               $mock = $this->getMockBuilder( ILBFactory::class )
+                       ->disableOriginalConstructor()->getMock();
+
+               $mock->expects( $this->once() )
+                       ->method( 'getMainLB' )
+                       ->with( $this->equalTo( $expectedWiki ) )
+                       ->willReturnCallback( function ( $domain ) use ( $expectedWiki ) {
+                               return $this->getMockLoadBalancer();
+                       } );
+
+               return $mock;
+       }
+
+       public static function provideTestGet() {
+               return [
+                       [
+                               'change_tag_def',
+                               false,
+                               false,
+                       ],
+                       [
+                               'content_models',
+                               false,
+                               false,
+                       ],
+                       [
+                               'slot_roles',
+                               false,
+                               false,
+                       ],
+                       [
+                               'change_tag_def',
+                               'test7245',
+                               'test7245',
+                       ],
+               ];
+       }
+
+       /** @dataProvider provideTestGet */
+       public function testGet( $tableName, $wiki, $expectedWiki ) {
+               $services = MediaWikiServices::getInstance();
+               $db = wfGetDB( DB_MASTER );
+               if ( $wiki === false ) {
+                       $wiki2 = $db->getWikiID();
+               } else {
+                       $wiki2 = $wiki;
+               }
+               $names = new NameTableStoreFactory(
+                       $this->getMockLoadBalancerFactory( $expectedWiki ),
+                       $services->getMainWANObjectCache(),
+                       LoggerFactory::getInstance( 'NameTableStoreFactory' )
+               );
+
+               $table = $names->get( $tableName, $wiki );
+               $table2 = $names->get( $tableName, $wiki2 );
+               $this->assertSame( $table, $table2 );
+               $this->assertInstanceOf( NameTableStore::class, $table );
+       }
+
+       /*
+        * The next three integration tests verify that the schema information is correct by loading
+        * the relevant information from the database.
+        */
+
+       public function testIntegratedGetChangeTagDef() {
+               $services = MediaWikiServices::getInstance();
+               $factory = $services->getNameTableStoreFactory();
+               $store = $factory->getChangeTagDef();
+               $this->assertType( 'array', $store->getMap() );
+       }
+
+       public function testIntegratedGetContentModels() {
+               $services = MediaWikiServices::getInstance();
+               $factory = $services->getNameTableStoreFactory();
+               $store = $factory->getContentModels();
+               $this->assertType( 'array', $store->getMap() );
+       }
+
+       public function testIntegratedGetSlotRoles() {
+               $services = MediaWikiServices::getInstance();
+               $factory = $services->getNameTableStoreFactory();
+               $store = $factory->getSlotRoles();
+               $this->assertType( 'array', $store->getMap() );
+       }
+}
index 2805ea8..517e7c6 100644 (file)
@@ -19,14 +19,6 @@ use WikiPage;
  * @group Database
  */
 class PageUpdaterTest extends MediaWikiTestCase {
-
-       public static function setUpBeforeClass() {
-               parent::setUpBeforeClass();
-
-               // force service reset!
-               MediaWikiServices::getInstance()->resetServiceForTesting( 'RevisionStore' );
-       }
-
        private function getDummyTitle( $method ) {
                return Title::newFromText( $method, $this->getDefaultWikitextNS() );
        }
index 30dacdb..eb048a7 100644 (file)
@@ -343,14 +343,14 @@ trait RevisionRecordTests {
                        RevisionRecord::DELETED_TEXT,
                        RevisionRecord::DELETED_TEXT,
                        [ 'sysop' ],
-                       Title::newFromText( __METHOD__ ),
+                       __METHOD__,
                        true,
                ];
                yield [
                        RevisionRecord::DELETED_TEXT,
                        RevisionRecord::DELETED_TEXT,
                        [],
-                       Title::newFromText( __METHOD__ ),
+                       __METHOD__,
                        false,
                ];
        }
@@ -360,6 +360,11 @@ trait RevisionRecordTests {
         * @covers \MediaWiki\Storage\RevisionRecord::userCanBitfield
         */
        public function testUserCanBitfield( $bitField, $field, $userGroups, $title, $expected ) {
+               if ( is_string( $title ) ) {
+                       // NOTE: Data providers cannot instantiate Title objects! See T202641.
+                       $title = Title::newFromText( $title );
+               }
+
                $this->forceStandardPermissions();
 
                $user = $this->getTestUser( $userGroups )->getUser();
@@ -371,72 +376,75 @@ trait RevisionRecordTests {
        }
 
        public function provideHasSameContent() {
-               /**
-                * @param SlotRecord[] $slots
-                * @param int $revId
-                * @return RevisionStoreRecord
-                */
-               $recordCreator = function ( array $slots, $revId ) {
-                       $title = Title::newFromText( 'provideHasSameContent' );
-                       $title->resetArticleID( 19 );
-                       $slots = new RevisionSlots( $slots );
-
-                       return new RevisionStoreRecord(
-                               $title,
-                               new UserIdentityValue( 11, __METHOD__, 0 ),
-                               CommentStoreComment::newUnsavedComment( __METHOD__ ),
-                               (object)[
-                                       'rev_id' => strval( $revId ),
-                                       'rev_page' => strval( $title->getArticleID() ),
-                                       'rev_timestamp' => '20200101000000',
-                                       'rev_deleted' => 0,
-                                       'rev_minor_edit' => 0,
-                                       'rev_parent_id' => '5',
-                                       'rev_len' => $slots->computeSize(),
-                                       'rev_sha1' => $slots->computeSha1(),
-                                       'page_latest' => '18',
-                               ],
-                               $slots
-                       );
-               };
-
                // Create some slots with content
                $mainA = SlotRecord::newUnsaved( 'main', new TextContent( 'A' ) );
                $mainB = SlotRecord::newUnsaved( 'main', new TextContent( 'B' ) );
                $auxA = SlotRecord::newUnsaved( 'aux', new TextContent( 'A' ) );
                $auxB = SlotRecord::newUnsaved( 'aux', new TextContent( 'A' ) );
 
-               $initialRecord = $recordCreator( [ $mainA ], 12 );
+               $initialRecordSpec = [ [ $mainA ], 12 ];
 
                return [
                        'same record object' => [
                                true,
-                               $initialRecord,
-                               $initialRecord,
+                               $initialRecordSpec,
+                               $initialRecordSpec,
                        ],
                        'same record content, different object' => [
                                true,
-                               $recordCreator( [ $mainA ], 12 ),
-                               $recordCreator( [ $mainA ], 13 ),
+                               [ [ $mainA ], 12 ],
+                               [ [ $mainA ], 13 ],
                        ],
                        'same record content, aux slot, different object' => [
                                true,
-                               $recordCreator( [ $auxA ], 12 ),
-                               $recordCreator( [ $auxB ], 13 ),
+                               [ [ $auxA ], 12 ],
+                               [ [ $auxB ], 13 ],
                        ],
                        'different content' => [
                                false,
-                               $recordCreator( [ $mainA ], 12 ),
-                               $recordCreator( [ $mainB ], 13 ),
+                               [ [ $mainA ], 12 ],
+                               [ [ $mainB ], 13 ],
                        ],
                        'different content and number of slots' => [
                                false,
-                               $recordCreator( [ $mainA ], 12 ),
-                               $recordCreator( [ $mainA, $mainB ], 13 ),
+                               [ [ $mainA ], 12 ],
+                               [ [ $mainA, $mainB ], 13 ],
                        ],
                ];
        }
 
+       /**
+        * @note Do not call directly from a data provider! Data providers cannot instantiate
+        * Title objects! See T202641.
+        *
+        * @param SlotRecord[] $slots
+        * @param int $revId
+        * @return RevisionStoreRecord
+        */
+       private function makeHasSameContentTestRecord( array $slots, $revId ) {
+               $title = Title::newFromText( 'provideHasSameContent' );
+               $title->resetArticleID( 19 );
+               $slots = new RevisionSlots( $slots );
+
+               return new RevisionStoreRecord(
+                       $title,
+                       new UserIdentityValue( 11, __METHOD__, 0 ),
+                       CommentStoreComment::newUnsavedComment( __METHOD__ ),
+                       (object)[
+                               'rev_id' => strval( $revId ),
+                               'rev_page' => strval( $title->getArticleID() ),
+                               'rev_timestamp' => '20200101000000',
+                               'rev_deleted' => 0,
+                               'rev_minor_edit' => 0,
+                               'rev_parent_id' => '5',
+                               'rev_len' => $slots->computeSize(),
+                               'rev_sha1' => $slots->computeSha1(),
+                               'page_latest' => '18',
+                       ],
+                       $slots
+               );
+       }
+
        /**
         * @dataProvider provideHasSameContent
         * @covers \MediaWiki\Storage\RevisionRecord::hasSameContent
@@ -444,9 +452,12 @@ trait RevisionRecordTests {
         */
        public function testHasSameContent(
                $expected,
-               RevisionRecord $record1,
-               RevisionRecord $record2
+               $recordSpec1,
+               $recordSpec2
        ) {
+               $record1 = $this->makeHasSameContentTestRecord( ...$recordSpec1 );
+               $record2 = $this->makeHasSameContentTestRecord( ...$recordSpec2 );
+
                $this->assertSame(
                        $expected,
                        $record1->hasSameContent( $record2 )
index 3f8bd4b..1d8771b 100644 (file)
@@ -8,6 +8,7 @@ use MediaWiki\Logger\Spi as LoggerSpi;
 use MediaWiki\Storage\BlobStore;
 use MediaWiki\Storage\BlobStoreFactory;
 use MediaWiki\Storage\NameTableStore;
+use MediaWiki\Storage\NameTableStoreFactory;
 use MediaWiki\Storage\RevisionStore;
 use MediaWiki\Storage\RevisionStoreFactory;
 use MediaWiki\Storage\SqlBlobStore;
@@ -25,6 +26,7 @@ class RevisionStoreFactoryTest extends MediaWikiTestCase {
                new RevisionStoreFactory(
                        $this->getMockLoadBalancerFactory(),
                        $this->getMockBlobStoreFactory(),
+                       $this->getNameTableStoreFactory(),
                        $this->getHashWANObjectCache(),
                        $this->getMockCommentStore(),
                        ActorMigration::newMigration(),
@@ -53,6 +55,7 @@ class RevisionStoreFactoryTest extends MediaWikiTestCase {
        ) {
                $lbFactory = $this->getMockLoadBalancerFactory();
                $blobStoreFactory = $this->getMockBlobStoreFactory();
+               $nameTableStoreFactory = $this->getNameTableStoreFactory();
                $cache = $this->getHashWANObjectCache();
                $commentStore = $this->getMockCommentStore();
                $actorMigration = ActorMigration::newMigration();
@@ -61,6 +64,7 @@ class RevisionStoreFactoryTest extends MediaWikiTestCase {
                $factory = new RevisionStoreFactory(
                        $lbFactory,
                        $blobStoreFactory,
+                       $nameTableStoreFactory,
                        $cache,
                        $commentStore,
                        $actorMigration,
@@ -138,6 +142,16 @@ class RevisionStoreFactoryTest extends MediaWikiTestCase {
                return $mock;
        }
 
+       /**
+        * @return NameTableStoreFactory
+        */
+       private function getNameTableStoreFactory() {
+               return new NameTableStoreFactory(
+                       $this->getMockLoadBalancerFactory(),
+                       $this->getHashWANObjectCache(),
+                       new NullLogger() );
+       }
+
        /**
         * @return \PHPUnit_Framework_MockObject_MockObject|CommentStore
         */
index 90bd57a..5307ca9 100644 (file)
@@ -110,13 +110,15 @@ class RevisionStoreTest extends MediaWikiTestCase {
                        $this->setExpectedException( MWException::class );
                }
 
+               $nameTables = MediaWikiServices::getInstance()->getNameTableStoreFactory();
+
                $store = new RevisionStore(
                        $this->getMockLoadBalancer(),
                        $this->getMockSqlBlobStore(),
                        $this->getHashWANObjectCache(),
                        $this->getMockCommentStore(),
-                       MediaWikiServices::getInstance()->getContentModelStore(),
-                       MediaWikiServices::getInstance()->getSlotRoleStore(),
+                       $nameTables->getContentModels(),
+                       $nameTables->getSlotRoles(),
                        $migrationMode,
                        MediaWikiServices::getInstance()->getActorMigration()
                );
@@ -508,17 +510,19 @@ class RevisionStoreTest extends MediaWikiTestCase {
                $blobStore = $this->getMockSqlBlobStore();
                $cache = $this->getHashWANObjectCache();
                $commentStore = $this->getMockCommentStore();
-               $contentModelStore = MediaWikiServices::getInstance()->getContentModelStore();
-               $slotRoleStore = MediaWikiServices::getInstance()->getSlotRoleStore();
+               $services = MediaWikiServices::getInstance();
+               $nameTables = $services->getNameTableStoreFactory();
+               $contentModelStore = $nameTables->getContentModels();
+               $slotRoleStore = $nameTables->getSlotRoles();
                $store = new RevisionStore(
                        $loadBalancer,
                        $blobStore,
                        $cache,
                        $commentStore,
-                       MediaWikiServices::getInstance()->getContentModelStore(),
-                       MediaWikiServices::getInstance()->getSlotRoleStore(),
+                       $nameTables->getContentModels(),
+                       $nameTables->getSlotRoles(),
                        $migration,
-                       MediaWikiServices::getInstance()->getActorMigration()
+                       $services->getActorMigration()
                );
                if ( !$expectException ) {
                        $store = TestingAccessWrapper::newFromObject( $store );
index 0700cf7..708ebc5 100644 (file)
@@ -10,22 +10,22 @@ class ApiQuerySearchTest extends ApiTestCase {
                        'empty search result' => [ [], [] ],
                        'has search results' => [
                                [ 'Zomg' ],
-                               [ $this->mockResult( 'Zomg' ) ],
+                               [ $this->mockResultClosure( 'Zomg' ) ],
                        ],
                        'filters broken search results' => [
                                [ 'A', 'B' ],
                                [
-                                       $this->mockResult( 'a' ),
-                                       $this->mockResult( 'Zomg' )->setBrokenTitle( true ),
-                                       $this->mockResult( 'b' ),
+                                       $this->mockResultClosure( 'a' ),
+                                       $this->mockResultClosure( 'Zomg', [ 'setBrokenTitle' => true ] ),
+                                       $this->mockResultClosure( 'b' ),
                                ],
                        ],
                        'filters results with missing revision' => [
                                [ 'B', 'A' ],
                                [
-                                       $this->mockResult( 'Zomg' )->setMissingRevision( true ),
-                                       $this->mockResult( 'b' ),
-                                       $this->mockResult( 'a' ),
+                                       $this->mockResultClosure( 'Zomg', [ 'setMissingRevision' => true ] ),
+                                       $this->mockResultClosure( 'b' ),
+                                       $this->mockResultClosure( 'a' ),
                                ],
                        ],
                ];
@@ -56,7 +56,10 @@ class ApiQuerySearchTest extends ApiTestCase {
                                [
                                        SearchResultSet::SECONDARY_RESULTS => [
                                                'utwiki' => new MockSearchResultSet( [
-                                                       $this->mockResult( 'Qwerty' )->setInterwikiPrefix( 'utwiki' ),
+                                                       $this->mockResultClosure(
+                                                               'Qwerty',
+                                                               [ 'setInterwikiPrefix' => 'utwiki' ]
+                                                       ),
                                                ] ),
                                        ],
                                ]
@@ -102,8 +105,28 @@ class ApiQuerySearchTest extends ApiTestCase {
                ] );
        }
 
-       private function mockResult( $title ) {
-               return MockSearchResult::newFromtitle( Title::newFromText( $title ) );
+       /**
+        * Returns a closure that evaluates to a MockSearchResult, to be resolved by
+        * MockSearchEngine::addMockResults() or MockresultSet::extractResults().
+        *
+        * This is needed because MockSearchResults cannot be instantiated in a data provider,
+        * since they load revisions. This would hit the "real" database instead of the mock
+        * database, which in turn may cause cache pollution and other inconsistencies, see T202641.
+        *
+        * @param string $title
+        * @param array $setters
+        * @return callable function(): MockSearchResult
+        */
+       private function mockResultClosure( $title, $setters = [] ) {
+               return function () use ( $title, $setters ){
+                       $result = MockSearchResult::newFromTitle( Title::newFromText( $title ) );
+
+                       foreach ( $setters as $method => $param ) {
+                               $result->$method( $param );
+                       }
+
+                       return $result;
+               };
        }
 
 }
index 3ef4f05..c770029 100644 (file)
@@ -510,7 +510,7 @@ class ChangeTagsTest extends MediaWikiTestCase {
                $dbw = wfGetDB( DB_MASTER );
                $dbw->delete( 'change_tag', '*' );
                $dbw->delete( 'change_tag_def', '*' );
-               MediaWikiServices::getInstance()->resetServiceForTesting( 'ChangeTagDefStore' );
+               MediaWikiServices::getInstance()->resetServiceForTesting( 'NameTableStoreFactory' );
 
                $rcId = 123;
                ChangeTags::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
@@ -564,7 +564,7 @@ class ChangeTagsTest extends MediaWikiTestCase {
                $dbw = wfGetDB( DB_MASTER );
                $dbw->delete( 'change_tag', '*' );
                $dbw->delete( 'change_tag_def', '*' );
-               MediaWikiServices::getInstance()->resetServiceForTesting( 'ChangeTagDefStore' );
+               MediaWikiServices::getInstance()->resetServiceForTesting( 'NameTableStoreFactory' );
 
                $rcId = 123;
                ChangeTags::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
@@ -582,7 +582,7 @@ class ChangeTagsTest extends MediaWikiTestCase {
                $dbw = wfGetDB( DB_MASTER );
                $dbw->delete( 'change_tag', '*' );
                $dbw->delete( 'change_tag_def', '*' );
-               MediaWikiServices::getInstance()->resetServiceForTesting( 'ChangeTagDefStore' );
+               MediaWikiServices::getInstance()->resetServiceForTesting( 'NameTableStoreFactory' );
 
                $rcId = 123;
                ChangeTags::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
index 3336235..07d02dd 100644 (file)
@@ -343,12 +343,30 @@ class DifferenceEngineTest extends MediaWikiTestCase {
                        trim( strip_tags( $diff ), "\n" ) );
        }
 
+       /**
+        * @param int $id
+        * @return Title
+        */
+       private function getMockTitle( $id = 23 ) {
+               $mock = $this->getMockBuilder( Title::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $mock->expects( $this->any() )
+                       ->method( 'getDBkey' )
+                       ->will( $this->returnValue( __CLASS__ ) );
+               $mock->expects( $this->any() )
+                       ->method( 'getArticleID' )
+                       ->will( $this->returnValue( $id ) );
+
+               return $mock;
+       }
+
        /**
         * @param SlotRecord[] $slots
         * @return MutableRevisionRecord
         */
        private function getRevisionRecord( ...$slots ) {
-               $title = Title::newFromText( 'Foo' );
+               $title = $this->getMockTitle();
                $revision = new MutableRevisionRecord( $title );
                foreach ( $slots as $slot ) {
                        $revision->setSlot( $slot );
index 86956f2..aa3f820 100644 (file)
@@ -572,6 +572,7 @@ mw.loader.register( [
                $version1 = $module->getVersionHash( $context );
                $module = new ResourceLoaderStartupModule();
                $version2 = $module->getVersionHash( $context );
+
                $this->setMwGlobals( 'wgArticlePath', '/w3' );
                $module = new ResourceLoaderStartupModule();
                $version3 = $module->getVersionHash( $context );
@@ -590,8 +591,7 @@ mw.loader.register( [
        }
 
        /**
-        * @covers ResourceLoaderStartupModule::getAllModuleHashes
-        * @covers ResourceLoaderStartupModule::getDefinitionSummary
+        * @covers ResourceLoaderStartupModule
         */
        public function testGetVersionHash_varyModule() {
                $context1 = $this->getResourceLoaderContext();
@@ -621,10 +621,11 @@ mw.loader.register( [
                $module = new ResourceLoaderStartupModule();
                $version3 = $module->getVersionHash( $context3 );
 
-               $this->assertEquals(
+               // Module name *is* significant (T201686)
+               $this->assertNotEquals(
                        $version1,
                        $version2,
-                       'Module name is insignificant'
+                       'Module name is significant'
                );
 
                $this->assertNotEquals(
@@ -634,4 +635,32 @@ mw.loader.register( [
                );
        }
 
+       /**
+        * @covers ResourceLoaderStartupModule
+        */
+       public function testGetVersionHash_varyDeps() {
+               $context = $this->getResourceLoaderContext();
+               $rl = $context->getResourceLoader();
+               $rl->register( [
+                       'test.a' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'x', 'y' ] ] ),
+               ] );
+               $module = new ResourceLoaderStartupModule();
+               $version1 = $module->getVersionHash( $context );
+
+               $context = $this->getResourceLoaderContext();
+               $rl = $context->getResourceLoader();
+               $rl->register( [
+                       'test.a' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'x', 'z' ] ] ),
+               ] );
+               $module = new ResourceLoaderStartupModule();
+               $version2 = $module->getVersionHash( $context );
+
+               // Dependencies *are* significant (T201686)
+               $this->assertNotEquals(
+                       $version1,
+                       $version2,
+                       'Dependencies are significant'
+               );
+       }
+
 }
index 41c1218..ee272b9 100644 (file)
@@ -76,8 +76,6 @@ class SearchEnginePrefixTest extends MediaWikiLangTestCase {
                parent::tearDown();
 
                TestingAccessWrapper::newFromClass( Hooks::class )->handlers = $this->originalHandlers;
-
-               $this->overrideMwServices();
        }
 
        protected function searchProvision( array $results = null ) {
index 2b7ea47..207ac28 100644 (file)
@@ -19,12 +19,17 @@ class MockSearchEngine extends SearchEngine {
         * @param SearchResult[] $results The results to return for $query
         */
        public static function addMockResults( $query, array $results ) {
-               self::$results[$query] = $results;
                $lc = MediaWikiServices::getInstance()->getLinkCache();
-               foreach ( $results as $result ) {
+               foreach ( $results as &$result ) {
+                       // Resolve deferred results; needed to work around T203279
+                       if ( is_callable( $result ) ) {
+                               $result = $result();
+                       }
+
                        // TODO: better page ids? Does it matter?
                        $lc->addGoodLinkObj( mt_rand(), $result->getTitle() );
                }
+               self::$results[$query] = $results;
        }
 
        /**
index 20e2a9f..38f6731 100644 (file)
@@ -8,8 +8,8 @@ class MockSearchResultSet extends SearchResultSet {
        private $interwikiResults;
 
        /**
-        * @param SearchResult[] $results
-        * @param SearchResultSet[][] $interwikiResults Map from result type
+        * @param SearchResult[]|callable[] $results
+        * @param SearchResultSet[][]|callable[][] $interwikiResults Map from result type
         *  to list of results for that type.
         */
        public function __construct( array $results, array $interwikiResults = [] ) {
@@ -27,6 +27,19 @@ class MockSearchResultSet extends SearchResultSet {
                        count( $this->interwikiResults[$type] ) > 0;
        }
 
+       public function extractResults() {
+               $results = parent::extractResults();
+
+               foreach ( $results as &$result ) {
+                       // Resolve deferred results; needed to work around T203279
+                       if ( is_callable( $result ) ) {
+                               $result = $result();
+                       }
+               }
+
+               return $results;
+       }
+
        public function getInterwikiResults( $type = self::SECONDARY_RESULTS ) {
                if ( $this->hasInterwikiResults( $type ) ) {
                        return $this->interwikiResults[$type];
index d83dedb..b1cca4a 100755 (executable)
@@ -129,9 +129,6 @@ class PHPUnitMaintClass extends Maintenance {
                        'Using HHVM ' . HHVM_VERSION . ' (' . PHP_VERSION . ")\n" :
                        'Using PHP ' . PHP_VERSION . "\n";
 
-               // Prepare global services for unit tests.
-               MediaWikiTestCase::prepareServices( new GlobalVarConfig() );
-
                $phpUnitClass::main();
        }
 
index 9d85fde..a6bc5a7 100644 (file)
@@ -13,17 +13,6 @@ use MediaWiki\MediaWikiServices;
  * @author Addshore
  */
 class SpecialPageFatalTest extends MediaWikiTestCase {
-
-       public static function setUpBeforeClass() {
-               parent::setUpBeforeClass();
-               self::overrideMwServices();
-       }
-
-       public static function tearDownAfterClass() {
-               self::overrideMwServices();
-               parent::tearDownAfterClass();
-       }
-
        public function provideSpecialPages() {
                $specialPages = [];
                $spf = MediaWikiServices::getInstance()->getSpecialPageFactory();
index 1850f6f..d2fca72 100644 (file)
@@ -114,9 +114,6 @@ class MediaWikiTestCaseTest extends MediaWikiTestCase {
 
                $this->overrideMwServices();
                $this->assertNotSame( $initialServices, MediaWikiServices::getInstance() );
-
-               $this->tearDown();
-               $this->assertSame( $initialServices, MediaWikiServices::getInstance() );
        }
 
        public function testSetService() {
@@ -126,17 +123,11 @@ class MediaWikiTestCaseTest extends MediaWikiTestCase {
                        ->disableOriginalConstructor()->getMock();
 
                $this->setService( 'DBLoadBalancer', $mockService );
-               $this->assertNotSame( $initialServices, MediaWikiServices::getInstance() );
                $this->assertNotSame(
                        $initialService,
                        MediaWikiServices::getInstance()->getDBLoadBalancer()
                );
                $this->assertSame( $mockService, MediaWikiServices::getInstance()->getDBLoadBalancer() );
-
-               $this->tearDown();
-               $this->assertSame( $initialServices, MediaWikiServices::getInstance() );
-               $this->assertNotSame( $mockService, MediaWikiServices::getInstance()->getDBLoadBalancer() );
-               $this->assertSame( $initialService, MediaWikiServices::getInstance()->getDBLoadBalancer() );
        }
 
        /**