Merge "Expand special page aliases for Serbian"
[lhc/web/wiklou.git] / tests / phpunit / maintenance / backupTextPassTest.php
1 <?php
2
3 namespace MediaWiki\Tests\Maintenance;
4
5 use Exception;
6 use MediaWikiLangTestCase;
7 use MWException;
8 use TextContentHandler;
9 use TextPassDumper;
10 use Title;
11 use WikiExporter;
12 use WikiPage;
13
14 require_once __DIR__ . "/../../../maintenance/dumpTextPass.php";
15
16 /**
17 * Tests for TextPassDumper that rely on the database
18 *
19 * Some of these tests use the old constuctor for TextPassDumper
20 * and the dump() function, while others use the new loadWithArgv( $args )
21 * function and execute(). This is to ensure both the old and new methods
22 * work properly.
23 *
24 * @group Database
25 * @group Dump
26 * @covers TextPassDumper
27 */
28 class TextPassDumperDatabaseTest extends DumpTestCase {
29
30 // We'll add several pages, revision and texts. The following variables hold the
31 // corresponding ids.
32 private $pageId1, $pageId2, $pageId3, $pageId4;
33 private static $numOfPages = 4;
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 static $numOfRevs = 8;
40
41 function addDBData() {
42 $this->tablesUsed[] = 'page';
43 $this->tablesUsed[] = 'revision';
44 $this->tablesUsed[] = 'ip_changes';
45 $this->tablesUsed[] = 'text';
46
47 $this->mergeMwGlobalArrayValue( 'wgContentHandlers', [
48 "BackupTextPassTestModel" => BackupTextPassTestModelHandler::class,
49 ] );
50
51 $ns = $this->getDefaultWikitextNS();
52
53 try {
54 // Simple page
55 $title = Title::newFromText( 'BackupDumperTestP1', $ns );
56 $page = WikiPage::factory( $title );
57 list( $this->revId1_1, $this->textId1_1 ) = $this->addRevision( $page,
58 "BackupDumperTestP1Text1", "BackupDumperTestP1Summary1" );
59 $this->pageId1 = $page->getId();
60
61 // Page with more than one revision
62 $title = Title::newFromText( 'BackupDumperTestP2', $ns );
63 $page = WikiPage::factory( $title );
64 list( $this->revId2_1, $this->textId2_1 ) = $this->addRevision( $page,
65 "BackupDumperTestP2Text1", "BackupDumperTestP2Summary1" );
66 list( $this->revId2_2, $this->textId2_2 ) = $this->addRevision( $page,
67 "BackupDumperTestP2Text2", "BackupDumperTestP2Summary2" );
68 list( $this->revId2_3, $this->textId2_3 ) = $this->addRevision( $page,
69 "BackupDumperTestP2Text3", "BackupDumperTestP2Summary3" );
70 list( $this->revId2_4, $this->textId2_4 ) = $this->addRevision( $page,
71 "BackupDumperTestP2Text4 some additional Text ",
72 "BackupDumperTestP2Summary4 extra " );
73 $this->pageId2 = $page->getId();
74
75 // Deleted page.
76 $title = Title::newFromText( 'BackupDumperTestP3', $ns );
77 $page = WikiPage::factory( $title );
78 list( $this->revId3_1, $this->textId3_1 ) = $this->addRevision( $page,
79 "BackupDumperTestP3Text1", "BackupDumperTestP2Summary1" );
80 list( $this->revId3_2, $this->textId3_2 ) = $this->addRevision( $page,
81 "BackupDumperTestP3Text2", "BackupDumperTestP2Summary2" );
82 $this->pageId3 = $page->getId();
83 $page->doDeleteArticle( "Testing ;)" );
84
85 // Page from non-default namespace and model.
86 // ExportTransform applies.
87
88 if ( $ns === NS_TALK ) {
89 // @todo work around this.
90 throw new MWException( "The default wikitext namespace is the talk namespace. "
91 . " We can't currently deal with that." );
92 }
93
94 $title = Title::newFromText( 'BackupDumperTestP1', NS_TALK );
95 $page = WikiPage::factory( $title );
96 list( $this->revId4_1, $this->textId4_1 ) = $this->addRevision( $page,
97 "Talk about BackupDumperTestP1 Text1",
98 "Talk BackupDumperTestP1 Summary1",
99 "BackupTextPassTestModel" );
100 $this->pageId4 = $page->getId();
101 } catch ( Exception $e ) {
102 // We'd love to pass $e directly. However, ... see
103 // documentation of exceptionFromAddDBData in
104 // DumpTestCase
105 $this->exceptionFromAddDBData = $e;
106 }
107 }
108
109 protected function setUp() {
110 parent::setUp();
111
112 // Since we will restrict dumping by page ranges (to allow
113 // working tests, even if the db gets prepopulated by a base
114 // class), we have to assert, that the page id are consecutively
115 // increasing
116 $this->assertEquals(
117 [ $this->pageId2, $this->pageId3, $this->pageId4 ],
118 [ $this->pageId1 + 1, $this->pageId1 + 2, $this->pageId1 + 3 ],
119 "Page ids increasing without holes" );
120 }
121
122 function testPlain() {
123 // Setting up the dump
124 $nameStub = $this->setUpStub();
125 $nameFull = $this->getNewTempFile();
126 $dumper = new TextPassDumper( [ "--stub=file:" . $nameStub,
127 "--output=file:" . $nameFull ] );
128 $dumper->reporting = false;
129 $dumper->setDB( $this->db );
130
131 // Performing the dump
132 $dumper->dump( WikiExporter::FULL, WikiExporter::TEXT );
133
134 // Checking for correctness of the dumped data
135 $this->assertDumpStart( $nameFull );
136
137 // Page 1
138 $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
139 $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
140 $this->textId1_1, false, "0bolhl6ol7i6x0e7yq91gxgaan39j87",
141 "BackupDumperTestP1Text1" );
142 $this->assertPageEnd();
143
144 // Page 2
145 $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
146 $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1",
147 $this->textId2_1, false, "jprywrymfhysqllua29tj3sc7z39dl2",
148 "BackupDumperTestP2Text1" );
149 $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2",
150 $this->textId2_2, false, "b7vj5ks32po5m1z1t1br4o7scdwwy95",
151 "BackupDumperTestP2Text2", $this->revId2_1 );
152 $this->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3",
153 $this->textId2_3, false, "jfunqmh1ssfb8rs43r19w98k28gg56r",
154 "BackupDumperTestP2Text3", $this->revId2_2 );
155 $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
156 $this->textId2_4, false, "6o1ciaxa6pybnqprmungwofc4lv00wv",
157 "BackupDumperTestP2Text4 some additional Text", $this->revId2_3 );
158 $this->assertPageEnd();
159
160 // Page 3
161 // -> Page is marked deleted. Hence not visible
162
163 // Page 4
164 $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" );
165 $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
166 $this->textId4_1, false, "nktofwzd0tl192k3zfepmlzxoax1lpe",
167 "TALK ABOUT BACKUPDUMPERTESTP1 TEXT1",
168 false,
169 "BackupTextPassTestModel",
170 "text/plain" );
171 $this->assertPageEnd();
172
173 $this->assertDumpEnd();
174 }
175
176 function testPrefetchPlain() {
177 // The mapping between ids and text, for the hits of the prefetch mock
178 $prefetchMap = [
179 [ $this->pageId1, $this->revId1_1, "Prefetch_________1Text1" ],
180 [ $this->pageId2, $this->revId2_3, "Prefetch_________2Text3" ]
181 ];
182
183 // The mock itself
184 $prefetchMock = $this->getMockBuilder( BaseDump::class )
185 ->setMethods( [ 'prefetch' ] )
186 ->disableOriginalConstructor()
187 ->getMock();
188 $prefetchMock->expects( $this->exactly( 6 ) )
189 ->method( 'prefetch' )
190 ->will( $this->returnValueMap( $prefetchMap ) );
191
192 // Setting up of the dump
193 $nameStub = $this->setUpStub();
194 $nameFull = $this->getNewTempFile();
195
196 $dumper = new TextPassDumper( [ "--stub=file:" . $nameStub,
197 "--output=file:" . $nameFull ] );
198
199 $dumper->prefetch = $prefetchMock;
200 $dumper->reporting = false;
201 $dumper->setDB( $this->db );
202
203 // Performing the dump
204 $dumper->dump( WikiExporter::FULL, WikiExporter::TEXT );
205
206 // Checking for correctness of the dumped data
207 $this->assertDumpStart( $nameFull );
208
209 // Page 1
210 $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
211 // Prefetch kicks in. This is still the SHA-1 of the original text,
212 // But the actual text (with different SHA-1) comes from prefetch.
213 $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
214 $this->textId1_1, false, "0bolhl6ol7i6x0e7yq91gxgaan39j87",
215 "Prefetch_________1Text1" );
216 $this->assertPageEnd();
217
218 // Page 2
219 $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
220 $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1",
221 $this->textId2_1, false, "jprywrymfhysqllua29tj3sc7z39dl2",
222 "BackupDumperTestP2Text1" );
223 $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2",
224 $this->textId2_2, false, "b7vj5ks32po5m1z1t1br4o7scdwwy95",
225 "BackupDumperTestP2Text2", $this->revId2_1 );
226 // Prefetch kicks in. This is still the SHA-1 of the original text,
227 // But the actual text (with different SHA-1) comes from prefetch.
228 $this->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3",
229 $this->textId2_3, false, "jfunqmh1ssfb8rs43r19w98k28gg56r",
230 "Prefetch_________2Text3", $this->revId2_2 );
231 $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
232 $this->textId2_4, false, "6o1ciaxa6pybnqprmungwofc4lv00wv",
233 "BackupDumperTestP2Text4 some additional Text", $this->revId2_3 );
234 $this->assertPageEnd();
235
236 // Page 3
237 // -> Page is marked deleted. Hence not visible
238
239 // Page 4
240 $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" );
241 $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
242 $this->textId4_1, false, "nktofwzd0tl192k3zfepmlzxoax1lpe",
243 "TALK ABOUT BACKUPDUMPERTESTP1 TEXT1",
244 false,
245 "BackupTextPassTestModel",
246 "text/plain" );
247 $this->assertPageEnd();
248
249 $this->assertDumpEnd();
250 }
251
252 /**
253 * Ensures that checkpoint dumps are used and written, by successively increasing the
254 * stub size and dumping until the duration crosses a threshold.
255 *
256 * @param string $checkpointFormat Either "file" for plain text or "gzip" for gzipped
257 * checkpoint files.
258 */
259 private function checkpointHelper( $checkpointFormat = "file" ) {
260 // Getting temporary names
261 $nameStub = $this->getNewTempFile();
262 $nameOutputDir = $this->getNewTempDirectory();
263
264 $stderr = fopen( 'php://output', 'a' );
265 if ( $stderr === false ) {
266 $this->fail( "Could not open stream for stderr" );
267 }
268
269 $iterations = 32; // We'll start with that many iterations of revisions
270 // in stub. Make sure that the generated volume is above the buffer size
271 // set below. Otherwise, the checkpointing does not trigger.
272 $lastDuration = 0;
273 $minDuration = 2; // We want the dump to take at least this many seconds
274 $checkpointAfter = 0.5; // Generate checkpoint after this many seconds
275
276 // Until a dump takes at least $minDuration seconds, perform a dump and check
277 // duration. If the dump did not take long enough increase the iteration
278 // count, to generate a bigger stub file next time.
279 while ( $lastDuration < $minDuration ) {
280 // Setting up the dump
281 wfRecursiveRemoveDir( $nameOutputDir );
282 $this->assertTrue( wfMkdirParents( $nameOutputDir ),
283 "Creating temporary output directory " );
284 $this->setUpStub( $nameStub, $iterations );
285 $dumper = new TextPassDumper();
286 $dumper->loadWithArgv( [ "--stub=file:" . $nameStub,
287 "--output=" . $checkpointFormat . ":" . $nameOutputDir . "/full",
288 "--maxtime=1" /*This is in minutes. Fixup is below*/,
289 "--buffersize=32768", // The default of 32 iterations fill up 32KB about twice
290 "--checkpointfile=checkpoint-%s-%s.xml.gz" ] );
291 $dumper->setDB( $this->db );
292 $dumper->maxTimeAllowed = $checkpointAfter; // Patching maxTime from 1 minute
293 $dumper->stderr = $stderr;
294
295 // The actual dump and taking time
296 $ts_before = microtime( true );
297 $dumper->execute();
298 $ts_after = microtime( true );
299 $lastDuration = $ts_after - $ts_before;
300
301 // Handling increasing the iteration count for the stubs
302 if ( $lastDuration < $minDuration ) {
303 $old_iterations = $iterations;
304 if ( $lastDuration > 0.2 ) {
305 // lastDuration is big enough, to allow an educated guess
306 $factor = ( $minDuration + 0.5 ) / $lastDuration;
307 if ( ( $factor > 1.1 ) && ( $factor < 100 ) ) {
308 // educated guess is reasonable
309 $iterations = (int)( $iterations * $factor );
310 }
311 }
312
313 if ( $old_iterations == $iterations ) {
314 // Heuristics were not applied, so we just *2.
315 $iterations *= 2;
316 }
317
318 $this->assertLessThan( 50000, $iterations,
319 "Emergency stop against infinitely increasing iteration "
320 . "count ( last duration: $lastDuration )" );
321 }
322 }
323
324 // The dump (hopefully) did take long enough to produce more than one
325 // checkpoint file.
326 // We now check all the checkpoint files for validity.
327
328 $files = scandir( $nameOutputDir );
329 $this->assertTrue( asort( $files ), "Sorting files in temporary directory" );
330 $fileOpened = false;
331 $lookingForPage = 1;
332 $checkpointFiles = 0;
333
334 // Each run of the following loop body tries to handle exactly 1 /page/ (not
335 // iteration of stub content). $i is only increased after having treated page 4.
336 for ( $i = 0; $i < $iterations; ) {
337 // 1. Assuring a file is opened and ready. Skipping across header if
338 // necessary.
339 if ( !$fileOpened ) {
340 $this->assertNotEmpty( $files, "No more existing dump files, "
341 . "but not yet all pages found" );
342 $fname = array_shift( $files );
343 while ( $fname == "." || $fname == ".." ) {
344 $this->assertNotEmpty( $files, "No more existing dump"
345 . " files, but not yet all pages found" );
346 $fname = array_shift( $files );
347 }
348 if ( $checkpointFormat == "gzip" ) {
349 $this->gunzip( $nameOutputDir . "/" . $fname );
350 }
351 $this->assertDumpStart( $nameOutputDir . "/" . $fname );
352 $fileOpened = true;
353 $checkpointFiles++;
354 }
355
356 // 2. Performing a single page check
357 switch ( $lookingForPage ) {
358 case 1:
359 // Page 1
360 $this->assertPageStart( $this->pageId1 + $i * self::$numOfPages, NS_MAIN,
361 "BackupDumperTestP1" );
362 $this->assertRevision( $this->revId1_1 + $i * self::$numOfRevs, "BackupDumperTestP1Summary1",
363 $this->textId1_1, false, "0bolhl6ol7i6x0e7yq91gxgaan39j87",
364 "BackupDumperTestP1Text1" );
365 $this->assertPageEnd();
366
367 $lookingForPage = 2;
368 break;
369
370 case 2:
371 // Page 2
372 $this->assertPageStart( $this->pageId2 + $i * self::$numOfPages, NS_MAIN,
373 "BackupDumperTestP2" );
374 $this->assertRevision( $this->revId2_1 + $i * self::$numOfRevs, "BackupDumperTestP2Summary1",
375 $this->textId2_1, false, "jprywrymfhysqllua29tj3sc7z39dl2",
376 "BackupDumperTestP2Text1" );
377 $this->assertRevision( $this->revId2_2 + $i * self::$numOfRevs, "BackupDumperTestP2Summary2",
378 $this->textId2_2, false, "b7vj5ks32po5m1z1t1br4o7scdwwy95",
379 "BackupDumperTestP2Text2", $this->revId2_1 + $i * self::$numOfRevs );
380 $this->assertRevision( $this->revId2_3 + $i * self::$numOfRevs, "BackupDumperTestP2Summary3",
381 $this->textId2_3, false, "jfunqmh1ssfb8rs43r19w98k28gg56r",
382 "BackupDumperTestP2Text3", $this->revId2_2 + $i * self::$numOfRevs );
383 $this->assertRevision( $this->revId2_4 + $i * self::$numOfRevs,
384 "BackupDumperTestP2Summary4 extra",
385 $this->textId2_4, false, "6o1ciaxa6pybnqprmungwofc4lv00wv",
386 "BackupDumperTestP2Text4 some additional Text",
387 $this->revId2_3 + $i * self::$numOfRevs );
388 $this->assertPageEnd();
389
390 $lookingForPage = 4;
391 break;
392
393 case 4:
394 // Page 4
395 $this->assertPageStart( $this->pageId4 + $i * self::$numOfPages, NS_TALK,
396 "Talk:BackupDumperTestP1" );
397 $this->assertRevision( $this->revId4_1 + $i * self::$numOfRevs,
398 "Talk BackupDumperTestP1 Summary1",
399 $this->textId4_1, false, "nktofwzd0tl192k3zfepmlzxoax1lpe",
400 "TALK ABOUT BACKUPDUMPERTESTP1 TEXT1",
401 false,
402 "BackupTextPassTestModel",
403 "text/plain" );
404 $this->assertPageEnd();
405
406 $lookingForPage = 1;
407
408 // We dealt with the whole iteration.
409 $i++;
410 break;
411
412 default:
413 $this->fail( "Bad setting for lookingForPage ($lookingForPage)" );
414 }
415
416 // 3. Checking for the end of the current checkpoint file
417 if ( $this->xml->nodeType == XMLReader::END_ELEMENT
418 && $this->xml->name == "mediawiki"
419 ) {
420 $this->assertDumpEnd();
421 $fileOpened = false;
422 }
423 }
424
425 // Assuring we completely read all files ...
426 $this->assertFalse( $fileOpened, "Currently read file still open?" );
427 $this->assertEmpty( $files, "Remaining unchecked files" );
428
429 // ... and have dealt with more than one checkpoint file
430 $this->assertGreaterThan(
431 1,
432 $checkpointFiles,
433 "expected more than 1 checkpoint to have been created. "
434 . "Checkpoint interval is $checkpointAfter seconds, maybe your computer is too fast?"
435 );
436
437 $this->expectETAOutput();
438 }
439
440 /**
441 * Broken per T70653.
442 *
443 * @group large
444 * @group Broken
445 */
446 function testCheckpointPlain() {
447 $this->checkpointHelper();
448 }
449
450 /**
451 * tests for working checkpoint generation in gzip format work.
452 *
453 * We keep this test in addition to the simpler self::testCheckpointPlain, as there
454 * were once problems when the used sinks were DumpPipeOutputs.
455 *
456 * xmldumps-backup typically uses bzip2 instead of gzip. However, as bzip2 requires
457 * PHP extensions, we go for gzip instead, which triggers the same relevant code
458 * paths while still being testable on more systems.
459 *
460 * Broken per T70653.
461 *
462 * @group large
463 * @group Broken
464 */
465 function testCheckpointGzip() {
466 $this->checkHasGzip();
467 $this->checkpointHelper( "gzip" );
468 }
469
470 /**
471 * Creates a stub file that is used for testing the text pass of dumps
472 *
473 * @param string $fname (Optional) Absolute name of the file to write
474 * the stub into. If this parameter is null, a new temporary
475 * file is generated that is automatically removed upon tearDown.
476 * @param int $iterations (Optional) specifies how often the block
477 * of 3 pages should go into the stub file. The page and
478 * revision id increase further and further, while the text
479 * id of the first iteration is reused. The pages and revision
480 * of iteration > 1 have no corresponding representation in the database.
481 * @return string Absolute filename of the stub
482 */
483 private function setUpStub( $fname = null, $iterations = 1 ) {
484 if ( $fname === null ) {
485 $fname = $this->getNewTempFile();
486 }
487 $header = '<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.10/" '
488 . 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
489 . 'xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.10/ '
490 . 'http://www.mediawiki.org/xml/export-0.10.xsd" version="0.10" xml:lang="en">
491 <siteinfo>
492 <sitename>wikisvn</sitename>
493 <base>http://localhost/wiki-svn/index.php/Main_Page</base>
494 <generator>MediaWiki 1.21alpha</generator>
495 <case>first-letter</case>
496 <namespaces>
497 <namespace key="-2" case="first-letter">Media</namespace>
498 <namespace key="-1" case="first-letter">Special</namespace>
499 <namespace key="0" case="first-letter" />
500 <namespace key="1" case="first-letter">Talk</namespace>
501 <namespace key="2" case="first-letter">User</namespace>
502 <namespace key="3" case="first-letter">User talk</namespace>
503 <namespace key="4" case="first-letter">Wikisvn</namespace>
504 <namespace key="5" case="first-letter">Wikisvn talk</namespace>
505 <namespace key="6" case="first-letter">File</namespace>
506 <namespace key="7" case="first-letter">File talk</namespace>
507 <namespace key="8" case="first-letter">MediaWiki</namespace>
508 <namespace key="9" case="first-letter">MediaWiki talk</namespace>
509 <namespace key="10" case="first-letter">Template</namespace>
510 <namespace key="11" case="first-letter">Template talk</namespace>
511 <namespace key="12" case="first-letter">Help</namespace>
512 <namespace key="13" case="first-letter">Help talk</namespace>
513 <namespace key="14" case="first-letter">Category</namespace>
514 <namespace key="15" case="first-letter">Category talk</namespace>
515 </namespaces>
516 </siteinfo>
517 ';
518 $tail = '</mediawiki>
519 ';
520
521 $content = $header;
522 $iterations = intval( $iterations );
523 for ( $i = 0; $i < $iterations; $i++ ) {
524 $page1 = ' <page>
525 <title>BackupDumperTestP1</title>
526 <ns>0</ns>
527 <id>' . ( $this->pageId1 + $i * self::$numOfPages ) . '</id>
528 <revision>
529 <id>' . ( $this->revId1_1 + $i * self::$numOfRevs ) . '</id>
530 <timestamp>2012-04-01T16:46:05Z</timestamp>
531 <contributor>
532 <ip>127.0.0.1</ip>
533 </contributor>
534 <comment>BackupDumperTestP1Summary1</comment>
535 <model>wikitext</model>
536 <format>text/x-wiki</format>
537 <text id="' . $this->textId1_1 . '" bytes="23" />
538 <sha1>0bolhl6ol7i6x0e7yq91gxgaan39j87</sha1>
539 </revision>
540 </page>
541 ';
542 $page2 = ' <page>
543 <title>BackupDumperTestP2</title>
544 <ns>0</ns>
545 <id>' . ( $this->pageId2 + $i * self::$numOfPages ) . '</id>
546 <revision>
547 <id>' . ( $this->revId2_1 + $i * self::$numOfRevs ) . '</id>
548 <timestamp>2012-04-01T16:46:05Z</timestamp>
549 <contributor>
550 <ip>127.0.0.1</ip>
551 </contributor>
552 <comment>BackupDumperTestP2Summary1</comment>
553 <model>wikitext</model>
554 <format>text/x-wiki</format>
555 <text id="' . $this->textId2_1 . '" bytes="23" />
556 <sha1>jprywrymfhysqllua29tj3sc7z39dl2</sha1>
557 </revision>
558 <revision>
559 <id>' . ( $this->revId2_2 + $i * self::$numOfRevs ) . '</id>
560 <parentid>' . ( $this->revId2_1 + $i * self::$numOfRevs ) . '</parentid>
561 <timestamp>2012-04-01T16:46:05Z</timestamp>
562 <contributor>
563 <ip>127.0.0.1</ip>
564 </contributor>
565 <comment>BackupDumperTestP2Summary2</comment>
566 <model>wikitext</model>
567 <format>text/x-wiki</format>
568 <text id="' . $this->textId2_2 . '" bytes="23" />
569 <sha1>b7vj5ks32po5m1z1t1br4o7scdwwy95</sha1>
570 </revision>
571 <revision>
572 <id>' . ( $this->revId2_3 + $i * self::$numOfRevs ) . '</id>
573 <parentid>' . ( $this->revId2_2 + $i * self::$numOfRevs ) . '</parentid>
574 <timestamp>2012-04-01T16:46:05Z</timestamp>
575 <contributor>
576 <ip>127.0.0.1</ip>
577 </contributor>
578 <comment>BackupDumperTestP2Summary3</comment>
579 <model>wikitext</model>
580 <format>text/x-wiki</format>
581 <text id="' . $this->textId2_3 . '" bytes="23" />
582 <sha1>jfunqmh1ssfb8rs43r19w98k28gg56r</sha1>
583 </revision>
584 <revision>
585 <id>' . ( $this->revId2_4 + $i * self::$numOfRevs ) . '</id>
586 <parentid>' . ( $this->revId2_3 + $i * self::$numOfRevs ) . '</parentid>
587 <timestamp>2012-04-01T16:46:05Z</timestamp>
588 <contributor>
589 <ip>127.0.0.1</ip>
590 </contributor>
591 <comment>BackupDumperTestP2Summary4 extra</comment>
592 <model>wikitext</model>
593 <format>text/x-wiki</format>
594 <text id="' . $this->textId2_4 . '" bytes="44" />
595 <sha1>6o1ciaxa6pybnqprmungwofc4lv00wv</sha1>
596 </revision>
597 </page>
598 ';
599 // page 3 not in stub
600
601 $page4 = ' <page>
602 <title>Talk:BackupDumperTestP1</title>
603 <ns>1</ns>
604 <id>' . ( $this->pageId4 + $i * self::$numOfPages ) . '</id>
605 <revision>
606 <id>' . ( $this->revId4_1 + $i * self::$numOfRevs ) . '</id>
607 <timestamp>2012-04-01T16:46:05Z</timestamp>
608 <contributor>
609 <ip>127.0.0.1</ip>
610 </contributor>
611 <comment>Talk BackupDumperTestP1 Summary1</comment>
612 <model>BackupTextPassTestModel</model>
613 <format>text/plain</format>
614 <text id="' . $this->textId4_1 . '" bytes="35" />
615 <sha1>nktofwzd0tl192k3zfepmlzxoax1lpe</sha1>
616 </revision>
617 </page>
618 ';
619 $content .= $page1 . $page2 . $page4;
620 }
621 $content .= $tail;
622 $this->assertEquals( strlen( $content ), file_put_contents(
623 $fname, $content ), "Length of prepared stub" );
624
625 return $fname;
626 }
627 }
628
629 class BackupTextPassTestModelHandler extends TextContentHandler {
630
631 public function __construct() {
632 parent::__construct( 'BackupTextPassTestModel' );
633 }
634
635 public function exportTransform( $text, $format = null ) {
636 return strtoupper( $text );
637 }
638
639 }
640
641 /**
642 * Tests for TextPassDumper that do not rely on the database
643 *
644 * (As the Database group is only detected at class level (not method level), we
645 * cannot bring this test case's tests into the above main test case.)
646 *
647 * @group Dump
648 * @covers TextPassDumper
649 */
650 class TextPassDumperDatabaselessTest extends MediaWikiLangTestCase {
651 /**
652 * Ensures that setting the buffer size is effective.
653 *
654 * @dataProvider bufferSizeProvider
655 */
656 function testBufferSizeSetting( $expected, $size, $msg ) {
657 $dumper = new TextPassDumperAccessor();
658 $dumper->loadWithArgv( [ "--buffersize=" . $size ] );
659 $dumper->execute();
660 $this->assertEquals( $expected, $dumper->getBufferSize(), $msg );
661 }
662
663 /**
664 * Ensures that setting the buffer size is effective.
665 *
666 * @dataProvider bufferSizeProvider
667 */
668 function bufferSizeProvider() {
669 // expected, bufferSize to initialize with, message
670 return [
671 [ 512 * 1024, 512 * 1024, "Setting 512KB is not effective" ],
672 [ 8192, 8192, "Setting 8KB is not effective" ],
673 [ 4096, 2048, "Could set buffer size below lower bound" ]
674 ];
675 }
676 }
677
678 /**
679 * Accessor for internal state of TextPassDumper
680 *
681 * Do not warrentless add getters here.
682 */
683 class TextPassDumperAccessor extends TextPassDumper {
684 /**
685 * Gets the bufferSize.
686 *
687 * If bufferSize setting does not work correctly, testCheckpoint... tests
688 * fail and point in the wrong direction. To aid in troubleshooting when
689 * testCheckpoint... tests break at some point in the future, we test the
690 * bufferSize setting, hence need this accessor.
691 *
692 * (Yes, bufferSize is internal state of the TextPassDumper, but aiding
693 * debugging of testCheckpoint... in the future seems to be worth testing
694 * against it nonetheless.)
695 */
696 public function getBufferSize() {
697 return $this->bufferSize;
698 }
699
700 function dump( $history, $text = null ) {
701 return true;
702 }
703 }