Merge "storage: Fix typo in NameTableStore::purgeWANCache comments"
[lhc/web/wiklou.git] / tests / phpunit / maintenance / backup_PageTest.php
1 <?php
2
3 namespace MediaWiki\Tests\Maintenance;
4
5 use DumpBackup;
6 use MediaWiki\MediaWikiServices;
7 use MediaWikiTestCase;
8 use MWException;
9 use Title;
10 use WikiExporter;
11 use Wikimedia\Rdbms\IDatabase;
12 use Wikimedia\Rdbms\LoadBalancer;
13 use WikiPage;
14
15 /**
16 * Tests for page dumps of BackupDumper
17 *
18 * @group Database
19 * @group Dump
20 * @covers BackupDumper
21 */
22 class BackupDumperPageTest extends DumpTestCase {
23
24 // We'll add several pages, revision and texts. The following variables hold the
25 // corresponding ids.
26 private $pageId1, $pageId2, $pageId3, $pageId4;
27 private $pageTitle1, $pageTitle2, $pageTitle3, $pageTitle4;
28 private $revId1_1, $textId1_1;
29 private $revId2_1, $textId2_1, $revId2_2, $textId2_2;
30 private $revId2_3, $textId2_3, $revId2_4, $textId2_4;
31 private $revId3_1, $textId3_1, $revId3_2, $textId3_2;
32 private $revId4_1, $textId4_1;
33 private $namespace, $talk_namespace;
34
35 /**
36 * @var LoadBalancer|null
37 */
38 private $streamingLoadBalancer = null;
39
40 function addDBData() {
41 // be sure, titles created here using english namespace names
42 $this->setContentLang( 'en' );
43
44 $this->tablesUsed[] = 'page';
45 $this->tablesUsed[] = 'revision';
46 $this->tablesUsed[] = 'ip_changes';
47 $this->tablesUsed[] = 'text';
48
49 try {
50 $this->namespace = $this->getDefaultWikitextNS();
51 $this->talk_namespace = NS_TALK;
52
53 if ( $this->namespace === $this->talk_namespace ) {
54 // @todo work around this.
55 throw new MWException( "The default wikitext namespace is the talk namespace. "
56 . " We can't currently deal with that." );
57 }
58
59 $this->pageTitle1 = Title::newFromText( 'BackupDumperTestP1', $this->namespace );
60 $page = WikiPage::factory( $this->pageTitle1 );
61 list( $this->revId1_1, $this->textId1_1 ) = $this->addRevision( $page,
62 "BackupDumperTestP1Text1", "BackupDumperTestP1Summary1" );
63 $this->pageId1 = $page->getId();
64
65 $this->pageTitle2 = Title::newFromText( 'BackupDumperTestP2', $this->namespace );
66 $page = WikiPage::factory( $this->pageTitle2 );
67 list( $this->revId2_1, $this->textId2_1 ) = $this->addRevision( $page,
68 "BackupDumperTestP2Text1", "BackupDumperTestP2Summary1" );
69 list( $this->revId2_2, $this->textId2_2 ) = $this->addRevision( $page,
70 "BackupDumperTestP2Text2", "BackupDumperTestP2Summary2" );
71 list( $this->revId2_3, $this->textId2_3 ) = $this->addRevision( $page,
72 "BackupDumperTestP2Text3", "BackupDumperTestP2Summary3" );
73 list( $this->revId2_4, $this->textId2_4 ) = $this->addRevision( $page,
74 "BackupDumperTestP2Text4 some additional Text ",
75 "BackupDumperTestP2Summary4 extra " );
76 $this->pageId2 = $page->getId();
77
78 $this->pageTitle3 = Title::newFromText( 'BackupDumperTestP3', $this->namespace );
79 $page = WikiPage::factory( $this->pageTitle3 );
80 list( $this->revId3_1, $this->textId3_1 ) = $this->addRevision( $page,
81 "BackupDumperTestP3Text1", "BackupDumperTestP2Summary1" );
82 list( $this->revId3_2, $this->textId3_2 ) = $this->addRevision( $page,
83 "BackupDumperTestP3Text2", "BackupDumperTestP2Summary2" );
84 $this->pageId3 = $page->getId();
85 $page->doDeleteArticle( "Testing ;)" );
86
87 $this->pageTitle4 = Title::newFromText( 'BackupDumperTestP1', $this->talk_namespace );
88 $page = WikiPage::factory( $this->pageTitle4 );
89 list( $this->revId4_1, $this->textId4_1 ) = $this->addRevision( $page,
90 "Talk about BackupDumperTestP1 Text1",
91 "Talk BackupDumperTestP1 Summary1" );
92 $this->pageId4 = $page->getId();
93 } catch ( Exception $e ) {
94 // We'd love to pass $e directly. However, ... see
95 // documentation of exceptionFromAddDBData in
96 // DumpTestCase
97 $this->exceptionFromAddDBData = $e;
98 }
99 }
100
101 protected function setUp() {
102 parent::setUp();
103
104 // Since we will restrict dumping by page ranges (to allow
105 // working tests, even if the db gets prepopulated by a base
106 // class), we have to assert, that the page id are consecutively
107 // increasing
108 $this->assertEquals(
109 [ $this->pageId2, $this->pageId3, $this->pageId4 ],
110 [ $this->pageId1 + 1, $this->pageId2 + 1, $this->pageId3 + 1 ],
111 "Page ids increasing without holes" );
112 }
113
114 function tearDown() {
115 parent::tearDown();
116
117 if ( isset( $this->streamingLoadBalancer ) ) {
118 $this->streamingLoadBalancer->closeAll();
119 }
120 }
121
122 /**
123 * Returns a new database connection which is separate from the conenctions returned
124 * by the default LoadBalancer instance.
125 *
126 * @return IDatabase
127 */
128 private function newStreamingDBConnection() {
129 // Create a *new* LoadBalancer, so no connections are shared
130 if ( !$this->streamingLoadBalancer ) {
131 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
132
133 $this->streamingLoadBalancer = $lbFactory->newMainLB();
134 }
135
136 $db = $this->streamingLoadBalancer->getConnection( DB_REPLICA );
137
138 // Make sure the DB connection has the fake table clones and the fake table prefix
139 MediaWikiTestCase::setupDatabaseWithTestPrefix( $db );
140
141 // Make sure the DB connection has all the test data
142 $this->copyTestData( $this->db, $db );
143
144 return $db;
145 }
146
147 /**
148 * @param array $argv
149 * @param int $startId
150 * @param int $endId
151 *
152 * @return DumpBackup
153 */
154 private function newDumpBackup( $argv, $startId, $endId ) {
155 $dumper = new DumpBackup( $argv );
156 $dumper->startId = $startId;
157 $dumper->endId = $endId;
158 $dumper->reporting = false;
159
160 // NOTE: The copyTestData() method used by newStreamingDBConnection()
161 // doesn't work with SQLite (T217607).
162 // But DatabaseSqlite doesn't support streaming anyway, so just skip that part.
163 if ( $this->db->getType() === 'sqlite' ) {
164 $dumper->setDB( $this->db );
165 } else {
166 $dumper->setDB( $this->newStreamingDBConnection() );
167 }
168
169 return $dumper;
170 }
171
172 function testFullTextPlain() {
173 // Preparing the dump
174 $fname = $this->getNewTempFile();
175
176 $dumper = $this->newDumpBackup(
177 [ '--full', '--quiet', '--output', 'file:' . $fname ],
178 $this->pageId1,
179 $this->pageId4 + 1
180 );
181
182 // Performing the dump
183 $dumper->execute();
184
185 // Checking the dumped data
186 $this->assertDumpStart( $fname );
187
188 // Page 1
189 $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() );
190 $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
191 $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87",
192 "BackupDumperTestP1Text1" );
193 $this->assertPageEnd();
194
195 // Page 2
196 $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() );
197 $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1",
198 $this->textId2_1, 23, "jprywrymfhysqllua29tj3sc7z39dl2",
199 "BackupDumperTestP2Text1" );
200 $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2",
201 $this->textId2_2, 23, "b7vj5ks32po5m1z1t1br4o7scdwwy95",
202 "BackupDumperTestP2Text2", $this->revId2_1 );
203 $this->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3",
204 $this->textId2_3, 23, "jfunqmh1ssfb8rs43r19w98k28gg56r",
205 "BackupDumperTestP2Text3", $this->revId2_2 );
206 $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
207 $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv",
208 "BackupDumperTestP2Text4 some additional Text", $this->revId2_3 );
209 $this->assertPageEnd();
210
211 // Page 3
212 // -> Page is marked deleted. Hence not visible
213
214 // Page 4
215 $this->assertPageStart(
216 $this->pageId4,
217 $this->talk_namespace,
218 $this->pageTitle4->getPrefixedText()
219 );
220 $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
221 $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe",
222 "Talk about BackupDumperTestP1 Text1" );
223 $this->assertPageEnd();
224
225 $this->assertDumpEnd();
226 }
227
228 function testFullStubPlain() {
229 // Preparing the dump
230 $fname = $this->getNewTempFile();
231
232 $dumper = $this->newDumpBackup(
233 [ '--full', '--quiet', '--output', 'file:' . $fname, '--stub' ],
234 $this->pageId1,
235 $this->pageId4 + 1
236 );
237
238 // Performing the dump
239 $dumper->execute();
240
241 // Checking the dumped data
242 $this->assertDumpStart( $fname );
243
244 // Page 1
245 $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() );
246 $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
247 $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" );
248 $this->assertPageEnd();
249
250 // Page 2
251 $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() );
252 $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1",
253 $this->textId2_1, 23, "jprywrymfhysqllua29tj3sc7z39dl2" );
254 $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2",
255 $this->textId2_2, 23, "b7vj5ks32po5m1z1t1br4o7scdwwy95", false, $this->revId2_1 );
256 $this->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3",
257 $this->textId2_3, 23, "jfunqmh1ssfb8rs43r19w98k28gg56r", false, $this->revId2_2 );
258 $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
259 $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 );
260 $this->assertPageEnd();
261
262 // Page 3
263 // -> Page is marked deleted. Hence not visible
264
265 // Page 4
266 $this->assertPageStart(
267 $this->pageId4,
268 $this->talk_namespace,
269 $this->pageTitle4->getPrefixedText()
270 );
271 $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
272 $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" );
273 $this->assertPageEnd();
274
275 $this->assertDumpEnd();
276 }
277
278 function testCurrentStubPlain() {
279 // Preparing the dump
280 $fname = $this->getNewTempFile();
281
282 $dumper = $this->newDumpBackup(
283 [ '--output', 'file:' . $fname ],
284 $this->pageId1,
285 $this->pageId4 + 1
286 );
287
288 // Performing the dump
289 $dumper->dump( WikiExporter::CURRENT, WikiExporter::STUB );
290
291 // Checking the dumped data
292 $this->assertDumpStart( $fname );
293
294 // Page 1
295 $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() );
296 $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
297 $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" );
298 $this->assertPageEnd();
299
300 // Page 2
301 $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() );
302 $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
303 $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 );
304 $this->assertPageEnd();
305
306 // Page 3
307 // -> Page is marked deleted. Hence not visible
308
309 // Page 4
310 $this->assertPageStart(
311 $this->pageId4,
312 $this->talk_namespace,
313 $this->pageTitle4->getPrefixedText()
314 );
315 $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
316 $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" );
317 $this->assertPageEnd();
318
319 $this->assertDumpEnd();
320 }
321
322 function testCurrentStubGzip() {
323 $this->checkHasGzip();
324
325 // Preparing the dump
326 $fname = $this->getNewTempFile();
327
328 $dumper = $this->newDumpBackup(
329 [ '--output', 'gzip:' . $fname ],
330 $this->pageId1,
331 $this->pageId4 + 1
332 );
333
334 // Performing the dump
335 $dumper->dump( WikiExporter::CURRENT, WikiExporter::STUB );
336
337 // Checking the dumped data
338 $this->gunzip( $fname );
339 $this->assertDumpStart( $fname );
340
341 // Page 1
342 $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() );
343 $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
344 $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" );
345 $this->assertPageEnd();
346
347 // Page 2
348 $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() );
349 $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
350 $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 );
351 $this->assertPageEnd();
352
353 // Page 3
354 // -> Page is marked deleted. Hence not visible
355
356 // Page 4
357 $this->assertPageStart(
358 $this->pageId4,
359 $this->talk_namespace,
360 $this->pageTitle4->getPrefixedText()
361 );
362 $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
363 $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" );
364 $this->assertPageEnd();
365
366 $this->assertDumpEnd();
367 }
368
369 /**
370 * xmldumps-backup typically performs a single dump that that writes
371 * out three files
372 * - gzipped stubs of everything (meta-history)
373 * - gzipped stubs of latest revisions of all pages (meta-current)
374 * - gzipped stubs of latest revisions of all pages of namespage 0
375 * (articles)
376 *
377 * We reproduce such a setup with our mini fixture, although we omit
378 * chunks, and all the other gimmicks of xmldumps-backup.
379 */
380 function testXmlDumpsBackupUseCase() {
381 $this->checkHasGzip();
382
383 $fnameMetaHistory = $this->getNewTempFile();
384 $fnameMetaCurrent = $this->getNewTempFile();
385 $fnameArticles = $this->getNewTempFile();
386
387 $dumper = $this->newDumpBackup(
388 [ "--full", "--stub", "--output=gzip:" . $fnameMetaHistory,
389 "--output=gzip:" . $fnameMetaCurrent, "--filter=latest",
390 "--output=gzip:" . $fnameArticles, "--filter=latest",
391 "--filter=notalk", "--filter=namespace:!NS_USER",
392 "--reporting=1000"
393 ],
394 $this->pageId1,
395 $this->pageId4 + 1
396 );
397 $dumper->reporting = true;
398
399 // xmldumps-backup uses reporting. We will not check the exact reported
400 // message, as they are dependent on the processing power of the used
401 // computer. We only check that reporting does not crash the dumping
402 // and that something is reported
403 $dumper->stderr = fopen( 'php://output', 'a' );
404 if ( $dumper->stderr === false ) {
405 $this->fail( "Could not open stream for stderr" );
406 }
407
408 // Performing the dump
409 $dumper->dump( WikiExporter::FULL, WikiExporter::STUB );
410
411 $this->assertTrue( fclose( $dumper->stderr ), "Closing stderr handle" );
412
413 // Checking meta-history -------------------------------------------------
414
415 $this->gunzip( $fnameMetaHistory );
416 $this->assertDumpStart( $fnameMetaHistory );
417
418 // Page 1
419 $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() );
420 $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
421 $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" );
422 $this->assertPageEnd();
423
424 // Page 2
425 $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() );
426 $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1",
427 $this->textId2_1, 23, "jprywrymfhysqllua29tj3sc7z39dl2" );
428 $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2",
429 $this->textId2_2, 23, "b7vj5ks32po5m1z1t1br4o7scdwwy95", false, $this->revId2_1 );
430 $this->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3",
431 $this->textId2_3, 23, "jfunqmh1ssfb8rs43r19w98k28gg56r", false, $this->revId2_2 );
432 $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
433 $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 );
434 $this->assertPageEnd();
435
436 // Page 3
437 // -> Page is marked deleted. Hence not visible
438
439 // Page 4
440 $this->assertPageStart(
441 $this->pageId4,
442 $this->talk_namespace,
443 $this->pageTitle4->getPrefixedText()
444 );
445 $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
446 $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" );
447 $this->assertPageEnd();
448
449 $this->assertDumpEnd();
450
451 // Checking meta-current -------------------------------------------------
452
453 $this->gunzip( $fnameMetaCurrent );
454 $this->assertDumpStart( $fnameMetaCurrent );
455
456 // Page 1
457 $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() );
458 $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
459 $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" );
460 $this->assertPageEnd();
461
462 // Page 2
463 $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() );
464 $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
465 $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 );
466 $this->assertPageEnd();
467
468 // Page 3
469 // -> Page is marked deleted. Hence not visible
470
471 // Page 4
472 $this->assertPageStart(
473 $this->pageId4,
474 $this->talk_namespace,
475 $this->pageTitle4->getPrefixedText()
476 );
477 $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
478 $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" );
479 $this->assertPageEnd();
480
481 $this->assertDumpEnd();
482
483 // Checking articles -------------------------------------------------
484
485 $this->gunzip( $fnameArticles );
486 $this->assertDumpStart( $fnameArticles );
487
488 // Page 1
489 $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() );
490 $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
491 $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" );
492 $this->assertPageEnd();
493
494 // Page 2
495 $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() );
496 $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
497 $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 );
498 $this->assertPageEnd();
499
500 // Page 3
501 // -> Page is marked deleted. Hence not visible
502
503 // Page 4
504 // -> Page is not in $this->namespace. Hence not visible
505
506 $this->assertDumpEnd();
507
508 $this->expectETAOutput();
509 }
510 }