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