backupTextPassTest.php 25 KB

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