backupTextPassTest.php 25 KB

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