3 namespace MediaWiki\Tests\Maintenance
;
7 use MediaWiki\MediaWikiServices
;
8 use MediaWiki\Revision\RevisionRecord
;
9 use MediaWiki\Revision\SlotRecord
;
10 use MediaWikiTestCase
;
16 use Wikimedia\Rdbms\IDatabase
;
17 use Wikimedia\Rdbms\LoadBalancer
;
22 * Tests for page dumps of BackupDumper
26 * @covers BackupDumper
28 class BackupDumperPageTest
extends DumpTestCase
{
30 // We'll add several pages, revision and texts. The following variables hold the
32 private $pageId1, $pageId2, $pageId3, $pageId4, $pageId5;
33 private $pageTitle1, $pageTitle2, $pageTitle3, $pageTitle4, $pageTitle5;
34 private $revId1_1, $textId1_1;
35 private $revId2_1, $textId2_1, $revId2_2, $textId2_2;
36 private $revId2_3, $textId2_3, $revId2_4, $textId2_4;
37 private $revId3_1, $textId3_1, $revId3_2, $textId3_2;
38 private $revId4_1, $textId4_1;
39 private $revId5_1, $textId5_1;
40 private $namespace, $talk_namespace;
43 * @var LoadBalancer|null
45 private $streamingLoadBalancer = null;
47 function addDBData() {
48 // be sure, titles created here using english namespace names
49 $this->setContentLang( 'en' );
51 $this->tablesUsed
[] = 'page';
52 $this->tablesUsed
[] = 'revision';
53 $this->tablesUsed
[] = 'ip_changes';
54 $this->tablesUsed
[] = 'text';
57 $this->namespace = $this->getDefaultWikitextNS();
58 $this->talk_namespace
= NS_TALK
;
60 if ( $this->namespace === $this->talk_namespace
) {
61 // @todo work around this.
62 throw new MWException( "The default wikitext namespace is the talk namespace. "
63 . " We can't currently deal with that." );
66 $this->pageTitle1
= Title
::newFromText( 'BackupDumperTestP1', $this->namespace );
67 $page = WikiPage
::factory( $this->pageTitle1
);
68 list( $this->revId1_1
, $this->textId1_1
) = $this->addRevision( $page,
69 "BackupDumperTestP1Text1", "BackupDumperTestP1Summary1" );
70 $this->pageId1
= $page->getId();
72 $this->pageTitle2
= Title
::newFromText( 'BackupDumperTestP2', $this->namespace );
73 $page = WikiPage
::factory( $this->pageTitle2
);
74 list( $this->revId2_1
, $this->textId2_1
) = $this->addRevision( $page,
75 "BackupDumperTestP2Text1", "BackupDumperTestP2Summary1" );
76 list( $this->revId2_2
, $this->textId2_2
) = $this->addRevision( $page,
77 "BackupDumperTestP2Text2", "BackupDumperTestP2Summary2" );
78 list( $this->revId2_3
, $this->textId2_3
) = $this->addRevision( $page,
79 "BackupDumperTestP2Text3", "BackupDumperTestP2Summary3" );
80 list( $this->revId2_4
, $this->textId2_4
) = $this->addRevision( $page,
81 "BackupDumperTestP2Text4 some additional Text ",
82 "BackupDumperTestP2Summary4 extra " );
83 $this->pageId2
= $page->getId();
85 $revDel = RevisionDeleter
::createList(
87 RequestContext
::getMain(),
91 $revDel->setVisibility( [
92 'value' => [ RevisionRecord
::DELETED_TEXT
=> 1 ],
93 'comment' => 'testing!'
96 $this->pageTitle3
= Title
::newFromText( 'BackupDumperTestP3', $this->namespace );
97 $page = WikiPage
::factory( $this->pageTitle3
);
98 list( $this->revId3_1
, $this->textId3_1
) = $this->addRevision( $page,
99 "BackupDumperTestP3Text1", "BackupDumperTestP2Summary1" );
100 list( $this->revId3_2
, $this->textId3_2
) = $this->addRevision( $page,
101 "BackupDumperTestP3Text2", "BackupDumperTestP2Summary2" );
102 $this->pageId3
= $page->getId();
103 $page->doDeleteArticle( "Testing ;)" );
105 $this->pageTitle4
= Title
::newFromText( 'BackupDumperTestP1', $this->talk_namespace
);
106 $page = WikiPage
::factory( $this->pageTitle4
);
107 list( $this->revId4_1
, $this->textId4_1
) = $this->addRevision( $page,
108 "Talk about BackupDumperTestP1 Text1",
109 "Talk BackupDumperTestP1 Summary1" );
110 $this->pageId4
= $page->getId();
112 $this->pageTitle5
= Title
::newFromText( 'BackupDumperTestP5' );
113 $page = WikiPage
::factory( $this->pageTitle5
);
114 list( $this->revId5_1
, $this->textId5_1
) = $this->addRevision( $page,
115 "BackupDumperTestP5 Text1",
116 "BackupDumperTestP5 Summary1" );
117 $this->pageId5
= $page->getId();
119 $this->corruptRevisionData( $page->getRevision()->getRevisionRecord() );
120 } catch ( Exception
$e ) {
121 // We'd love to pass $e directly. However, ... see
122 // documentation of exceptionFromAddDBData in
124 $this->exceptionFromAddDBData
= $e;
129 * Corrupt the information about the given revision in the database.
131 * @param RevisionRecord $revision
133 private function corruptRevisionData( RevisionRecord
$revision ) {
134 global $wgMultiContentRevisionSchemaMigrationStage;
136 if ( ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD
) ) {
144 [ 'rev_id' => $revision->getId() ]
148 if ( ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW
) ) {
152 'content_address' => 'tt:0',
153 'content_sha1' => '',
154 'content_size' => '0',
156 [ 'content_id' => $revision->getSlot( SlotRecord
::MAIN
)->getContentId() ]
161 protected function setUp() {
164 // Since we will restrict dumping by page ranges (to allow
165 // working tests, even if the db gets prepopulated by a base
166 // class), we have to assert, that the page id are consecutively
169 [ $this->pageId2
, $this->pageId3
, $this->pageId4
],
170 [ $this->pageId1 +
1, $this->pageId2 +
1, $this->pageId3 +
1 ],
171 "Page ids increasing without holes" );
174 function tearDown() {
177 if ( isset( $this->streamingLoadBalancer
) ) {
178 $this->streamingLoadBalancer
->closeAll();
183 * Returns a new database connection which is separate from the conenctions returned
184 * by the default LoadBalancer instance.
188 private function newStreamingDBConnection() {
189 // Create a *new* LoadBalancer, so no connections are shared
190 if ( !$this->streamingLoadBalancer
) {
191 $lbFactory = MediaWikiServices
::getInstance()->getDBLoadBalancerFactory();
193 $this->streamingLoadBalancer
= $lbFactory->newMainLB();
196 $db = $this->streamingLoadBalancer
->getConnection( DB_REPLICA
);
198 // Make sure the DB connection has the fake table clones and the fake table prefix
199 MediaWikiTestCase
::setupDatabaseWithTestPrefix( $db );
201 // Make sure the DB connection has all the test data
202 $this->copyTestData( $this->db
, $db );
209 * @param int $startId
214 private function newDumpBackup( $argv, $startId, $endId ) {
215 $dumper = new DumpBackup( $argv );
216 $dumper->startId
= $startId;
217 $dumper->endId
= $endId;
218 $dumper->reporting
= false;
220 // NOTE: The copyTestData() method used by newStreamingDBConnection()
221 // doesn't work with SQLite (T217607).
222 // But DatabaseSqlite doesn't support streaming anyway, so just skip that part.
223 if ( $this->db
->getType() === 'sqlite' ) {
224 $dumper->setDB( $this->db
);
226 $dumper->setDB( $this->newStreamingDBConnection() );
232 public function schemaVersionProvider() {
233 foreach ( XmlDumpWriter
::$supportedSchemas as $schemaVersion ) {
234 yield
[ $schemaVersion ];
239 * @dataProvider schemaVersionProvider
241 function testFullTextPlain( $schemaVersion ) {
242 // Preparing the dump
243 $fname = $this->getNewTempFile();
245 $dumper = $this->newDumpBackup(
246 [ '--full', '--quiet', '--output', 'file:' . $fname, '--schema-version', $schemaVersion ],
251 // Performing the dump. Suppress warnings, since we want to test
252 // accessing broken revision data (page 5).
253 $this->setMwGlobals( 'wgDevelopmentWarnings', false );
255 $this->setMwGlobals( 'wgDevelopmentWarnings', true );
257 // Checking the dumped data
258 $this->assertDumpSchema( $fname, $this->getXmlSchemaPath( $schemaVersion ) );
259 $asserter = $this->getDumpAsserter( $schemaVersion );
261 $asserter->assertDumpStart( $fname );
264 $asserter->assertPageStart(
267 $this->pageTitle1
->getPrefixedText()
269 $asserter->assertRevision(
271 "BackupDumperTestP1Summary1",
274 "0bolhl6ol7i6x0e7yq91gxgaan39j87",
275 "BackupDumperTestP1Text1"
277 $asserter->assertPageEnd();
280 $asserter->assertPageStart(
283 $this->pageTitle2
->getPrefixedText()
285 $asserter->assertRevision(
287 "BackupDumperTestP2Summary1",
290 "jprywrymfhysqllua29tj3sc7z39dl2",
291 "BackupDumperTestP2Text1"
293 $asserter->assertRevision(
295 "BackupDumperTestP2Summary2",
302 $asserter->assertRevision(
304 "BackupDumperTestP2Summary3",
307 "jfunqmh1ssfb8rs43r19w98k28gg56r",
308 "BackupDumperTestP2Text3",
311 $asserter->assertRevision(
313 "BackupDumperTestP2Summary4 extra",
316 "6o1ciaxa6pybnqprmungwofc4lv00wv",
317 "BackupDumperTestP2Text4 some additional Text",
320 $asserter->assertPageEnd();
323 // -> Page is marked deleted. Hence not visible
326 $asserter->assertPageStart(
328 $this->talk_namespace
,
329 $this->pageTitle4
->getPrefixedText()
331 $asserter->assertRevision(
333 "Talk BackupDumperTestP1 Summary1",
336 "nktofwzd0tl192k3zfepmlzxoax1lpe",
337 "Talk about BackupDumperTestP1 Text1",
339 CONTENT_MODEL_WIKITEXT
,
340 CONTENT_FORMAT_WIKITEXT
,
343 $asserter->assertPageEnd();
345 // Page 5 (broken revision data)
346 $asserter->assertPageStart(
349 $this->pageTitle5
->getPrefixedText()
351 $asserter->assertRevision(
353 "BackupDumperTestP5 Summary1",
359 CONTENT_MODEL_WIKITEXT
,
360 CONTENT_FORMAT_WIKITEXT
,
363 $asserter->assertPageEnd();
365 $asserter->assertDumpEnd();
367 // FIXME: add multi-slot test case!
371 * @dataProvider schemaVersionProvider
373 function testFullStubPlain( $schemaVersion ) {
374 // Preparing the dump
375 $fname = $this->getNewTempFile();
377 $dumper = $this->newDumpBackup(
384 '--schema-version', $schemaVersion,
390 // Performing the dump. Suppress warnings, since we want to test
391 // accessing broken revision data (page 5).
392 $this->setMwGlobals( 'wgDevelopmentWarnings', false );
394 $this->setMwGlobals( 'wgDevelopmentWarnings', true );
396 // Checking the dumped data
397 $this->assertDumpSchema( $fname, $this->getXmlSchemaPath( $schemaVersion ) );
398 $asserter = $this->getDumpAsserter( $schemaVersion );
400 $asserter->assertDumpStart( $fname );
403 $asserter->assertPageStart(
406 $this->pageTitle1
->getPrefixedText()
408 $asserter->assertRevision(
410 "BackupDumperTestP1Summary1",
413 "0bolhl6ol7i6x0e7yq91gxgaan39j87"
415 $asserter->assertPageEnd();
418 $asserter->assertPageStart(
421 $this->pageTitle2
->getPrefixedText()
423 $asserter->assertRevision(
425 "BackupDumperTestP2Summary1",
428 "jprywrymfhysqllua29tj3sc7z39dl2"
430 $asserter->assertRevision(
432 "BackupDumperTestP2Summary2",
439 $asserter->assertRevision(
441 "BackupDumperTestP2Summary3",
444 "jfunqmh1ssfb8rs43r19w98k28gg56r",
448 $asserter->assertRevision(
450 "BackupDumperTestP2Summary4 extra",
453 "6o1ciaxa6pybnqprmungwofc4lv00wv",
457 $asserter->assertPageEnd();
460 // -> Page is marked deleted. Hence not visible
463 $asserter->assertPageStart(
465 $this->talk_namespace
,
466 $this->pageTitle4
->getPrefixedText()
468 $asserter->assertRevision(
470 "Talk BackupDumperTestP1 Summary1",
473 "nktofwzd0tl192k3zfepmlzxoax1lpe"
475 $asserter->assertPageEnd();
477 // Page 5 (broken revision data)
478 $asserter->assertPageStart(
481 $this->pageTitle5
->getPrefixedText()
483 $asserter->assertRevision(
485 "BackupDumperTestP5 Summary1",
490 $asserter->assertPageEnd();
492 $asserter->assertDumpEnd();
496 * @dataProvider schemaVersionProvider
498 function testCurrentStubPlain( $schemaVersion ) {
499 // Preparing the dump
500 $fname = $this->getNewTempFile();
502 $dumper = $this->newDumpBackup(
503 [ '--output', 'file:' . $fname, '--schema-version', $schemaVersion ],
508 // Performing the dump
509 $dumper->dump( WikiExporter
::CURRENT
, WikiExporter
::STUB
);
511 // Checking the dumped data
512 $this->assertDumpSchema( $fname, $this->getXmlSchemaPath( $schemaVersion ) );
514 $asserter = $this->getDumpAsserter( $schemaVersion );
515 $asserter->assertDumpStart( $fname );
518 $asserter->assertPageStart(
521 $this->pageTitle1
->getPrefixedText()
523 $asserter->assertRevision(
525 "BackupDumperTestP1Summary1",
528 "0bolhl6ol7i6x0e7yq91gxgaan39j87"
530 $asserter->assertPageEnd();
533 $asserter->assertPageStart(
536 $this->pageTitle2
->getPrefixedText()
538 $asserter->assertRevision(
540 "BackupDumperTestP2Summary4 extra",
543 "6o1ciaxa6pybnqprmungwofc4lv00wv",
547 $asserter->assertPageEnd();
550 // -> Page is marked deleted. Hence not visible
553 $asserter->assertPageStart(
555 $this->talk_namespace
,
556 $this->pageTitle4
->getPrefixedText()
558 $asserter->assertRevision(
560 "Talk BackupDumperTestP1 Summary1",
563 "nktofwzd0tl192k3zfepmlzxoax1lpe"
565 $asserter->assertPageEnd();
567 $asserter->assertDumpEnd();
570 function testCurrentStubGzip() {
571 $this->checkHasGzip();
573 // Preparing the dump
574 $fname = $this->getNewTempFile();
576 $dumper = $this->newDumpBackup(
577 [ '--output', 'gzip:' . $fname ],
582 // Performing the dump
583 $dumper->dump( WikiExporter
::CURRENT
, WikiExporter
::STUB
);
585 // Checking the dumped data
586 $this->gunzip( $fname );
588 $asserter = $this->getDumpAsserter();
589 $asserter->assertDumpStart( $fname );
592 $asserter->assertPageStart(
595 $this->pageTitle1
->getPrefixedText()
597 $asserter->assertRevision(
599 "BackupDumperTestP1Summary1",
602 "0bolhl6ol7i6x0e7yq91gxgaan39j87"
604 $asserter->assertPageEnd();
607 $asserter->assertPageStart(
610 $this->pageTitle2
->getPrefixedText()
612 $asserter->assertRevision(
614 "BackupDumperTestP2Summary4 extra",
617 "6o1ciaxa6pybnqprmungwofc4lv00wv",
621 $asserter->assertPageEnd();
624 // -> Page is marked deleted. Hence not visible
627 $asserter->assertPageStart(
629 $this->talk_namespace
,
630 $this->pageTitle4
->getPrefixedText()
632 $asserter->assertRevision( $this->revId4_1
, "Talk BackupDumperTestP1 Summary1",
633 $this->textId4_1
, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" );
634 $asserter->assertPageEnd();
636 $asserter->assertDumpEnd();
640 * xmldumps-backup typically performs a single dump that that writes
642 * - gzipped stubs of everything (meta-history)
643 * - gzipped stubs of latest revisions of all pages (meta-current)
644 * - gzipped stubs of latest revisions of all pages of namespage 0
647 * We reproduce such a setup with our mini fixture, although we omit
648 * chunks, and all the other gimmicks of xmldumps-backup.
650 * @dataProvider schemaVersionProvider
652 function testXmlDumpsBackupUseCase( $schemaVersion ) {
653 $this->checkHasGzip();
655 $fnameMetaHistory = $this->getNewTempFile();
656 $fnameMetaCurrent = $this->getNewTempFile();
657 $fnameArticles = $this->getNewTempFile();
659 $dumper = $this->newDumpBackup(
660 [ "--full", "--stub", "--output=gzip:" . $fnameMetaHistory,
661 "--output=gzip:" . $fnameMetaCurrent, "--filter=latest",
662 "--output=gzip:" . $fnameArticles, "--filter=latest",
663 "--filter=notalk", "--filter=namespace:!NS_USER",
664 "--reporting=1000", '--schema-version', $schemaVersion
669 $dumper->reporting
= true;
671 // xmldumps-backup uses reporting. We will not check the exact reported
672 // message, as they are dependent on the processing power of the used
673 // computer. We only check that reporting does not crash the dumping
674 // and that something is reported
675 $dumper->stderr
= fopen( 'php://output', 'a' );
676 if ( $dumper->stderr
=== false ) {
677 $this->fail( "Could not open stream for stderr" );
680 // Performing the dump
681 $dumper->dump( WikiExporter
::FULL
, WikiExporter
::STUB
);
683 $this->assertTrue( fclose( $dumper->stderr
), "Closing stderr handle" );
685 // Checking meta-history -------------------------------------------------
687 $this->gunzip( $fnameMetaHistory );
688 $this->assertDumpSchema( $fnameMetaHistory, $this->getXmlSchemaPath( $schemaVersion ) );
690 $asserter = $this->getDumpAsserter( $schemaVersion );
691 $asserter->assertDumpStart( $fnameMetaHistory );
694 $asserter->assertPageStart(
697 $this->pageTitle1
->getPrefixedText()
699 $asserter->assertRevision(
701 "BackupDumperTestP1Summary1",
704 "0bolhl6ol7i6x0e7yq91gxgaan39j87"
706 $asserter->assertPageEnd();
709 $asserter->assertPageStart(
712 $this->pageTitle2
->getPrefixedText()
714 $asserter->assertRevision(
716 "BackupDumperTestP2Summary1",
719 "jprywrymfhysqllua29tj3sc7z39dl2"
721 $asserter->assertRevision(
723 "BackupDumperTestP2Summary2",
730 $asserter->assertRevision(
732 "BackupDumperTestP2Summary3",
735 "jfunqmh1ssfb8rs43r19w98k28gg56r",
739 $asserter->assertRevision(
741 "BackupDumperTestP2Summary4 extra",
744 "6o1ciaxa6pybnqprmungwofc4lv00wv",
748 $asserter->assertPageEnd();
751 // -> Page is marked deleted. Hence not visible
754 $asserter->assertPageStart(
756 $this->talk_namespace
,
757 $this->pageTitle4
->getPrefixedText( $schemaVersion )
759 $asserter->assertRevision(
761 "Talk BackupDumperTestP1 Summary1",
764 "nktofwzd0tl192k3zfepmlzxoax1lpe"
766 $asserter->assertPageEnd();
768 $asserter->assertDumpEnd();
770 // Checking meta-current -------------------------------------------------
772 $this->gunzip( $fnameMetaCurrent );
773 $this->assertDumpSchema( $fnameMetaCurrent, $this->getXmlSchemaPath( $schemaVersion ) );
775 $asserter = $this->getDumpAsserter( $schemaVersion );
776 $asserter->assertDumpStart( $fnameMetaCurrent );
779 $asserter->assertPageStart(
782 $this->pageTitle1
->getPrefixedText()
784 $asserter->assertRevision(
786 "BackupDumperTestP1Summary1",
789 "0bolhl6ol7i6x0e7yq91gxgaan39j87"
791 $asserter->assertPageEnd();
794 $asserter->assertPageStart(
797 $this->pageTitle2
->getPrefixedText()
799 $asserter->assertRevision(
801 "BackupDumperTestP2Summary4 extra",
804 "6o1ciaxa6pybnqprmungwofc4lv00wv",
808 $asserter->assertPageEnd();
811 // -> Page is marked deleted. Hence not visible
814 $asserter->assertPageStart(
816 $this->talk_namespace
,
817 $this->pageTitle4
->getPrefixedText()
819 $asserter->assertRevision(
821 "Talk BackupDumperTestP1 Summary1",
824 "nktofwzd0tl192k3zfepmlzxoax1lpe"
826 $asserter->assertPageEnd();
828 $asserter->assertDumpEnd();
830 // Checking articles -------------------------------------------------
832 $this->gunzip( $fnameArticles );
833 $this->assertDumpSchema( $fnameArticles, $this->getXmlSchemaPath( $schemaVersion ) );
835 $asserter = $this->getDumpAsserter( $schemaVersion );
836 $asserter->assertDumpStart( $fnameArticles );
839 $asserter->assertPageStart(
842 $this->pageTitle1
->getPrefixedText()
844 $asserter->assertRevision(
846 "BackupDumperTestP1Summary1",
849 "0bolhl6ol7i6x0e7yq91gxgaan39j87"
851 $asserter->assertPageEnd();
854 $asserter->assertPageStart(
857 $this->pageTitle2
->getPrefixedText()
859 $asserter->assertRevision(
861 "BackupDumperTestP2Summary4 extra",
864 "6o1ciaxa6pybnqprmungwofc4lv00wv",
868 $asserter->assertPageEnd();
871 // -> Page is marked deleted. Hence not visible
874 // -> Page is not in $this->namespace. Hence not visible
876 $asserter->assertDumpEnd();
878 $this->expectETAOutput();