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