MediaWiki  REL1_24
backupTextPassTest.php
Go to the documentation of this file.
00001 <?php
00002 
00003 require_once __DIR__ . "/../../../maintenance/backupTextPass.inc";
00004 
00012 class TextPassDumperTest extends DumpTestCase {
00013 
00014     // We'll add several pages, revision and texts. The following variables hold the
00015     // corresponding ids.
00016     private $pageId1, $pageId2, $pageId3, $pageId4;
00017     private static $numOfPages = 4;
00018     private $revId1_1, $textId1_1;
00019     private $revId2_1, $textId2_1, $revId2_2, $textId2_2;
00020     private $revId2_3, $textId2_3, $revId2_4, $textId2_4;
00021     private $revId3_1, $textId3_1, $revId3_2, $textId3_2;
00022     private $revId4_1, $textId4_1;
00023     private static $numOfRevs = 8;
00024 
00025     function addDBData() {
00026         $this->tablesUsed[] = 'page';
00027         $this->tablesUsed[] = 'revision';
00028         $this->tablesUsed[] = 'text';
00029 
00030         $ns = $this->getDefaultWikitextNS();
00031 
00032         try {
00033             // Simple page
00034             $title = Title::newFromText( 'BackupDumperTestP1', $ns );
00035             $page = WikiPage::factory( $title );
00036             list( $this->revId1_1, $this->textId1_1 ) = $this->addRevision( $page,
00037                 "BackupDumperTestP1Text1", "BackupDumperTestP1Summary1" );
00038             $this->pageId1 = $page->getId();
00039 
00040             // Page with more than one revision
00041             $title = Title::newFromText( 'BackupDumperTestP2', $ns );
00042             $page = WikiPage::factory( $title );
00043             list( $this->revId2_1, $this->textId2_1 ) = $this->addRevision( $page,
00044                 "BackupDumperTestP2Text1", "BackupDumperTestP2Summary1" );
00045             list( $this->revId2_2, $this->textId2_2 ) = $this->addRevision( $page,
00046                 "BackupDumperTestP2Text2", "BackupDumperTestP2Summary2" );
00047             list( $this->revId2_3, $this->textId2_3 ) = $this->addRevision( $page,
00048                 "BackupDumperTestP2Text3", "BackupDumperTestP2Summary3" );
00049             list( $this->revId2_4, $this->textId2_4 ) = $this->addRevision( $page,
00050                 "BackupDumperTestP2Text4 some additional Text  ",
00051                 "BackupDumperTestP2Summary4 extra " );
00052             $this->pageId2 = $page->getId();
00053 
00054             // Deleted page.
00055             $title = Title::newFromText( 'BackupDumperTestP3', $ns );
00056             $page = WikiPage::factory( $title );
00057             list( $this->revId3_1, $this->textId3_1 ) = $this->addRevision( $page,
00058                 "BackupDumperTestP3Text1", "BackupDumperTestP2Summary1" );
00059             list( $this->revId3_2, $this->textId3_2 ) = $this->addRevision( $page,
00060                 "BackupDumperTestP3Text2", "BackupDumperTestP2Summary2" );
00061             $this->pageId3 = $page->getId();
00062             $page->doDeleteArticle( "Testing ;)" );
00063 
00064             // Page from non-default namespace
00065 
00066             if ( $ns === NS_TALK ) {
00067                 // @todo work around this.
00068                 throw new MWException( "The default wikitext namespace is the talk namespace. "
00069                     . " We can't currently deal with that." );
00070             }
00071 
00072             $title = Title::newFromText( 'BackupDumperTestP1', NS_TALK );
00073             $page = WikiPage::factory( $title );
00074             list( $this->revId4_1, $this->textId4_1 ) = $this->addRevision( $page,
00075                 "Talk about BackupDumperTestP1 Text1",
00076                 "Talk BackupDumperTestP1 Summary1" );
00077             $this->pageId4 = $page->getId();
00078         } catch ( Exception $e ) {
00079             // We'd love to pass $e directly. However, ... see
00080             // documentation of exceptionFromAddDBData in
00081             // DumpTestCase
00082             $this->exceptionFromAddDBData = $e;
00083         }
00084     }
00085 
00086     protected function setUp() {
00087         parent::setUp();
00088 
00089         // Since we will restrict dumping by page ranges (to allow
00090         // working tests, even if the db gets prepopulated by a base
00091         // class), we have to assert, that the page id are consecutively
00092         // increasing
00093         $this->assertEquals(
00094             array( $this->pageId2, $this->pageId3, $this->pageId4 ),
00095             array( $this->pageId1 + 1, $this->pageId2 + 1, $this->pageId3 + 1 ),
00096             "Page ids increasing without holes" );
00097     }
00098 
00099     function testPlain() {
00100         // Setting up the dump
00101         $nameStub = $this->setUpStub();
00102         $nameFull = $this->getNewTempFile();
00103         $dumper = new TextPassDumper( array( "--stub=file:" . $nameStub,
00104             "--output=file:" . $nameFull ) );
00105         $dumper->reporting = false;
00106         $dumper->setDb( $this->db );
00107 
00108         // Performing the dump
00109         $dumper->dump( WikiExporter::FULL, WikiExporter::TEXT );
00110 
00111         // Checking for correctness of the dumped data
00112         $this->assertDumpStart( $nameFull );
00113 
00114         // Page 1
00115         $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
00116         $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
00117             $this->textId1_1, false, "0bolhl6ol7i6x0e7yq91gxgaan39j87",
00118             "BackupDumperTestP1Text1" );
00119         $this->assertPageEnd();
00120 
00121         // Page 2
00122         $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
00123         $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1",
00124             $this->textId2_1, false, "jprywrymfhysqllua29tj3sc7z39dl2",
00125             "BackupDumperTestP2Text1" );
00126         $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2",
00127             $this->textId2_2, false, "b7vj5ks32po5m1z1t1br4o7scdwwy95",
00128             "BackupDumperTestP2Text2", $this->revId2_1 );
00129         $this->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3",
00130             $this->textId2_3, false, "jfunqmh1ssfb8rs43r19w98k28gg56r",
00131             "BackupDumperTestP2Text3", $this->revId2_2 );
00132         $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
00133             $this->textId2_4, false, "6o1ciaxa6pybnqprmungwofc4lv00wv",
00134             "BackupDumperTestP2Text4 some additional Text", $this->revId2_3 );
00135         $this->assertPageEnd();
00136 
00137         // Page 3
00138         // -> Page is marked deleted. Hence not visible
00139 
00140         // Page 4
00141         $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" );
00142         $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
00143             $this->textId4_1, false, "nktofwzd0tl192k3zfepmlzxoax1lpe",
00144             "Talk about BackupDumperTestP1 Text1" );
00145         $this->assertPageEnd();
00146 
00147         $this->assertDumpEnd();
00148     }
00149 
00150     function testPrefetchPlain() {
00151         // The mapping between ids and text, for the hits of the prefetch mock
00152         $prefetchMap = array(
00153             array( $this->pageId1, $this->revId1_1, "Prefetch_________1Text1" ),
00154             array( $this->pageId2, $this->revId2_3, "Prefetch_________2Text3" )
00155         );
00156 
00157         // The mock itself
00158         $prefetchMock = $this->getMock( 'BaseDump', array( 'prefetch' ), array(), '', false );
00159         $prefetchMock->expects( $this->exactly( 6 ) )
00160             ->method( 'prefetch' )
00161             ->will( $this->returnValueMap( $prefetchMap ) );
00162 
00163         // Setting up of the dump
00164         $nameStub = $this->setUpStub();
00165         $nameFull = $this->getNewTempFile();
00166         $dumper = new TextPassDumper( array( "--stub=file:"
00167             . $nameStub, "--output=file:" . $nameFull ) );
00168         $dumper->prefetch = $prefetchMock;
00169         $dumper->reporting = false;
00170         $dumper->setDb( $this->db );
00171 
00172         // Performing the dump
00173         $dumper->dump( WikiExporter::FULL, WikiExporter::TEXT );
00174 
00175         // Checking for correctness of the dumped data
00176         $this->assertDumpStart( $nameFull );
00177 
00178         // Page 1
00179         $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
00180         // Prefetch kicks in. This is still the SHA-1 of the original text,
00181         // But the actual text (with different SHA-1) comes from prefetch.
00182         $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
00183             $this->textId1_1, false, "0bolhl6ol7i6x0e7yq91gxgaan39j87",
00184             "Prefetch_________1Text1" );
00185         $this->assertPageEnd();
00186 
00187         // Page 2
00188         $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
00189         $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1",
00190             $this->textId2_1, false, "jprywrymfhysqllua29tj3sc7z39dl2",
00191             "BackupDumperTestP2Text1" );
00192         $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2",
00193             $this->textId2_2, false, "b7vj5ks32po5m1z1t1br4o7scdwwy95",
00194             "BackupDumperTestP2Text2", $this->revId2_1 );
00195         // Prefetch kicks in. This is still the SHA-1 of the original text,
00196         // But the actual text (with different SHA-1) comes from prefetch.
00197         $this->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3",
00198             $this->textId2_3, false, "jfunqmh1ssfb8rs43r19w98k28gg56r",
00199             "Prefetch_________2Text3", $this->revId2_2 );
00200         $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
00201             $this->textId2_4, false, "6o1ciaxa6pybnqprmungwofc4lv00wv",
00202             "BackupDumperTestP2Text4 some additional Text", $this->revId2_3 );
00203         $this->assertPageEnd();
00204 
00205         // Page 3
00206         // -> Page is marked deleted. Hence not visible
00207 
00208         // Page 4
00209         $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" );
00210         $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
00211             $this->textId4_1, false, "nktofwzd0tl192k3zfepmlzxoax1lpe",
00212             "Talk about BackupDumperTestP1 Text1" );
00213         $this->assertPageEnd();
00214 
00215         $this->assertDumpEnd();
00216     }
00217 
00225     private function checkpointHelper( $checkpointFormat = "file" ) {
00226         // Getting temporary names
00227         $nameStub = $this->getNewTempFile();
00228         $nameOutputDir = $this->getNewTempDirectory();
00229 
00230         $stderr = fopen( 'php://output', 'a' );
00231         if ( $stderr === false ) {
00232             $this->fail( "Could not open stream for stderr" );
00233         }
00234 
00235         $iterations = 32; // We'll start with that many iterations of revisions in stub
00236         $lastDuration = 0;
00237         $minDuration = 2; // We want the dump to take at least this many seconds
00238         $checkpointAfter = 0.5; // Generate checkpoint after this many seconds
00239 
00240         // Until a dump takes at least $minDuration seconds, perform a dump and check
00241         // duration. If the dump did not take long enough increase the iteration
00242         // count, to generate a bigger stub file next time.
00243         while ( $lastDuration < $minDuration ) {
00244 
00245             // Setting up the dump
00246             wfRecursiveRemoveDir( $nameOutputDir );
00247             $this->assertTrue( wfMkdirParents( $nameOutputDir ),
00248                 "Creating temporary output directory " );
00249             $this->setUpStub( $nameStub, $iterations );
00250             $dumper = new TextPassDumper( array( "--stub=file:" . $nameStub,
00251                 "--output=" . $checkpointFormat . ":" . $nameOutputDir . "/full",
00252                 "--maxtime=1" /*This is in minutes. Fixup is below*/,
00253                 "--checkpointfile=checkpoint-%s-%s.xml.gz" ) );
00254             $dumper->setDb( $this->db );
00255             $dumper->maxTimeAllowed = $checkpointAfter; // Patching maxTime from 1 minute
00256             $dumper->stderr = $stderr;
00257 
00258             // The actual dump and taking time
00259             $ts_before = microtime( true );
00260             $dumper->dump( WikiExporter::FULL, WikiExporter::TEXT );
00261             $ts_after = microtime( true );
00262             $lastDuration = $ts_after - $ts_before;
00263 
00264             // Handling increasing the iteration count for the stubs
00265             if ( $lastDuration < $minDuration ) {
00266                 $old_iterations = $iterations;
00267                 if ( $lastDuration > 0.2 ) {
00268                     // lastDuration is big enough, to allow an educated guess
00269                     $factor = ( $minDuration + 0.5 ) / $lastDuration;
00270                     if ( ( $factor > 1.1 ) && ( $factor < 100 ) ) {
00271                         // educated guess is reasonable
00272                         $iterations = (int)( $iterations * $factor );
00273                     }
00274                 }
00275 
00276                 if ( $old_iterations == $iterations ) {
00277                     // Heuristics were not applied, so we just *2.
00278                     $iterations *= 2;
00279                 }
00280 
00281                 $this->assertLessThan( 50000, $iterations,
00282                     "Emergency stop against infinitely increasing iteration "
00283                         . "count ( last duration: $lastDuration )" );
00284             }
00285         }
00286 
00287         // The dump (hopefully) did take long enough to produce more than one
00288         // checkpoint file.
00289         //
00290         // We now check all the checkpoint files for validity.
00291 
00292         $files = scandir( $nameOutputDir );
00293         $this->assertTrue( asort( $files ), "Sorting files in temporary directory" );
00294         $fileOpened = false;
00295         $lookingForPage = 1;
00296         $checkpointFiles = 0;
00297 
00298         // Each run of the following loop body tries to handle exactly 1 /page/ (not
00299         // iteration of stub content). $i is only increased after having treated page 4.
00300         for ( $i = 0; $i < $iterations; ) {
00301 
00302             // 1. Assuring a file is opened and ready. Skipping across header if
00303             //    necessary.
00304             if ( !$fileOpened ) {
00305                 $this->assertNotEmpty( $files, "No more existing dump files, "
00306                     . "but not yet all pages found" );
00307                 $fname = array_shift( $files );
00308                 while ( $fname == "." || $fname == ".." ) {
00309                     $this->assertNotEmpty( $files, "No more existing dump"
00310                         . " files, but not yet all pages found" );
00311                     $fname = array_shift( $files );
00312                 }
00313                 if ( $checkpointFormat == "gzip" ) {
00314                     $this->gunzip( $nameOutputDir . "/" . $fname );
00315                 }
00316                 $this->assertDumpStart( $nameOutputDir . "/" . $fname );
00317                 $fileOpened = true;
00318                 $checkpointFiles++;
00319             }
00320 
00321             // 2. Performing a single page check
00322             switch ( $lookingForPage ) {
00323                 case 1:
00324                     // Page 1
00325                     $this->assertPageStart( $this->pageId1 + $i * self::$numOfPages, NS_MAIN,
00326                         "BackupDumperTestP1" );
00327                     $this->assertRevision( $this->revId1_1 + $i * self::$numOfRevs, "BackupDumperTestP1Summary1",
00328                         $this->textId1_1, false, "0bolhl6ol7i6x0e7yq91gxgaan39j87",
00329                         "BackupDumperTestP1Text1" );
00330                     $this->assertPageEnd();
00331 
00332                     $lookingForPage = 2;
00333                     break;
00334 
00335                 case 2:
00336                     // Page 2
00337                     $this->assertPageStart( $this->pageId2 + $i * self::$numOfPages, NS_MAIN,
00338                         "BackupDumperTestP2" );
00339                     $this->assertRevision( $this->revId2_1 + $i * self::$numOfRevs, "BackupDumperTestP2Summary1",
00340                         $this->textId2_1, false, "jprywrymfhysqllua29tj3sc7z39dl2",
00341                         "BackupDumperTestP2Text1" );
00342                     $this->assertRevision( $this->revId2_2 + $i * self::$numOfRevs, "BackupDumperTestP2Summary2",
00343                         $this->textId2_2, false, "b7vj5ks32po5m1z1t1br4o7scdwwy95",
00344                         "BackupDumperTestP2Text2", $this->revId2_1 + $i * self::$numOfRevs );
00345                     $this->assertRevision( $this->revId2_3 + $i * self::$numOfRevs, "BackupDumperTestP2Summary3",
00346                         $this->textId2_3, false, "jfunqmh1ssfb8rs43r19w98k28gg56r",
00347                         "BackupDumperTestP2Text3", $this->revId2_2 + $i * self::$numOfRevs );
00348                     $this->assertRevision( $this->revId2_4 + $i * self::$numOfRevs,
00349                         "BackupDumperTestP2Summary4 extra",
00350                         $this->textId2_4, false, "6o1ciaxa6pybnqprmungwofc4lv00wv",
00351                         "BackupDumperTestP2Text4 some additional Text",
00352                         $this->revId2_3 + $i * self::$numOfRevs );
00353                     $this->assertPageEnd();
00354 
00355                     $lookingForPage = 4;
00356                     break;
00357 
00358                 case 4:
00359                     // Page 4
00360                     $this->assertPageStart( $this->pageId4 + $i * self::$numOfPages, NS_TALK,
00361                         "Talk:BackupDumperTestP1" );
00362                     $this->assertRevision( $this->revId4_1 + $i * self::$numOfRevs,
00363                         "Talk BackupDumperTestP1 Summary1",
00364                         $this->textId4_1, false, "nktofwzd0tl192k3zfepmlzxoax1lpe",
00365                         "Talk about BackupDumperTestP1 Text1" );
00366                     $this->assertPageEnd();
00367 
00368                     $lookingForPage = 1;
00369 
00370                     // We dealt with the whole iteration.
00371                     $i++;
00372                     break;
00373 
00374                 default:
00375                     $this->fail( "Bad setting for lookingForPage ($lookingForPage)" );
00376             }
00377 
00378             // 3. Checking for the end of the current checkpoint file
00379             if ( $this->xml->nodeType == XMLReader::END_ELEMENT
00380                 && $this->xml->name == "mediawiki"
00381             ) {
00382                 $this->assertDumpEnd();
00383                 $fileOpened = false;
00384             }
00385         }
00386 
00387         // Assuring we completely read all files ...
00388         $this->assertFalse( $fileOpened, "Currently read file still open?" );
00389         $this->assertEmpty( $files, "Remaining unchecked files" );
00390 
00391         // ... and have dealt with more than one checkpoint file
00392         $this->assertGreaterThan(
00393             1,
00394             $checkpointFiles,
00395             "expected more than 1 checkpoint to have been created. "
00396                 . "Checkpoint interval is $checkpointAfter seconds, maybe your computer is too fast?"
00397         );
00398 
00399         $this->expectETAOutput();
00400     }
00401 
00405     function testCheckpointPlain() {
00406         $this->checkpointHelper();
00407     }
00408 
00421     function testCheckpointGzip() {
00422         $this->checkHasGzip();
00423         $this->checkpointHelper( "gzip" );
00424     }
00425 
00439     private function setUpStub( $fname = null, $iterations = 1 ) {
00440         if ( $fname === null ) {
00441             $fname = $this->getNewTempFile();
00442         }
00443         $header = '<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.7/" '
00444             . 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
00445             . 'xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.7/ '
00446             . 'http://www.mediawiki.org/xml/export-0.7.xsd" version="0.7" xml:lang="en">
00447   <siteinfo>
00448     <sitename>wikisvn</sitename>
00449     <base>http://localhost/wiki-svn/index.php/Main_Page</base>
00450     <generator>MediaWiki 1.21alpha</generator>
00451     <case>first-letter</case>
00452     <namespaces>
00453       <namespace key="-2" case="first-letter">Media</namespace>
00454       <namespace key="-1" case="first-letter">Special</namespace>
00455       <namespace key="0" case="first-letter" />
00456       <namespace key="1" case="first-letter">Talk</namespace>
00457       <namespace key="2" case="first-letter">User</namespace>
00458       <namespace key="3" case="first-letter">User talk</namespace>
00459       <namespace key="4" case="first-letter">Wikisvn</namespace>
00460       <namespace key="5" case="first-letter">Wikisvn talk</namespace>
00461       <namespace key="6" case="first-letter">File</namespace>
00462       <namespace key="7" case="first-letter">File talk</namespace>
00463       <namespace key="8" case="first-letter">MediaWiki</namespace>
00464       <namespace key="9" case="first-letter">MediaWiki talk</namespace>
00465       <namespace key="10" case="first-letter">Template</namespace>
00466       <namespace key="11" case="first-letter">Template talk</namespace>
00467       <namespace key="12" case="first-letter">Help</namespace>
00468       <namespace key="13" case="first-letter">Help talk</namespace>
00469       <namespace key="14" case="first-letter">Category</namespace>
00470       <namespace key="15" case="first-letter">Category talk</namespace>
00471     </namespaces>
00472   </siteinfo>
00473 ';
00474         $tail = '</mediawiki>
00475 ';
00476 
00477         $content = $header;
00478         $iterations = intval( $iterations );
00479         for ( $i = 0; $i < $iterations; $i++ ) {
00480 
00481             $page1 = '  <page>
00482     <title>BackupDumperTestP1</title>
00483     <ns>0</ns>
00484     <id>' . ( $this->pageId1 + $i * self::$numOfPages ) . '</id>
00485     <revision>
00486       <id>' . ( $this->revId1_1 + $i * self::$numOfRevs ) . '</id>
00487       <timestamp>2012-04-01T16:46:05Z</timestamp>
00488       <contributor>
00489         <ip>127.0.0.1</ip>
00490       </contributor>
00491       <comment>BackupDumperTestP1Summary1</comment>
00492       <sha1>0bolhl6ol7i6x0e7yq91gxgaan39j87</sha1>
00493       <model>wikitext</model>
00494       <format>text/x-wiki</format>
00495       <text id="' . $this->textId1_1 . '" bytes="23" />
00496     </revision>
00497   </page>
00498 ';
00499             $page2 = '  <page>
00500     <title>BackupDumperTestP2</title>
00501     <ns>0</ns>
00502     <id>' . ( $this->pageId2 + $i * self::$numOfPages ) . '</id>
00503     <revision>
00504       <id>' . ( $this->revId2_1 + $i * self::$numOfRevs ) . '</id>
00505       <timestamp>2012-04-01T16:46:05Z</timestamp>
00506       <contributor>
00507         <ip>127.0.0.1</ip>
00508       </contributor>
00509       <comment>BackupDumperTestP2Summary1</comment>
00510       <sha1>jprywrymfhysqllua29tj3sc7z39dl2</sha1>
00511       <model>wikitext</model>
00512       <format>text/x-wiki</format>
00513       <text id="' . $this->textId2_1 . '" bytes="23" />
00514     </revision>
00515     <revision>
00516       <id>' . ( $this->revId2_2 + $i * self::$numOfRevs ) . '</id>
00517       <parentid>' . ( $this->revId2_1 + $i * self::$numOfRevs ) . '</parentid>
00518       <timestamp>2012-04-01T16:46:05Z</timestamp>
00519       <contributor>
00520         <ip>127.0.0.1</ip>
00521       </contributor>
00522       <comment>BackupDumperTestP2Summary2</comment>
00523       <sha1>b7vj5ks32po5m1z1t1br4o7scdwwy95</sha1>
00524       <model>wikitext</model>
00525       <format>text/x-wiki</format>
00526       <text id="' . $this->textId2_2 . '" bytes="23" />
00527     </revision>
00528     <revision>
00529       <id>' . ( $this->revId2_3 + $i * self::$numOfRevs ) . '</id>
00530       <parentid>' . ( $this->revId2_2 + $i * self::$numOfRevs ) . '</parentid>
00531       <timestamp>2012-04-01T16:46:05Z</timestamp>
00532       <contributor>
00533         <ip>127.0.0.1</ip>
00534       </contributor>
00535       <comment>BackupDumperTestP2Summary3</comment>
00536       <sha1>jfunqmh1ssfb8rs43r19w98k28gg56r</sha1>
00537       <model>wikitext</model>
00538       <format>text/x-wiki</format>
00539       <text id="' . $this->textId2_3 . '" bytes="23" />
00540     </revision>
00541     <revision>
00542       <id>' . ( $this->revId2_4 + $i * self::$numOfRevs ) . '</id>
00543       <parentid>' . ( $this->revId2_3 + $i * self::$numOfRevs ) . '</parentid>
00544       <timestamp>2012-04-01T16:46:05Z</timestamp>
00545       <contributor>
00546         <ip>127.0.0.1</ip>
00547       </contributor>
00548       <comment>BackupDumperTestP2Summary4 extra</comment>
00549       <sha1>6o1ciaxa6pybnqprmungwofc4lv00wv</sha1>
00550       <model>wikitext</model>
00551       <format>text/x-wiki</format>
00552       <text id="' . $this->textId2_4 . '" bytes="44" />
00553     </revision>
00554   </page>
00555 ';
00556             // page 3 not in stub
00557 
00558             $page4 = '  <page>
00559     <title>Talk:BackupDumperTestP1</title>
00560     <ns>1</ns>
00561     <id>' . ( $this->pageId4 + $i * self::$numOfPages ) . '</id>
00562     <revision>
00563       <id>' . ( $this->revId4_1 + $i * self::$numOfRevs ) . '</id>
00564       <timestamp>2012-04-01T16:46:05Z</timestamp>
00565       <contributor>
00566         <ip>127.0.0.1</ip>
00567       </contributor>
00568       <comment>Talk BackupDumperTestP1 Summary1</comment>
00569       <sha1>nktofwzd0tl192k3zfepmlzxoax1lpe</sha1>
00570       <model>wikitext</model>
00571       <format>text/x-wiki</format>
00572       <text id="' . $this->textId4_1 . '" bytes="35" />
00573     </revision>
00574   </page>
00575 ';
00576             $content .= $page1 . $page2 . $page4;
00577         }
00578         $content .= $tail;
00579         $this->assertEquals( strlen( $content ), file_put_contents(
00580             $fname, $content ), "Length of prepared stub" );
00581 
00582         return $fname;
00583     }
00584 }