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
) ) {
139 [ 'rev_text_id' => 0 ],
140 [ 'rev_id' => $revision->getId() ]
144 if ( ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW
) ) {
147 [ 'content_address' => 'tt:0' ],
148 [ 'content_id' => $revision->getSlot( SlotRecord
::MAIN
)->getContentId() ]
153 protected function setUp() {
156 // Since we will restrict dumping by page ranges (to allow
157 // working tests, even if the db gets prepopulated by a base
158 // class), we have to assert, that the page id are consecutively
161 [ $this->pageId2
, $this->pageId3
, $this->pageId4
],
162 [ $this->pageId1 +
1, $this->pageId2 +
1, $this->pageId3 +
1 ],
163 "Page ids increasing without holes" );
166 function tearDown() {
169 if ( isset( $this->streamingLoadBalancer
) ) {
170 $this->streamingLoadBalancer
->closeAll();
175 * Returns a new database connection which is separate from the conenctions returned
176 * by the default LoadBalancer instance.
180 private function newStreamingDBConnection() {
181 // Create a *new* LoadBalancer, so no connections are shared
182 if ( !$this->streamingLoadBalancer
) {
183 $lbFactory = MediaWikiServices
::getInstance()->getDBLoadBalancerFactory();
185 $this->streamingLoadBalancer
= $lbFactory->newMainLB();
188 $db = $this->streamingLoadBalancer
->getConnection( DB_REPLICA
);
190 // Make sure the DB connection has the fake table clones and the fake table prefix
191 MediaWikiTestCase
::setupDatabaseWithTestPrefix( $db );
193 // Make sure the DB connection has all the test data
194 $this->copyTestData( $this->db
, $db );
201 * @param int $startId
206 private function newDumpBackup( $argv, $startId, $endId ) {
207 $dumper = new DumpBackup( $argv );
208 $dumper->startId
= $startId;
209 $dumper->endId
= $endId;
210 $dumper->reporting
= false;
212 // NOTE: The copyTestData() method used by newStreamingDBConnection()
213 // doesn't work with SQLite (T217607).
214 // But DatabaseSqlite doesn't support streaming anyway, so just skip that part.
215 if ( $this->db
->getType() === 'sqlite' ) {
216 $dumper->setDB( $this->db
);
218 $dumper->setDB( $this->newStreamingDBConnection() );
224 public function schemaVersionProvider() {
225 foreach ( XmlDumpWriter
::$supportedSchemas as $schemaVersion ) {
226 yield
[ $schemaVersion ];
231 * @dataProvider schemaVersionProvider
233 function testFullTextPlain( $schemaVersion ) {
234 // Preparing the dump
235 $fname = $this->getNewTempFile();
237 $dumper = $this->newDumpBackup(
238 [ '--full', '--quiet', '--output', 'file:' . $fname, '--schema-version', $schemaVersion ],
243 // Performing the dump. Suppress warnings, since we want to test
244 // accessing broken revision data (page 5).
245 $this->setMwGlobals( 'wgDevelopmentWarnings', false );
247 $this->setMwGlobals( 'wgDevelopmentWarnings', true );
249 // Checking the dumped data
250 $this->assertDumpSchema( $fname, $this->getXmlSchemaPath( $schemaVersion ) );
251 $asserter = $this->getDumpAsserter( $schemaVersion );
253 $asserter->assertDumpStart( $fname );
256 $asserter->assertPageStart(
259 $this->pageTitle1
->getPrefixedText()
261 $asserter->assertRevision(
263 "BackupDumperTestP1Summary1",
266 "0bolhl6ol7i6x0e7yq91gxgaan39j87",
267 "BackupDumperTestP1Text1"
269 $asserter->assertPageEnd();
272 $asserter->assertPageStart(
275 $this->pageTitle2
->getPrefixedText()
277 $asserter->assertRevision(
279 "BackupDumperTestP2Summary1",
282 "jprywrymfhysqllua29tj3sc7z39dl2",
283 "BackupDumperTestP2Text1"
285 $asserter->assertRevision(
287 "BackupDumperTestP2Summary2",
294 $asserter->assertRevision(
296 "BackupDumperTestP2Summary3",
299 "jfunqmh1ssfb8rs43r19w98k28gg56r",
300 "BackupDumperTestP2Text3",
303 $asserter->assertRevision(
305 "BackupDumperTestP2Summary4 extra",
308 "6o1ciaxa6pybnqprmungwofc4lv00wv",
309 "BackupDumperTestP2Text4 some additional Text",
312 $asserter->assertPageEnd();
315 // -> Page is marked deleted. Hence not visible
318 $asserter->assertPageStart(
320 $this->talk_namespace
,
321 $this->pageTitle4
->getPrefixedText()
323 $asserter->assertRevision(
325 "Talk BackupDumperTestP1 Summary1",
328 "nktofwzd0tl192k3zfepmlzxoax1lpe",
329 "Talk about BackupDumperTestP1 Text1",
331 CONTENT_MODEL_WIKITEXT
,
332 CONTENT_FORMAT_WIKITEXT
334 $asserter->assertPageEnd();
336 // Page 5 (broken revision data)
337 $asserter->assertPageStart(
340 $this->pageTitle5
->getPrefixedText()
342 $asserter->assertRevision(
344 "BackupDumperTestP5 Summary1",
347 "d2vipufvkfs9wfruwjfj8eschxw0fbl",
350 CONTENT_MODEL_WIKITEXT
,
351 CONTENT_FORMAT_WIKITEXT
353 $asserter->assertPageEnd();
355 $asserter->assertDumpEnd();
357 // FIXME: add multi-slot test case!
361 * @dataProvider schemaVersionProvider
363 function testFullStubPlain( $schemaVersion ) {
364 // Preparing the dump
365 $fname = $this->getNewTempFile();
367 $dumper = $this->newDumpBackup(
374 '--schema-version', $schemaVersion,
380 // Performing the dump. Suppress warnings, since we want to test
381 // accessing broken revision data (page 5).
382 $this->setMwGlobals( 'wgDevelopmentWarnings', false );
384 $this->setMwGlobals( 'wgDevelopmentWarnings', true );
386 // Checking the dumped data
387 $this->assertDumpSchema( $fname, $this->getXmlSchemaPath( $schemaVersion ) );
388 $asserter = $this->getDumpAsserter( $schemaVersion );
390 $asserter->assertDumpStart( $fname );
393 $asserter->assertPageStart(
396 $this->pageTitle1
->getPrefixedText()
398 $asserter->assertRevision(
400 "BackupDumperTestP1Summary1",
403 "0bolhl6ol7i6x0e7yq91gxgaan39j87"
405 $asserter->assertPageEnd();
408 $asserter->assertPageStart(
411 $this->pageTitle2
->getPrefixedText()
413 $asserter->assertRevision(
415 "BackupDumperTestP2Summary1",
418 "jprywrymfhysqllua29tj3sc7z39dl2"
420 $asserter->assertRevision(
422 "BackupDumperTestP2Summary2",
429 $asserter->assertRevision(
431 "BackupDumperTestP2Summary3",
434 "jfunqmh1ssfb8rs43r19w98k28gg56r",
438 $asserter->assertRevision(
440 "BackupDumperTestP2Summary4 extra",
443 "6o1ciaxa6pybnqprmungwofc4lv00wv",
447 $asserter->assertPageEnd();
450 // -> Page is marked deleted. Hence not visible
453 $asserter->assertPageStart(
455 $this->talk_namespace
,
456 $this->pageTitle4
->getPrefixedText()
458 $asserter->assertRevision(
460 "Talk BackupDumperTestP1 Summary1",
463 "nktofwzd0tl192k3zfepmlzxoax1lpe"
465 $asserter->assertPageEnd();
467 // Page 5 (broken revision data)
468 $asserter->assertPageStart(
471 $this->pageTitle5
->getPrefixedText()
473 $asserter->assertRevision(
475 "BackupDumperTestP5 Summary1",
478 "d2vipufvkfs9wfruwjfj8eschxw0fbl"
480 $asserter->assertPageEnd();
482 $asserter->assertDumpEnd();
486 * @dataProvider schemaVersionProvider
488 function testCurrentStubPlain( $schemaVersion ) {
489 // Preparing the dump
490 $fname = $this->getNewTempFile();
492 $dumper = $this->newDumpBackup(
493 [ '--output', 'file:' . $fname, '--schema-version', $schemaVersion ],
498 // Performing the dump
499 $dumper->dump( WikiExporter
::CURRENT
, WikiExporter
::STUB
);
501 // Checking the dumped data
502 $this->assertDumpSchema( $fname, $this->getXmlSchemaPath( $schemaVersion ) );
504 $asserter = $this->getDumpAsserter( $schemaVersion );
505 $asserter->assertDumpStart( $fname );
508 $asserter->assertPageStart(
511 $this->pageTitle1
->getPrefixedText()
513 $asserter->assertRevision(
515 "BackupDumperTestP1Summary1",
518 "0bolhl6ol7i6x0e7yq91gxgaan39j87"
520 $asserter->assertPageEnd();
523 $asserter->assertPageStart(
526 $this->pageTitle2
->getPrefixedText()
528 $asserter->assertRevision(
530 "BackupDumperTestP2Summary4 extra",
533 "6o1ciaxa6pybnqprmungwofc4lv00wv",
537 $asserter->assertPageEnd();
540 // -> Page is marked deleted. Hence not visible
543 $asserter->assertPageStart(
545 $this->talk_namespace
,
546 $this->pageTitle4
->getPrefixedText()
548 $asserter->assertRevision(
550 "Talk BackupDumperTestP1 Summary1",
553 "nktofwzd0tl192k3zfepmlzxoax1lpe"
555 $asserter->assertPageEnd();
557 $asserter->assertDumpEnd();
560 function testCurrentStubGzip() {
561 $this->checkHasGzip();
563 // Preparing the dump
564 $fname = $this->getNewTempFile();
566 $dumper = $this->newDumpBackup(
567 [ '--output', 'gzip:' . $fname ],
572 // Performing the dump
573 $dumper->dump( WikiExporter
::CURRENT
, WikiExporter
::STUB
);
575 // Checking the dumped data
576 $this->gunzip( $fname );
578 $asserter = $this->getDumpAsserter();
579 $asserter->assertDumpStart( $fname );
582 $asserter->assertPageStart(
585 $this->pageTitle1
->getPrefixedText()
587 $asserter->assertRevision(
589 "BackupDumperTestP1Summary1",
592 "0bolhl6ol7i6x0e7yq91gxgaan39j87"
594 $asserter->assertPageEnd();
597 $asserter->assertPageStart(
600 $this->pageTitle2
->getPrefixedText()
602 $asserter->assertRevision(
604 "BackupDumperTestP2Summary4 extra",
607 "6o1ciaxa6pybnqprmungwofc4lv00wv",
611 $asserter->assertPageEnd();
614 // -> Page is marked deleted. Hence not visible
617 $asserter->assertPageStart(
619 $this->talk_namespace
,
620 $this->pageTitle4
->getPrefixedText()
622 $asserter->assertRevision( $this->revId4_1
, "Talk BackupDumperTestP1 Summary1",
623 $this->textId4_1
, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" );
624 $asserter->assertPageEnd();
626 $asserter->assertDumpEnd();
630 * xmldumps-backup typically performs a single dump that that writes
632 * - gzipped stubs of everything (meta-history)
633 * - gzipped stubs of latest revisions of all pages (meta-current)
634 * - gzipped stubs of latest revisions of all pages of namespage 0
637 * We reproduce such a setup with our mini fixture, although we omit
638 * chunks, and all the other gimmicks of xmldumps-backup.
640 * @dataProvider schemaVersionProvider
642 function testXmlDumpsBackupUseCase( $schemaVersion ) {
643 $this->checkHasGzip();
645 $fnameMetaHistory = $this->getNewTempFile();
646 $fnameMetaCurrent = $this->getNewTempFile();
647 $fnameArticles = $this->getNewTempFile();
649 $dumper = $this->newDumpBackup(
650 [ "--full", "--stub", "--output=gzip:" . $fnameMetaHistory,
651 "--output=gzip:" . $fnameMetaCurrent, "--filter=latest",
652 "--output=gzip:" . $fnameArticles, "--filter=latest",
653 "--filter=notalk", "--filter=namespace:!NS_USER",
654 "--reporting=1000", '--schema-version', $schemaVersion
659 $dumper->reporting
= true;
661 // xmldumps-backup uses reporting. We will not check the exact reported
662 // message, as they are dependent on the processing power of the used
663 // computer. We only check that reporting does not crash the dumping
664 // and that something is reported
665 $dumper->stderr
= fopen( 'php://output', 'a' );
666 if ( $dumper->stderr
=== false ) {
667 $this->fail( "Could not open stream for stderr" );
670 // Performing the dump
671 $dumper->dump( WikiExporter
::FULL
, WikiExporter
::STUB
);
673 $this->assertTrue( fclose( $dumper->stderr
), "Closing stderr handle" );
675 // Checking meta-history -------------------------------------------------
677 $this->gunzip( $fnameMetaHistory );
678 $this->assertDumpSchema( $fnameMetaHistory, $this->getXmlSchemaPath( $schemaVersion ) );
680 $asserter = $this->getDumpAsserter( $schemaVersion );
681 $asserter->assertDumpStart( $fnameMetaHistory );
684 $asserter->assertPageStart(
687 $this->pageTitle1
->getPrefixedText()
689 $asserter->assertRevision(
691 "BackupDumperTestP1Summary1",
694 "0bolhl6ol7i6x0e7yq91gxgaan39j87"
696 $asserter->assertPageEnd();
699 $asserter->assertPageStart(
702 $this->pageTitle2
->getPrefixedText()
704 $asserter->assertRevision(
706 "BackupDumperTestP2Summary1",
709 "jprywrymfhysqllua29tj3sc7z39dl2"
711 $asserter->assertRevision(
713 "BackupDumperTestP2Summary2",
720 $asserter->assertRevision(
722 "BackupDumperTestP2Summary3",
725 "jfunqmh1ssfb8rs43r19w98k28gg56r",
729 $asserter->assertRevision(
731 "BackupDumperTestP2Summary4 extra",
734 "6o1ciaxa6pybnqprmungwofc4lv00wv",
738 $asserter->assertPageEnd();
741 // -> Page is marked deleted. Hence not visible
744 $asserter->assertPageStart(
746 $this->talk_namespace
,
747 $this->pageTitle4
->getPrefixedText( $schemaVersion )
749 $asserter->assertRevision(
751 "Talk BackupDumperTestP1 Summary1",
754 "nktofwzd0tl192k3zfepmlzxoax1lpe"
756 $asserter->assertPageEnd();
758 $asserter->assertDumpEnd();
760 // Checking meta-current -------------------------------------------------
762 $this->gunzip( $fnameMetaCurrent );
763 $this->assertDumpSchema( $fnameMetaCurrent, $this->getXmlSchemaPath( $schemaVersion ) );
765 $asserter = $this->getDumpAsserter( $schemaVersion );
766 $asserter->assertDumpStart( $fnameMetaCurrent );
769 $asserter->assertPageStart(
772 $this->pageTitle1
->getPrefixedText()
774 $asserter->assertRevision(
776 "BackupDumperTestP1Summary1",
779 "0bolhl6ol7i6x0e7yq91gxgaan39j87"
781 $asserter->assertPageEnd();
784 $asserter->assertPageStart(
787 $this->pageTitle2
->getPrefixedText()
789 $asserter->assertRevision(
791 "BackupDumperTestP2Summary4 extra",
794 "6o1ciaxa6pybnqprmungwofc4lv00wv",
798 $asserter->assertPageEnd();
801 // -> Page is marked deleted. Hence not visible
804 $asserter->assertPageStart(
806 $this->talk_namespace
,
807 $this->pageTitle4
->getPrefixedText()
809 $asserter->assertRevision(
811 "Talk BackupDumperTestP1 Summary1",
814 "nktofwzd0tl192k3zfepmlzxoax1lpe"
816 $asserter->assertPageEnd();
818 $asserter->assertDumpEnd();
820 // Checking articles -------------------------------------------------
822 $this->gunzip( $fnameArticles );
823 $this->assertDumpSchema( $fnameArticles, $this->getXmlSchemaPath( $schemaVersion ) );
825 $asserter = $this->getDumpAsserter( $schemaVersion );
826 $asserter->assertDumpStart( $fnameArticles );
829 $asserter->assertPageStart(
832 $this->pageTitle1
->getPrefixedText()
834 $asserter->assertRevision(
836 "BackupDumperTestP1Summary1",
839 "0bolhl6ol7i6x0e7yq91gxgaan39j87"
841 $asserter->assertPageEnd();
844 $asserter->assertPageStart(
847 $this->pageTitle2
->getPrefixedText()
849 $asserter->assertRevision(
851 "BackupDumperTestP2Summary4 extra",
854 "6o1ciaxa6pybnqprmungwofc4lv00wv",
858 $asserter->assertPageEnd();
861 // -> Page is marked deleted. Hence not visible
864 // -> Page is not in $this->namespace. Hence not visible
866 $asserter->assertDumpEnd();
868 $this->expectETAOutput();