From 0ecfe75502b5a7a7224cf79531b1fd90662048cb Mon Sep 17 00:00:00 2001 From: Tim Starling Date: Tue, 4 Sep 2018 11:59:03 +1000 Subject: [PATCH] Introduce NameTableStoreFactory With a separate service for each of the NameTableStore tables, it wasn't possible to instantiate a NameTableStore for a foreign wiki, leading to the inelegant situation of having RevisionStoreFactory construct a new NameTableStoreFactory every time a RevisionStore for a foreign wiki was requested. These NameTableStore objects were not tracked in any structured way, so there was no way to reset them for tests. So, introduce NameTableStoreFactory, which tracks object instances for both local and remote table access. This also avoids having schema details in ServiceWiring.php. Depends-On: I5c78cfb8bf90eca935a3264592366f63517c4fad Bug: T202641 Change-Id: Ic0f2d1d94bad9dcc047ff19a1f92db89b7e014ce --- includes/MediaWikiServices.php | 14 +- includes/ServiceWiring.php | 58 ++----- includes/Storage/NameTableStoreFactory.php | 149 ++++++++++++++++++ includes/Storage/RevisionStoreFactory.php | 50 +----- tests/phpunit/MediaWikiTestCase.php | 15 +- .../Storage/NameTableStoreFactoryTest.php | 114 ++++++++++++++ .../Storage/RevisionStoreFactoryTest.php | 14 ++ .../includes/Storage/RevisionStoreTest.php | 18 ++- .../includes/changetags/ChangeTagsTest.php | 6 +- 9 files changed, 329 insertions(+), 109 deletions(-) create mode 100644 includes/Storage/NameTableStoreFactory.php create mode 100644 tests/phpunit/includes/Storage/NameTableStoreFactoryTest.php diff --git a/includes/MediaWikiServices.php b/includes/MediaWikiServices.php index 5b53ad11be..b236ca1da1 100644 --- a/includes/MediaWikiServices.php +++ b/includes/MediaWikiServices.php @@ -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(); } /** diff --git a/includes/ServiceWiring.php b/includes/ServiceWiring.php index 59cdec9377..b8bd5d2270 100644 --- a/includes/ServiceWiring.php +++ b/includes/ServiceWiring.php @@ -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 index 0000000000..02ea9a7656 --- /dev/null +++ b/includes/Storage/NameTableStoreFactory.php @@ -0,0 +1,149 @@ + [ + '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 ); + } +} diff --git a/includes/Storage/RevisionStoreFactory.php b/includes/Storage/RevisionStoreFactory.php index 9419b40939..aaaafc15d1 100644 --- a/includes/Storage/RevisionStoreFactory.php +++ b/includes/Storage/RevisionStoreFactory.php @@ -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 - ); - } - } diff --git a/tests/phpunit/MediaWikiTestCase.php b/tests/phpunit/MediaWikiTestCase.php index 3917ca6152..78636b111c 100644 --- a/tests/phpunit/MediaWikiTestCase.php +++ b/tests/phpunit/MediaWikiTestCase.php @@ -1677,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 ); @@ -1715,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(); } @@ -1746,10 +1744,15 @@ 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__ ); } + + // re-initialize site_stats table + if ( $tableName === 'site_stats' ) { + SiteStatsInit::doPlaceholderInit(); + } } private static function unprefixTable( &$tableName, $ind, $prefix ) { diff --git a/tests/phpunit/includes/Storage/NameTableStoreFactoryTest.php b/tests/phpunit/includes/Storage/NameTableStoreFactoryTest.php new file mode 100644 index 0000000000..f377993d89 --- /dev/null +++ b/tests/phpunit/includes/Storage/NameTableStoreFactoryTest.php @@ -0,0 +1,114 @@ +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() ); + } +} diff --git a/tests/phpunit/includes/Storage/RevisionStoreFactoryTest.php b/tests/phpunit/includes/Storage/RevisionStoreFactoryTest.php index 3f8bd4b6ab..1d8771be5c 100644 --- a/tests/phpunit/includes/Storage/RevisionStoreFactoryTest.php +++ b/tests/phpunit/includes/Storage/RevisionStoreFactoryTest.php @@ -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 */ diff --git a/tests/phpunit/includes/Storage/RevisionStoreTest.php b/tests/phpunit/includes/Storage/RevisionStoreTest.php index 90bd57aa6d..5307ca9233 100644 --- a/tests/phpunit/includes/Storage/RevisionStoreTest.php +++ b/tests/phpunit/includes/Storage/RevisionStoreTest.php @@ -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 ); diff --git a/tests/phpunit/includes/changetags/ChangeTagsTest.php b/tests/phpunit/includes/changetags/ChangeTagsTest.php index 3ef4f05dcb..c770029816 100644 --- a/tests/phpunit/includes/changetags/ChangeTagsTest.php +++ b/tests/phpunit/includes/changetags/ChangeTagsTest.php @@ -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 ); -- 2.20.1