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