From 32e9266d8dccbb5e6f4ff8f208658eae1e1b4d7a Mon Sep 17 00:00:00 2001 From: daniel Date: Mon, 14 May 2018 13:57:43 +0200 Subject: [PATCH] Introduce per-schema unit tests for revision storage. This introduces traits for testing different schema variations. These are not very useful in this patch, but make it much easier to add tests for MCR schema migration in subsequent patches. The code in this patch was previously part of If259b1e1c49ceaa4. Change-Id: I239572f75bebbc9c731a3e3860c4eff179dc15e4 --- autoload.php | 1 + includes/db/PatchFileLocation.php | 90 ++++++ tests/common/TestsAutoLoader.php | 3 + .../includes/RevisionContentHandlerDbTest.php | 14 - tests/phpunit/includes/RevisionDbTestBase.php | 19 ++ .../RevisionNoContentHandlerDbTest.php | 14 - .../includes/RevisionNoContentModelDbTest.php | 23 ++ .../phpunit/includes/RevisionPreMcrDbTest.php | 23 ++ .../includes/Storage/McrSchemaDetection.php | 42 +++ .../NoContentModelRevisionStoreDbTest.php | 114 +++++++ .../Storage/PreMcrRevisionStoreDbTest.php | 84 ++++++ .../includes/Storage/PreMcrSchemaOverride.php | 54 ++++ ...DbTest.php => RevisionStoreDbTestBase.php} | 278 ++++++++++++++++-- .../includes/Storage/RevisionStoreTest.php | 81 ----- .../Storage/create-pre-mcr-fields.sql | 3 + .../includes/Storage/drop-mcr-tables.sql | 4 + .../includes/page/WikiPageDbTestBase.php | 19 ++ .../page/WikiPageNoContentHandlerDbTest.php | 14 - .../page/WikiPageNoContentModelDbTest.php | 23 ++ ...lerDbTest.php => WikiPagePreMcrDbTest.php} | 11 +- 20 files changed, 760 insertions(+), 154 deletions(-) create mode 100644 includes/db/PatchFileLocation.php delete mode 100644 tests/phpunit/includes/RevisionContentHandlerDbTest.php delete mode 100644 tests/phpunit/includes/RevisionNoContentHandlerDbTest.php create mode 100644 tests/phpunit/includes/RevisionNoContentModelDbTest.php create mode 100644 tests/phpunit/includes/RevisionPreMcrDbTest.php create mode 100644 tests/phpunit/includes/Storage/McrSchemaDetection.php create mode 100644 tests/phpunit/includes/Storage/NoContentModelRevisionStoreDbTest.php create mode 100644 tests/phpunit/includes/Storage/PreMcrRevisionStoreDbTest.php create mode 100644 tests/phpunit/includes/Storage/PreMcrSchemaOverride.php rename tests/phpunit/includes/Storage/{RevisionStoreDbTest.php => RevisionStoreDbTestBase.php} (86%) create mode 100644 tests/phpunit/includes/Storage/create-pre-mcr-fields.sql create mode 100644 tests/phpunit/includes/Storage/drop-mcr-tables.sql delete mode 100644 tests/phpunit/includes/page/WikiPageNoContentHandlerDbTest.php create mode 100644 tests/phpunit/includes/page/WikiPageNoContentModelDbTest.php rename tests/phpunit/includes/page/{WikiPageContentHandlerDbTest.php => WikiPagePreMcrDbTest.php} (76%) diff --git a/autoload.php b/autoload.php index 9981cabe01..867430b4af 100644 --- a/autoload.php +++ b/autoload.php @@ -889,6 +889,7 @@ $wgAutoloadLocalClasses = [ 'MediaWiki\\Auth\\Throttler' => __DIR__ . '/includes/auth/Throttler.php', 'MediaWiki\\Auth\\UserDataAuthenticationRequest' => __DIR__ . '/includes/auth/UserDataAuthenticationRequest.php', 'MediaWiki\\Auth\\UsernameAuthenticationRequest' => __DIR__ . '/includes/auth/UsernameAuthenticationRequest.php', + 'MediaWiki\\DB\\PatchFileLocation' => __DIR__ . '/includes/db/PatchFileLocation.php', 'MediaWiki\\Diff\\ComplexityException' => __DIR__ . '/includes/diff/ComplexityException.php', 'MediaWiki\\Diff\\WordAccumulator' => __DIR__ . '/includes/diff/WordAccumulator.php', 'MediaWiki\\HeaderCallback' => __DIR__ . '/includes/HeaderCallback.php', diff --git a/includes/db/PatchFileLocation.php b/includes/db/PatchFileLocation.php new file mode 100644 index 0000000000..013724c343 --- /dev/null +++ b/includes/db/PatchFileLocation.php @@ -0,0 +1,90 @@ +getType(); + + if ( $patchDir === null ) { + $patchDir = $GLOBALS['IP'] . '/maintenance'; + } + + $paths = [ + + // For a small number of patch files, closely associated with code, + // e.g. for unit tests: + "$patchDir/$name.$dbType.sql", + + // For a large number of patch files, e.g. for schema updates of extensions: + "$patchDir/$dbType/$name.sql", + + // For MediaWiki core schema update patches: + "$patchDir/$dbType/archives/$name.sql", + + // Database-agnostic fallback: + "$patchDir/$name.sql", + + // Database-agnostic fallback for MediaWiki core schema update patches: + "$patchDir/archives/$name.sql" + ]; + + foreach ( $paths as $p ) { + if ( file_exists( $p ) ) { + return $p; + } + } + + throw new RuntimeException( "No SQL script matching $name could be found in $patchDir" ); + } + +} diff --git a/tests/common/TestsAutoLoader.php b/tests/common/TestsAutoLoader.php index abf718d07e..a79867913e 100644 --- a/tests/common/TestsAutoLoader.php +++ b/tests/common/TestsAutoLoader.php @@ -149,8 +149,11 @@ $wgAutoloadClasses += [ 'SpecialPageExecutor' => "$testDir/phpunit/includes/specials/SpecialPageExecutor.php", # tests/phpunit/includes/Storage + 'MediaWiki\Tests\Storage\McrSchemaDetection' => "$testDir/phpunit/includes/Storage/McrSchemaDetection.php", 'MediaWiki\Tests\Storage\RevisionSlotsTest' => "$testDir/phpunit/includes/Storage/RevisionSlotsTest.php", 'MediaWiki\Tests\Storage\RevisionRecordTests' => "$testDir/phpunit/includes/Storage/RevisionRecordTests.php", + 'MediaWiki\Tests\Storage\RevisionStoreDbTestBase' => "$testDir/phpunit/includes/Storage/RevisionStoreDbTestBase.php", + 'MediaWiki\Tests\Storage\PreMcrSchemaOverride' => "$testDir/phpunit/includes/Storage/PreMcrSchemaOverride.php", # tests/phpunit/languages 'LanguageClassesTestCase' => "$testDir/phpunit/languages/LanguageClassesTestCase.php", diff --git a/tests/phpunit/includes/RevisionContentHandlerDbTest.php b/tests/phpunit/includes/RevisionContentHandlerDbTest.php deleted file mode 100644 index fa0153d3e0..0000000000 --- a/tests/phpunit/includes/RevisionContentHandlerDbTest.php +++ /dev/null @@ -1,14 +0,0 @@ -tablesUsed += $this->getMcrTablesToReset(); + parent::setUp(); $this->mergeMwGlobalArrayValue( @@ -72,11 +84,17 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase { ); $this->setMwGlobals( 'wgContentHandlerUseDB', $this->getContentHandlerUseDB() ); + $this->setMwGlobals( + 'wgMultiContentRevisionSchemaMigrationStage', + $this->getMcrMigrationStage() + ); MWNamespace::clearCaches(); // Reset namespace cache $wgContLang->resetNamespaces(); + $this->overrideMwServices(); + if ( !$this->testPage ) { /** * We have to create a new page for each subclass as the page creation may result @@ -1346,6 +1364,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase { */ public function testNewKnownCurrent() { // Setup the services + $this->resetGlobalServices(); $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] ); $this->setService( 'MainWANObjectCache', $cache ); $db = wfGetDB( DB_MASTER ); diff --git a/tests/phpunit/includes/RevisionNoContentHandlerDbTest.php b/tests/phpunit/includes/RevisionNoContentHandlerDbTest.php deleted file mode 100644 index c980a487f7..0000000000 --- a/tests/phpunit/includes/RevisionNoContentHandlerDbTest.php +++ /dev/null @@ -1,14 +0,0 @@ -tableExists( 'slots', __METHOD__ ); + } + + /** + * Returns true if pre-MCR fields still exist in the database. + * If yes, the database is compatible with with MIGRATION_OLD mode. + * If hasMcrTables() also returns true, the database supports MIGRATION_WRITE_BOTH mode. + * + * Note that if the database has been updated in MIGRATION_NEW mode, + * the rev_text_id field will be 0 for new revisions. This means that + * in MIGRATION_OLD mode, reading such revisions will fail, even though + * all the necessary fields exist. + * This is not relevant for unit tests, since unit tests reset the database content anyway. + * + * @param IDatabase $db + * @return bool + */ + protected function hasPreMcrFields( IDatabase $db ) { + return $db->fieldExists( 'revision', 'rev_content_model', __METHOD__ ); + } + +} diff --git a/tests/phpunit/includes/Storage/NoContentModelRevisionStoreDbTest.php b/tests/phpunit/includes/Storage/NoContentModelRevisionStoreDbTest.php new file mode 100644 index 0000000000..c77a94a2c9 --- /dev/null +++ b/tests/phpunit/includes/Storage/NoContentModelRevisionStoreDbTest.php @@ -0,0 +1,114 @@ + [ 'archive' ], + 'fields' => array_merge( + $this->getDefaultArchiveFields(), + [ + 'ar_comment_text' => 'ar_comment', + 'ar_comment_data' => 'NULL', + 'ar_comment_cid' => 'NULL', + 'ar_user_text' => 'ar_user_text', + 'ar_user' => 'ar_user', + 'ar_actor' => 'NULL', + ] + ), + 'joins' => [], + ] + ]; + } + + public function provideGetQueryInfo() { + yield [ + [], + [ + 'tables' => [ 'revision' ], + 'fields' => array_merge( + $this->getDefaultQueryFields(), + $this->getCommentQueryFields(), + $this->getActorQueryFields() + ), + 'joins' => [], + ] + ]; + yield [ + [ 'page' ], + [ + 'tables' => [ 'revision', 'page' ], + 'fields' => array_merge( + $this->getDefaultQueryFields(), + $this->getCommentQueryFields(), + $this->getActorQueryFields(), + [ + 'page_namespace', + 'page_title', + 'page_id', + 'page_latest', + 'page_is_redirect', + 'page_len', + ] + ), + 'joins' => [ + 'page' => [ 'INNER JOIN', [ 'page_id = rev_page' ] ], + ], + ] + ]; + yield [ + [ 'user' ], + [ + 'tables' => [ 'revision', 'user' ], + 'fields' => array_merge( + $this->getDefaultQueryFields(), + $this->getCommentQueryFields(), + $this->getActorQueryFields(), + [ + 'user_name', + ] + ), + 'joins' => [ + 'user' => [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ], + ], + ] + ]; + yield [ + [ 'text' ], + [ + 'tables' => [ 'revision', 'text' ], + 'fields' => array_merge( + $this->getDefaultQueryFields(), + $this->getCommentQueryFields(), + $this->getActorQueryFields(), + [ + 'old_text', + 'old_flags', + ] + ), + 'joins' => [ + 'text' => [ 'INNER JOIN', [ 'rev_text_id=old_id' ] ], + ], + ] + ]; + } + +} diff --git a/tests/phpunit/includes/Storage/PreMcrRevisionStoreDbTest.php b/tests/phpunit/includes/Storage/PreMcrRevisionStoreDbTest.php new file mode 100644 index 0000000000..4336691185 --- /dev/null +++ b/tests/phpunit/includes/Storage/PreMcrRevisionStoreDbTest.php @@ -0,0 +1,84 @@ + [ 'archive' ], + 'fields' => array_merge( + $this->getDefaultArchiveFields(), + [ + 'ar_comment_text' => 'ar_comment', + 'ar_comment_data' => 'NULL', + 'ar_comment_cid' => 'NULL', + 'ar_user_text' => 'ar_user_text', + 'ar_user' => 'ar_user', + 'ar_actor' => 'NULL', + 'ar_content_format', + 'ar_content_model', + ] + ), + 'joins' => [], + ] + ]; + } + + public function provideGetQueryInfo() { + yield [ + [], + [ + 'tables' => [ 'revision' ], + 'fields' => array_merge( + $this->getDefaultQueryFields(), + $this->getCommentQueryFields(), + $this->getActorQueryFields(), + $this->getContentHandlerQueryFields() + ), + 'joins' => [], + ] + ]; + yield [ + [ 'page', 'user', 'text' ], + [ + 'tables' => [ 'revision', 'page', 'user', 'text' ], + 'fields' => array_merge( + $this->getDefaultQueryFields(), + $this->getCommentQueryFields(), + $this->getActorQueryFields(), + $this->getContentHandlerQueryFields(), + [ + 'page_namespace', + 'page_title', + 'page_id', + 'page_latest', + 'page_is_redirect', + 'page_len', + 'user_name', + 'old_text', + 'old_flags' + ] + ), + 'joins' => [ + 'page' => [ 'INNER JOIN', [ 'page_id = rev_page' ] ], + 'user' => [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ], + 'text' => [ 'INNER JOIN', [ 'rev_text_id=old_id' ] ], + ], + ] + ]; + } + +} diff --git a/tests/phpunit/includes/Storage/PreMcrSchemaOverride.php b/tests/phpunit/includes/Storage/PreMcrSchemaOverride.php new file mode 100644 index 0000000000..5d516e8258 --- /dev/null +++ b/tests/phpunit/includes/Storage/PreMcrSchemaOverride.php @@ -0,0 +1,54 @@ + [], + 'drop' => [], + 'create' => [], + 'alter' => [], + ]; + + if ( $this->hasMcrTables( $db ) ) { + $overrides['drop'] = [ 'slots', 'content', 'slot_roles', 'content_models', ]; + $overrides['scripts'][] = $this->getSqlPatchPath( $db, '/drop-mcr-tables', __DIR__ ); + } + + if ( !$this->hasPreMcrFields( $db ) ) { + $overrides['alter'][] = 'revision'; + $overrides['scripts'][] = $this->getSqlPatchPath( $db, '/create-pre-mcr-fields', __DIR__ ); + } + + return $overrides; + } + +} diff --git a/tests/phpunit/includes/Storage/RevisionStoreDbTest.php b/tests/phpunit/includes/Storage/RevisionStoreDbTestBase.php similarity index 86% rename from tests/phpunit/includes/Storage/RevisionStoreDbTest.php rename to tests/phpunit/includes/Storage/RevisionStoreDbTestBase.php index 2a9295628e..bdff4cd7d4 100644 --- a/tests/phpunit/includes/Storage/RevisionStoreDbTest.php +++ b/tests/phpunit/includes/Storage/RevisionStoreDbTestBase.php @@ -17,6 +17,7 @@ use MediaWiki\Storage\RevisionStore; use MediaWiki\Storage\SlotRecord; use MediaWiki\Storage\SqlBlobStore; use MediaWikiTestCase; +use PHPUnit_Framework_MockObject_MockObject; use Revision; use TestUserRegistry; use Title; @@ -31,8 +32,30 @@ use WikitextContent; /** * @group Database + * @group RevisionStore */ -class RevisionStoreDbTest extends MediaWikiTestCase { +abstract class RevisionStoreDbTestBase extends MediaWikiTestCase { + + /** + * @return int + */ + abstract protected function getMcrMigrationStage(); + + /** + * @return bool + */ + protected function getContentHandlerUseDB() { + return true; + } + + /** + * @return string[] + */ + abstract protected function getMcrTablesToReset(); + + public function needsDB() { + return true; + } public function setUp() { parent::setUp(); @@ -40,10 +63,24 @@ class RevisionStoreDbTest extends MediaWikiTestCase { $this->tablesUsed[] = 'page'; $this->tablesUsed[] = 'revision'; $this->tablesUsed[] = 'comment'; + + $this->tablesUsed += $this->getMcrTablesToReset(); + + $this->setMwGlobals( + 'wgMultiContentRevisionSchemaMigrationStage', + $this->getMcrMigrationStage() + ); + + $this->setMwGlobals( + 'wgContentHandlerUseDB', + $this->getContentHandlerUseDB() + ); + + $this->overrideMwServices(); } /** - * @return LoadBalancer + * @return LoadBalancer|PHPUnit_Framework_MockObject_MockObject */ private function getLoadBalancerMock( array $server ) { $lb = $this->getMockBuilder( LoadBalancer::class ) @@ -61,7 +98,7 @@ class RevisionStoreDbTest extends MediaWikiTestCase { } /** - * @return Database + * @return Database|PHPUnit_Framework_MockObject_MockObject */ private function getDatabaseMock( array $params ) { $db = $this->getMockBuilder( DatabaseSqlite::class ) @@ -128,6 +165,7 @@ class RevisionStoreDbTest extends MediaWikiTestCase { ); $db = $loadBalancer->getConnection( DB_REPLICA ); + /** @var SqlBlobStore $blobStore */ $blobStore = $this->getMockBuilder( SqlBlobStore::class ) ->disableOriginalConstructor() ->getMock(); @@ -241,10 +279,6 @@ class RevisionStoreDbTest extends MediaWikiTestCase { return $rev; } - private function getRandomCommentStoreComment() { - return CommentStoreComment::newUnsavedComment( __METHOD__ . '.' . rand( 0, 1000 ) ); - } - public function provideInsertRevisionOn_successes() { yield 'Bare minimum revision insertion' => [ Title::newFromText( 'UTPage' ), @@ -271,19 +305,43 @@ class RevisionStoreDbTest extends MediaWikiTestCase { ]; } + private function getRandomCommentStoreComment() { + return CommentStoreComment::newUnsavedComment( __METHOD__ . '.' . rand( 0, 1000 ) ); + } + /** * @dataProvider provideInsertRevisionOn_successes * @covers \MediaWiki\Storage\RevisionStore::insertRevisionOn */ - public function testInsertRevisionOn_successes( Title $title, array $revDetails = [] ) { + public function testInsertRevisionOn_successes( + Title $title, + array $revDetails = [] + ) { $rev = $this->getRevisionRecordFromDetailsArray( $title, $revDetails ); + $this->overrideMwServices(); $store = MediaWikiServices::getInstance()->getRevisionStore(); $return = $store->insertRevisionOn( $rev, wfGetDB( DB_MASTER ) ); $this->assertLinkTargetsEqual( $title, $return->getPageAsLinkTarget() ); $this->assertRevisionRecordsEqual( $rev, $return ); $this->assertRevisionCompleteness( $return ); + $this->assertRevisionExistsInDatabase( $return ); + } + + protected function assertRevisionExistsInDatabase( RevisionRecord $rev ) { + $this->assertSelect( + 'revision', [ 'count(*)' ], [ 'rev_id' => $rev->getId() ], [ [ '1' ] ] + ); + } + + /** + * @param SlotRecord $a + * @param SlotRecord $b + */ + protected function assertSameSlotContent( SlotRecord $a, SlotRecord $b ) { + // Assert that the same blob address has been used. + $this->assertSame( $a->getAddress(), $b->getAddress() ); } /** @@ -299,6 +357,7 @@ class RevisionStoreDbTest extends MediaWikiTestCase { 'user' => true, ]; + $this->overrideMwServices(); $store = MediaWikiServices::getInstance()->getRevisionStore(); // Insert the first revision @@ -314,16 +373,17 @@ class RevisionStoreDbTest extends MediaWikiTestCase { $this->assertLinkTargetsEqual( $title, $secondReturn->getPageAsLinkTarget() ); $this->assertRevisionRecordsEqual( $revTwo, $secondReturn ); - // Assert that the same blob address has been used. - $this->assertEquals( - $firstReturn->getSlot( 'main' )->getAddress(), - $secondReturn->getSlot( 'main' )->getAddress() - ); + $firstMainSlot = $firstReturn->getSlot( 'main' ); + $secondMainSlot = $secondReturn->getSlot( 'main' ); + + $this->assertSameSlotContent( $firstMainSlot, $secondMainSlot ); + // And that different revisions have been created. - $this->assertNotSame( - $firstReturn->getId(), - $secondReturn->getId() - ); + $this->assertNotSame( $firstReturn->getId(), $secondReturn->getId() ); + + // Make sure the slot rows reference the correct revision + $this->assertSame( $firstReturn->getId(), $firstMainSlot->getRevision() ); + $this->assertSame( $secondReturn->getId(), $secondMainSlot->getRevision() ); } public function provideInsertRevisionOn_failures() { @@ -382,7 +442,8 @@ class RevisionStoreDbTest extends MediaWikiTestCase { public function testInsertRevisionOn_failures( Title $title, array $revDetails = [], - Exception $exception ) { + Exception $exception + ) { $rev = $this->getRevisionRecordFromDetailsArray( $title, $revDetails ); $store = MediaWikiServices::getInstance()->getRevisionStore(); @@ -397,12 +458,12 @@ class RevisionStoreDbTest extends MediaWikiTestCase { public function provideNewNullRevision() { yield [ - Title::newFromText( 'UTPage' ), + Title::newFromText( 'UTPage_notAutoCreated' ), CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment1' ), true, ]; yield [ - Title::newFromText( 'UTPage' ), + Title::newFromText( 'UTPage_notAutoCreated' ), CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment2', [ 'a' => 1 ] ), false, ]; @@ -413,10 +474,22 @@ class RevisionStoreDbTest extends MediaWikiTestCase { * @covers \MediaWiki\Storage\RevisionStore::newNullRevision */ public function testNewNullRevision( Title $title, $comment, $minor ) { + $this->overrideMwServices(); + + $page = WikiPage::factory( $title ); + $status = $page->doEditContent( + new WikitextContent( __METHOD__ ), + __METHOD__, + 0, + false + ); + /** @var Revision $rev */ + $rev = $status->value['revision']; + $store = MediaWikiServices::getInstance()->getRevisionStore(); $user = TestUserRegistry::getMutableTestUser( __METHOD__ )->getUser(); - $parent = $store->getRevisionByTitle( $title ); + $parent = $store->getRevisionById( $rev->getId() ); $record = $store->newNullRevision( wfGetDB( DB_MASTER ), $title, @@ -437,7 +510,7 @@ class RevisionStoreDbTest extends MediaWikiTestCase { $this->assertTrue( $slot->isInherited(), 'isInherited' ); $this->assertSame( $parentSlot->getOrigin(), $slot->getOrigin(), 'getOrigin' ); - $this->assertSame( $parentSlot->getAddress(), $slot->getAddress(), 'getAddress' ); + $this->assertSameSlotContent( $parentSlot, $slot ); } /** @@ -596,14 +669,14 @@ class RevisionStoreDbTest extends MediaWikiTestCase { $this->assertSame( __METHOD__, $revRecord->getComment()->text ); } - private function revisionToRow( Revision $rev ) { + protected function revisionToRow( Revision $rev ) { $page = WikiPage::factory( $rev->getTitle() ); return (object)[ 'rev_id' => (string)$rev->getId(), 'rev_page' => (string)$rev->getPage(), 'rev_text_id' => (string)$rev->getTextId(), - 'rev_timestamp' => (string)$rev->getTimestamp(), + 'rev_timestamp' => $this->db->timestamp( $rev->getTimestamp() ), 'rev_user_text' => (string)$rev->getUserText(), 'rev_user' => (string)$rev->getUser(), 'rev_minor_edit' => $rev->isMinor() ? '1' : '0', @@ -659,9 +732,6 @@ class RevisionStoreDbTest extends MediaWikiTestCase { * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow_1_29 */ public function testNewRevisionFromRow_anonEdit() { - $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH ); - $this->overrideMwServices(); - $page = WikiPage::factory( Title::newFromText( 'UTPage' ) ); $text = __METHOD__ . 'a-ä'; /** @var Revision $rev */ @@ -710,9 +780,6 @@ class RevisionStoreDbTest extends MediaWikiTestCase { * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow_1_29 */ public function testNewRevisionFromRow_userEdit() { - $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH ); - $this->overrideMwServices(); - $page = WikiPage::factory( Title::newFromText( 'UTPage' ) ); $text = __METHOD__ . 'b-ä'; /** @var Revision $rev */ @@ -1279,4 +1346,155 @@ class RevisionStoreDbTest extends MediaWikiTestCase { $this->testNewMutableRevisionFromArray( $array ); } + protected function getDefaultQueryFields( $returnTextIdField = true ) { + $fields = [ + 'rev_id', + 'rev_page', + 'rev_timestamp', + 'rev_minor_edit', + 'rev_deleted', + 'rev_len', + 'rev_parent_id', + 'rev_sha1', + ]; + if ( $returnTextIdField ) { + $fields[] = 'rev_text_id'; + } + return $fields; + } + + protected function getCommentQueryFields() { + return [ + 'rev_comment_text' => 'rev_comment', + 'rev_comment_data' => 'NULL', + 'rev_comment_cid' => 'NULL', + ]; + } + + protected function getActorQueryFields() { + return [ + 'rev_user' => 'rev_user', + 'rev_user_text' => 'rev_user_text', + 'rev_actor' => 'NULL', + ]; + } + + protected function getContentHandlerQueryFields() { + return [ + 'rev_content_format', + 'rev_content_model', + ]; + } + + abstract public function provideGetQueryInfo(); + + /** + * @dataProvider provideGetQueryInfo + * @covers \MediaWiki\Storage\RevisionStore::getQueryInfo + */ + public function testGetQueryInfo( $options, $expected ) { + $store = MediaWikiServices::getInstance()->getRevisionStore(); + + $queryInfo = $store->getQueryInfo( $options ); + + $this->assertArrayEqualsIgnoringIntKeyOrder( + $expected['tables'], + $queryInfo['tables'] + ); + $this->assertArrayEqualsIgnoringIntKeyOrder( + $expected['fields'], + $queryInfo['fields'] + ); + $this->assertArrayEqualsIgnoringIntKeyOrder( + $expected['joins'], + $queryInfo['joins'] + ); + } + + protected function getDefaultArchiveFields( $returnTextFields = true ) { + $fields = [ + 'ar_id', + 'ar_page_id', + 'ar_namespace', + 'ar_title', + 'ar_rev_id', + 'ar_timestamp', + 'ar_minor_edit', + 'ar_deleted', + 'ar_len', + 'ar_parent_id', + 'ar_sha1', + ]; + if ( $returnTextFields ) { + $fields[] = 'ar_text_id'; + } + return $fields; + } + + abstract public function provideGetArchiveQueryInfo(); + + /** + * @dataProvider provideGetArchiveQueryInfo + * @covers \MediaWiki\Storage\RevisionStore::getArchiveQueryInfo + */ + public function testGetArchiveQueryInfo( $expected ) { + $store = MediaWikiServices::getInstance()->getRevisionStore(); + + $archiveQueryInfo = $store->getArchiveQueryInfo(); + + $this->assertArrayEqualsIgnoringIntKeyOrder( + $expected['tables'], + $archiveQueryInfo['tables'] + ); + + $this->assertArrayEqualsIgnoringIntKeyOrder( + $expected['fields'], + $archiveQueryInfo['fields'] + ); + + $this->assertArrayEqualsIgnoringIntKeyOrder( + $expected['joins'], + $archiveQueryInfo['joins'] + ); + } + + /** + * Assert that the two arrays passed are equal, ignoring the order of the values that integer + * keys. + * + * Note: Failures of this assertion can be slightly confusing as the arrays are actually + * split into a string key array and an int key array before assertions occur. + * + * @param array $expected + * @param array $actual + */ + private function assertArrayEqualsIgnoringIntKeyOrder( array $expected, array $actual ) { + $this->objectAssociativeSort( $expected ); + $this->objectAssociativeSort( $actual ); + + // Separate the int key values from the string key values so that assertion failures are + // easier to understand. + $expectedIntKeyValues = []; + $actualIntKeyValues = []; + + // Remove all int keys and re add them at the end after sorting by value + // This will result in all int keys being in the same order with same ints at the end of + // the array + foreach ( $expected as $key => $value ) { + if ( is_int( $key ) ) { + unset( $expected[$key] ); + $expectedIntKeyValues[] = $value; + } + } + foreach ( $actual as $key => $value ) { + if ( is_int( $key ) ) { + unset( $actual[$key] ); + $actualIntKeyValues[] = $value; + } + } + + $this->assertArrayEquals( $expected, $actual, false, true ); + $this->assertArrayEquals( $expectedIntKeyValues, $actualIntKeyValues, false, true ); + } + } diff --git a/tests/phpunit/includes/Storage/RevisionStoreTest.php b/tests/phpunit/includes/Storage/RevisionStoreTest.php index fed9a0c3d5..3749f294bf 100644 --- a/tests/phpunit/includes/Storage/RevisionStoreTest.php +++ b/tests/phpunit/includes/Storage/RevisionStoreTest.php @@ -247,87 +247,6 @@ class RevisionStoreTest extends MediaWikiTestCase { $this->assertEquals( $expected, $store->getQueryInfo( $options ) ); } - private function getDefaultArchiveFields() { - return [ - 'ar_id', - 'ar_page_id', - 'ar_namespace', - 'ar_title', - 'ar_rev_id', - 'ar_text_id', - 'ar_timestamp', - 'ar_minor_edit', - 'ar_deleted', - 'ar_len', - 'ar_parent_id', - 'ar_sha1', - ]; - } - - /** - * @covers \MediaWiki\Storage\RevisionStore::getArchiveQueryInfo - */ - public function testGetArchiveQueryInfo_contentHandlerDb() { - $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD ); - $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD ); - $this->overrideMwServices(); - $store = $this->getRevisionStore(); - $store->setContentHandlerUseDB( true ); - $this->assertEquals( - [ - 'tables' => [ - 'archive' - ], - 'fields' => array_merge( - $this->getDefaultArchiveFields(), - [ - 'ar_comment_text' => 'ar_comment', - 'ar_comment_data' => 'NULL', - 'ar_comment_cid' => 'NULL', - 'ar_user_text' => 'ar_user_text', - 'ar_user' => 'ar_user', - 'ar_actor' => 'NULL', - 'ar_content_format', - 'ar_content_model', - ] - ), - 'joins' => [], - ], - $store->getArchiveQueryInfo() - ); - } - - /** - * @covers \MediaWiki\Storage\RevisionStore::getArchiveQueryInfo - */ - public function testGetArchiveQueryInfo_noContentHandlerDb() { - $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD ); - $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD ); - $this->overrideMwServices(); - $store = $this->getRevisionStore(); - $store->setContentHandlerUseDB( false ); - $this->assertEquals( - [ - 'tables' => [ - 'archive' - ], - 'fields' => array_merge( - $this->getDefaultArchiveFields(), - [ - 'ar_comment_text' => 'ar_comment', - 'ar_comment_data' => 'NULL', - 'ar_comment_cid' => 'NULL', - 'ar_user_text' => 'ar_user_text', - 'ar_user' => 'ar_user', - 'ar_actor' => 'NULL', - ] - ), - 'joins' => [], - ], - $store->getArchiveQueryInfo() - ); - } - public function testGetTitle_successFromPageId() { $mockLoadBalancer = $this->getMockLoadBalancer(); // Title calls wfGetDB() so we have to set the main service diff --git a/tests/phpunit/includes/Storage/create-pre-mcr-fields.sql b/tests/phpunit/includes/Storage/create-pre-mcr-fields.sql new file mode 100644 index 0000000000..09deb4f2cd --- /dev/null +++ b/tests/phpunit/includes/Storage/create-pre-mcr-fields.sql @@ -0,0 +1,3 @@ +ALTER TABLE /*_*/revision ADD rev_text_id INTEGER DEFAULT 0; +ALTER TABLE /*_*/revision ADD rev_content_model VARBINARY(32) DEFAULT NULL; +ALTER TABLE /*_*/revision ADD rev_content_format VARBINARY(64) DEFAULT NULL; diff --git a/tests/phpunit/includes/Storage/drop-mcr-tables.sql b/tests/phpunit/includes/Storage/drop-mcr-tables.sql new file mode 100644 index 0000000000..bc89edc95e --- /dev/null +++ b/tests/phpunit/includes/Storage/drop-mcr-tables.sql @@ -0,0 +1,4 @@ +DROP TABLE /*_*/slots; +DROP TABLE /*_*/content; +DROP TABLE /*_*/content_models; +DROP TABLE /*_*/slot_roles; diff --git a/tests/phpunit/includes/page/WikiPageDbTestBase.php b/tests/phpunit/includes/page/WikiPageDbTestBase.php index 40c4e1ecd3..b150da18af 100644 --- a/tests/phpunit/includes/page/WikiPageDbTestBase.php +++ b/tests/phpunit/includes/page/WikiPageDbTestBase.php @@ -30,10 +30,29 @@ abstract class WikiPageDbTestBase extends MediaWikiLangTestCase { 'iwlinks' ] ); } + /** + * @return int + */ + abstract protected function getMcrMigrationStage(); + + /** + * @return string[] + */ + abstract protected function getMcrTablesToReset(); + protected function setUp() { parent::setUp(); + + $this->tablesUsed += $this->getMcrTablesToReset(); + $this->setMwGlobals( 'wgContentHandlerUseDB', $this->getContentHandlerUseDB() ); + $this->setMwGlobals( + 'wgMultiContentRevisionSchemaMigrationStage', + $this->getMcrMigrationStage() + ); $this->pagesToDelete = []; + + $this->overrideMwServices(); } protected function tearDown() { diff --git a/tests/phpunit/includes/page/WikiPageNoContentHandlerDbTest.php b/tests/phpunit/includes/page/WikiPageNoContentHandlerDbTest.php deleted file mode 100644 index a6ce185a12..0000000000 --- a/tests/phpunit/includes/page/WikiPageNoContentHandlerDbTest.php +++ /dev/null @@ -1,14 +0,0 @@ -