if ( $text === false && isset( $this->prefetch ) && $prefetchNotTried ) {
$prefetchNotTried = false;
$tryIsPrefetch = true;
- $text = $this->prefetch->prefetch( $this->thisPage, $this->thisRev );
+ $text = $this->prefetch->prefetch( intval( $this->thisPage ),
+ intval( $this->thisRev ) );
if ( $text === null ) {
$text = false;
}
*
* Clears $wgUser, and reports errors from addDBData to PHPUnit
*/
- function setUp() {
+ protected function setUp() {
global $wgUser;
parent::setUp();
$wgUser = new User();
}
+ /**
+ * Checks for test output consisting only of lines containing ETA announcements
+ */
+ function expectETAOutput() {
+ // Newer PHPUnits require assertion about the output using PHPUnit's own
+ // expectOutput[...] functions. However, the PHPUnit shipped prediactes
+ // do not allow to check /each/ line of the output using /readable/ REs.
+ // So we ...
+ //
+ // 1. ... add a dummy output checking to make PHPUnit not complain
+ // about unchecked test output
+ $this->expectOutputRegex( '//' );
+
+ // 2. Do the real output checking on our own.
+ $lines = explode( "\n", $this->getActualOutput() );
+ $this->assertGreaterThan( 1, count( $lines ), "Minimal lines of produced output" );
+ $this->assertEquals( '', array_pop( $lines ), "Output ends in LF" );
+ $timestamp_re = "[0-9]{4}-[01][0-9]-[0-3][0-9] [0-2][0-9]:[0-5][0-9]:[0-6][0-9]";
+ foreach ( $lines as $line ) {
+ $this->assertRegExp( "/$timestamp_re: .* \(ID [0-9]+\) [0-9]* pages .*, [0-9]* revs .*, ETA/", $line );
+ }
+ }
+
+
/**
* Step the current XML reader until node end of given name is found.
*
--- /dev/null
+<?php
+global $IP;
+require_once( "$IP/maintenance/backupPrefetch.inc" );
+
+/**
+ * Tests for BaseDump
+ *
+ * @group Dump
+ */
+class BaseDumpTest extends MediaWikiTestCase {
+
+ /**
+ * @var BaseDump the BaseDump instance used within a test.
+ *
+ * If set, this BaseDump gets automatically closed in tearDown.
+ */
+ private $dump = null;
+
+ protected function tearDown() {
+ parent::tearDown();
+
+ if ( $this->dump !== null ) {
+ $this->dump->close();
+ }
+ }
+
+ /**
+ * asserts that a prefetch yields an expected string
+ *
+ * @param $expected string|null: the exepcted result of the prefetch
+ * @param $page int: the page number to prefetch the text for
+ * @param $revision int: the revision number to prefetch the text for
+ */
+ private function assertPrefetchEquals( $expected, $page, $revision ) {
+ $this->assertEquals( $expected, $this->dump->prefetch( $page, $revision ),
+ "Prefetch of page $page revision $revision" );
+
+ }
+
+ function testSequential() {
+ $fname = $this->setUpPrefetch();
+ $this->dump = new BaseDump( $fname );
+
+ $this->assertPrefetchEquals( "BackupDumperTestP1Text1", 1, 1 );
+ $this->assertPrefetchEquals( "BackupDumperTestP2Text1", 2, 2 );
+ $this->assertPrefetchEquals( "BackupDumperTestP2Text4 some additional Text", 2, 5 );
+ $this->assertPrefetchEquals( "Talk about BackupDumperTestP1 Text1", 4, 8 );
+ }
+
+ function testSynchronizeRevisionMissToRevision() {
+ $fname = $this->setUpPrefetch();
+ $this->dump = new BaseDump( $fname );
+
+ $this->assertPrefetchEquals( "BackupDumperTestP2Text1", 2, 2 );
+ $this->assertPrefetchEquals( null, 2, 3 );
+ $this->assertPrefetchEquals( "BackupDumperTestP2Text4 some additional Text", 2, 5 );
+ }
+
+ function testSynchronizeRevisionMissToPage() {
+ $fname = $this->setUpPrefetch();
+ $this->dump = new BaseDump( $fname );
+
+ $this->assertPrefetchEquals( "BackupDumperTestP2Text1", 2, 2 );
+ $this->assertPrefetchEquals( null, 2, 40 );
+ $this->assertPrefetchEquals( "Talk about BackupDumperTestP1 Text1", 4, 8 );
+ }
+
+ function testSynchronizePageMiss() {
+ $fname = $this->setUpPrefetch();
+ $this->dump = new BaseDump( $fname );
+
+ $this->assertPrefetchEquals( "BackupDumperTestP2Text1", 2, 2 );
+ $this->assertPrefetchEquals( null, 3, 40 );
+ $this->assertPrefetchEquals( "Talk about BackupDumperTestP1 Text1", 4, 8 );
+ }
+
+ function testPageMissAtEnd() {
+ $fname = $this->setUpPrefetch();
+ $this->dump = new BaseDump( $fname );
+
+ $this->assertPrefetchEquals( "BackupDumperTestP2Text1", 2, 2 );
+ $this->assertPrefetchEquals( null, 6, 40 );
+ }
+
+ function testRevisionMissAtEnd() {
+ $fname = $this->setUpPrefetch();
+ $this->dump = new BaseDump( $fname );
+
+ $this->assertPrefetchEquals( "BackupDumperTestP2Text1", 2, 2 );
+ $this->assertPrefetchEquals( null, 4, 40 );
+ }
+
+ function testSynchronizePageMissAtStart() {
+ $fname = $this->setUpPrefetch();
+ $this->dump = new BaseDump( $fname );
+
+ $this->assertPrefetchEquals( null, 0, 2 );
+ $this->assertPrefetchEquals( "BackupDumperTestP2Text1", 2, 2 );
+ }
+
+ function testSynchronizeRevisionMissAtStart() {
+ $fname = $this->setUpPrefetch();
+ $this->dump = new BaseDump( $fname );
+
+ $this->assertPrefetchEquals( null, 1, -2 );
+ $this->assertPrefetchEquals( "BackupDumperTestP2Text1", 2, 2 );
+ }
+
+ function testSequentialAcrossFiles() {
+ $fname1 = $this->setUpPrefetch( array( 1 ) );
+ $fname2 = $this->setUpPrefetch( array( 2, 4 ) );
+ $this->dump = new BaseDump( $fname1 . ";" . $fname2 );
+
+ $this->assertPrefetchEquals( "BackupDumperTestP1Text1", 1, 1 );
+ $this->assertPrefetchEquals( "BackupDumperTestP2Text1", 2, 2 );
+ $this->assertPrefetchEquals( "BackupDumperTestP2Text4 some additional Text", 2, 5 );
+ $this->assertPrefetchEquals( "Talk about BackupDumperTestP1 Text1", 4, 8 );
+ }
+
+ function testSynchronizeSkipAcrossFile() {
+ $fname1 = $this->setUpPrefetch( array( 1 ) );
+ $fname2 = $this->setUpPrefetch( array( 2 ) );
+ $fname3 = $this->setUpPrefetch( array( 4 ) );
+ $this->dump = new BaseDump( $fname1 . ";" . $fname2 . ";" . $fname3 );
+
+ $this->assertPrefetchEquals( "BackupDumperTestP1Text1", 1, 1 );
+ $this->assertPrefetchEquals( "Talk about BackupDumperTestP1 Text1", 4, 8 );
+ }
+
+ function testSynchronizeMissInWholeFirstFile() {
+ $fname1 = $this->setUpPrefetch( array( 1 ) );
+ $fname2 = $this->setUpPrefetch( array( 2 ) );
+ $this->dump = new BaseDump( $fname1 . ";" . $fname2 );
+
+ $this->assertPrefetchEquals( "BackupDumperTestP2Text1", 2, 2 );
+ }
+
+
+ /**
+ * Constructs a temporary file that can be used for prefetching
+ *
+ * The temporary file is removed by DumpBackup upon tearDown.
+ *
+ * @param $requested_pages Array The indices of the page parts that should
+ * go into the prefetch file. 1,2,4 are available.
+ * @return String The file name of the created temporary file
+ */
+ private function setUpPrefetch( $requested_pages = array( 1, 2, 4 ) ) {
+ // The file name, where we store the prepared prefetch file
+ $fname = $this->getNewTempFile();
+
+ // The header of every prefetch file
+ $header = '<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.6/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.6/ http://www.mediawiki.org/xml/export-0.6.xsd" version="0.6" xml:lang="en">
+ <siteinfo>
+ <sitename>wikisvn</sitename>
+ <base>http://localhost/wiki-svn/index.php/Main_Page</base>
+ <generator>MediaWiki 1.20alpha</generator>
+ <case>first-letter</case>
+ <namespaces>
+ <namespace key="-2" case="first-letter">Media</namespace>
+ <namespace key="-1" case="first-letter">Special</namespace>
+ <namespace key="0" case="first-letter" />
+ <namespace key="1" case="first-letter">Talk</namespace>
+ <namespace key="2" case="first-letter">User</namespace>
+ <namespace key="3" case="first-letter">User talk</namespace>
+ <namespace key="4" case="first-letter">Wikisvn</namespace>
+ <namespace key="5" case="first-letter">Wikisvn talk</namespace>
+ <namespace key="6" case="first-letter">File</namespace>
+ <namespace key="7" case="first-letter">File talk</namespace>
+ <namespace key="8" case="first-letter">MediaWiki</namespace>
+ <namespace key="9" case="first-letter">MediaWiki talk</namespace>
+ <namespace key="10" case="first-letter">Template</namespace>
+ <namespace key="11" case="first-letter">Template talk</namespace>
+ <namespace key="12" case="first-letter">Help</namespace>
+ <namespace key="13" case="first-letter">Help talk</namespace>
+ <namespace key="14" case="first-letter">Category</namespace>
+ <namespace key="15" case="first-letter">Category talk</namespace>
+ </namespaces>
+ </siteinfo>
+';
+
+
+ // An array holding the pages that are available for prefetch
+ $available_pages = array();
+
+ // Simple plain page
+ $available_pages[1] = ' <page>
+ <title>BackupDumperTestP1</title>
+ <ns>0</ns>
+ <id>1</id>
+ <revision>
+ <id>1</id>
+ <timestamp>2012-04-01T16:46:05Z</timestamp>
+ <contributor>
+ <ip>127.0.0.1</ip>
+ </contributor>
+ <comment>BackupDumperTestP1Summary1</comment>
+ <text xml:space="preserve">BackupDumperTestP1Text1</text>
+ <sha1>0bolhl6ol7i6x0e7yq91gxgaan39j87</sha1>
+ </revision>
+ </page>
+';
+ // Page with more than one revisions. Hole in rev ids.
+ $available_pages[2] = ' <page>
+ <title>BackupDumperTestP2</title>
+ <ns>0</ns>
+ <id>2</id>
+ <revision>
+ <id>2</id>
+ <timestamp>2012-04-01T16:46:05Z</timestamp>
+ <contributor>
+ <ip>127.0.0.1</ip>
+ </contributor>
+ <comment>BackupDumperTestP2Summary1</comment>
+ <text xml:space="preserve">BackupDumperTestP2Text1</text>
+ <sha1>jprywrymfhysqllua29tj3sc7z39dl2</sha1>
+ </revision>
+ <revision>
+ <id>5</id>
+ <timestamp>2012-04-01T16:46:05Z</timestamp>
+ <contributor>
+ <ip>127.0.0.1</ip>
+ </contributor>
+ <comment>BackupDumperTestP2Summary4 extra</comment>
+ <text xml:space="preserve">BackupDumperTestP2Text4 some additional Text</text>
+ <sha1>6o1ciaxa6pybnqprmungwofc4lv00wv</sha1>
+ </revision>
+ </page>
+';
+ // Page with id higher than previous id + 1
+ $available_pages[4] = ' <page>
+ <title>Talk:BackupDumperTestP1</title>
+ <ns>1</ns>
+ <id>4</id>
+ <revision>
+ <id>8</id>
+ <timestamp>2012-04-01T16:46:05Z</timestamp>
+ <contributor>
+ <ip>127.0.0.1</ip>
+ </contributor>
+ <comment>Talk BackupDumperTestP1 Summary1</comment>
+ <text xml:space="preserve">Talk about BackupDumperTestP1 Text1</text>
+ <sha1>nktofwzd0tl192k3zfepmlzxoax1lpe</sha1>
+ </revision>
+ </page>
+';
+
+ // The common ending for all files
+ $tail = '</mediawiki>
+';
+
+ // Putting together the content of the prefetch files
+ $content = $header;
+ foreach ( $requested_pages as $i ) {
+ $this->assertTrue( array_key_exists( $i, $available_pages ),
+ "Check for availability of requested page " . $i );
+ $content .= $available_pages[ $i ];
+ }
+ $content .= $tail;
+
+ $this->assertEquals( strlen( $content ), file_put_contents(
+ $fname, $content ), "Length of prepared prefetch" );
+
+ return $fname;
+ }
+
+}
--- /dev/null
+<?php
+global $IP;
+require_once( "$IP/maintenance/backup.inc" );
+require_once( "$IP/maintenance/backupTextPass.inc" );
+
+/**
+ * Tests for page dumps of BackupDumper
+ *
+ * @group Database
+ * @group Dump
+ */
+class TextPassDumperTest extends DumpTestCase {
+
+ // We'll add several pages, revision and texts. The following variables hold the
+ // corresponding ids.
+ private $pageId1, $pageId2, $pageId3, $pageId4, $pageId5;
+ private $revId1_1, $textId1_1;
+ private $revId2_1, $textId2_1, $revId2_2, $textId2_2;
+ private $revId2_3, $textId2_3, $revId2_4, $textId2_4;
+ private $revId3_1, $textId3_1, $revId3_2, $textId3_2;
+ private $revId4_1, $textId4_1;
+
+ function addDBData() {
+ $this->tablesUsed[] = 'page';
+ $this->tablesUsed[] = 'revision';
+ $this->tablesUsed[] = 'text';
+
+ try {
+ // Simple page
+ $title = Title::newFromText( 'BackupDumperTestP1' );
+ $page = WikiPage::factory( $title );
+ list( $this->revId1_1, $this->textId1_1 ) = $this->addRevision( $page,
+ "BackupDumperTestP1Text1", "BackupDumperTestP1Summary1" );
+ $this->pageId1 = $page->getId();
+
+ // Page with more than one revision
+ $title = Title::newFromText( 'BackupDumperTestP2' );
+ $page = WikiPage::factory( $title );
+ list( $this->revId2_1, $this->textId2_1 ) = $this->addRevision( $page,
+ "BackupDumperTestP2Text1", "BackupDumperTestP2Summary1" );
+ list( $this->revId2_2, $this->textId2_2 ) = $this->addRevision( $page,
+ "BackupDumperTestP2Text2", "BackupDumperTestP2Summary2" );
+ list( $this->revId2_3, $this->textId2_3 ) = $this->addRevision( $page,
+ "BackupDumperTestP2Text3", "BackupDumperTestP2Summary3" );
+ list( $this->revId2_4, $this->textId2_4 ) = $this->addRevision( $page,
+ "BackupDumperTestP2Text4 some additional Text ",
+ "BackupDumperTestP2Summary4 extra " );
+ $this->pageId2 = $page->getId();
+
+ // Deleted page.
+ $title = Title::newFromText( 'BackupDumperTestP3' );
+ $page = WikiPage::factory( $title );
+ list( $this->revId3_1, $this->textId3_1 ) = $this->addRevision( $page,
+ "BackupDumperTestP3Text1", "BackupDumperTestP2Summary1" );
+ list( $this->revId3_2, $this->textId3_2 ) = $this->addRevision( $page,
+ "BackupDumperTestP3Text2", "BackupDumperTestP2Summary2" );
+ $this->pageId3 = $page->getId();
+ $page->doDeleteArticle( "Testing ;)" );
+
+ // Page from non-default namespace
+ $title = Title::newFromText( 'BackupDumperTestP1', NS_TALK );
+ $page = WikiPage::factory( $title );
+ list( $this->revId4_1, $this->textId4_1 ) = $this->addRevision( $page,
+ "Talk about BackupDumperTestP1 Text1",
+ "Talk BackupDumperTestP1 Summary1" );
+ $this->pageId4 = $page->getId();
+ } catch ( Exception $e ) {
+ // We'd love to pass $e directly. However, ... see
+ // documentation of exceptionFromAddDBData in
+ // DumpTestCase
+ $this->exceptionFromAddDBData = $e;
+ }
+
+ }
+
+ protected function setUp() {
+ parent::setUp();
+
+ // Since we will restrict dumping by page ranges (to allow
+ // working tests, even if the db gets prepopulated by a base
+ // class), we have to assert, that the page id are consecutively
+ // increasing
+ $this->assertEquals(
+ array( $this->pageId2, $this->pageId3, $this->pageId4 ),
+ array( $this->pageId1 + 1, $this->pageId2 + 1, $this->pageId3 + 1 ),
+ "Page ids increasing without holes" );
+
+ }
+
+ function testPlain() {
+ // Setting up the dump
+ $nameStub = $this->setUpStub();
+ $nameFull = $this->getNewTempFile();
+ $dumper = new TextPassDumper( array ( "--stub=file:" . $nameStub,
+ "--output=file:" . $nameFull ) );
+ $dumper->reporting = false;
+ $dumper->setDb( $this->db );
+
+ // Performing the dump
+ $dumper->dump( WikiExporter::FULL, WikiExporter::TEXT );
+
+ // Checking for correctness of the dumped data
+ $this->assertDumpStart( $nameFull );
+
+ // Page 1
+ $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
+ $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
+ $this->textId1_1, false, "0bolhl6ol7i6x0e7yq91gxgaan39j87",
+ "BackupDumperTestP1Text1" );
+ $this->assertPageEnd();
+
+ // Page 2
+ $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
+ $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1",
+ $this->textId2_1, false, "jprywrymfhysqllua29tj3sc7z39dl2",
+ "BackupDumperTestP2Text1" );
+ $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2",
+ $this->textId2_2, false, "b7vj5ks32po5m1z1t1br4o7scdwwy95",
+ "BackupDumperTestP2Text2" );
+ $this->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3",
+ $this->textId2_3, false, "jfunqmh1ssfb8rs43r19w98k28gg56r",
+ "BackupDumperTestP2Text3" );
+ $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
+ $this->textId2_4, false, "6o1ciaxa6pybnqprmungwofc4lv00wv",
+ "BackupDumperTestP2Text4 some additional Text" );
+ $this->assertPageEnd();
+
+ // Page 3
+ // -> Page is marked deleted. Hence not visible
+
+ // Page 4
+ $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" );
+ $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
+ $this->textId4_1, false, "nktofwzd0tl192k3zfepmlzxoax1lpe",
+ "Talk about BackupDumperTestP1 Text1" );
+ $this->assertPageEnd();
+
+ $this->assertDumpEnd();
+ }
+
+ function testPrefetchPlain() {
+ // The mapping between ids and text, for the hits of the prefetch mock
+ $prefetchMap = array(
+ array( $this->pageId1, $this->revId1_1, "Prefetch_________1Text1" ),
+ array( $this->pageId2, $this->revId2_3, "Prefetch_________2Text3" )
+ );
+
+ // The mock itself
+ $prefetchMock = $this->getMock( 'BaseDump', array( 'prefetch' ), array(), '', FALSE );
+ $prefetchMock->expects( $this->exactly( 6 ) )
+ ->method( 'prefetch' )
+ ->will( $this->returnValueMap( $prefetchMap ) );
+
+ // Setting up of the dump
+ $nameStub = $this->setUpStub();
+ $nameFull = $this->getNewTempFile();
+ $dumper = new TextPassDumper( array ( "--stub=file:"
+ . $nameStub, "--output=file:" . $nameFull ) );
+ $dumper->prefetch = $prefetchMock;
+ $dumper->reporting = false;
+ $dumper->setDb( $this->db );
+
+ // Performing the dump
+ $dumper->dump( WikiExporter::FULL, WikiExporter::TEXT );
+
+ // Checking for correctness of the dumped data
+ $this->assertDumpStart( $nameFull );
+
+ // Page 1
+ $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
+ // Prefetch kicks in. This is still the SHA-1 of the original text,
+ // But the actual text (with different SHA-1) comes from prefetch.
+ $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
+ $this->textId1_1, false, "0bolhl6ol7i6x0e7yq91gxgaan39j87",
+ "Prefetch_________1Text1" );
+ $this->assertPageEnd();
+
+ // Page 2
+ $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
+ $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1",
+ $this->textId2_1, false, "jprywrymfhysqllua29tj3sc7z39dl2",
+ "BackupDumperTestP2Text1" );
+ $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2",
+ $this->textId2_2, false, "b7vj5ks32po5m1z1t1br4o7scdwwy95",
+ "BackupDumperTestP2Text2" );
+ // Prefetch kicks in. This is still the SHA-1 of the original text,
+ // But the actual text (with different SHA-1) comes from prefetch.
+ $this->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3",
+ $this->textId2_3, false, "jfunqmh1ssfb8rs43r19w98k28gg56r",
+ "Prefetch_________2Text3" );
+ $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
+ $this->textId2_4, false, "6o1ciaxa6pybnqprmungwofc4lv00wv",
+ "BackupDumperTestP2Text4 some additional Text" );
+ $this->assertPageEnd();
+
+ // Page 3
+ // -> Page is marked deleted. Hence not visible
+
+ // Page 4
+ $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" );
+ $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
+ $this->textId4_1, false, "nktofwzd0tl192k3zfepmlzxoax1lpe",
+ "Talk about BackupDumperTestP1 Text1" );
+ $this->assertPageEnd();
+
+ $this->assertDumpEnd();
+
+ }
+
+ /**
+ * Ensures that checkpoint dumps are used and written, by successively increasing the
+ * stub size and dumping until the duration crosses a threshold.
+ *
+ * @param $checkpointFormat string: Either "file" for plain text or "gzip" for gzipped
+ * checkpoint files.
+ */
+ private function checkpointHelper( $checkpointFormat = "file" ) {
+ // Getting temporary names
+ $nameStub = $this->getNewTempFile();
+ $nameOutputDir = $this->getNewTempDirectory();
+
+ $stderr = fopen( 'php://output', 'a' );
+ if ( $stderr === FALSE ) {
+ $this->fail( "Could not open stream for stderr" );
+ }
+
+ $iterations = 32; // We'll start with that many iterations of revisions in stub
+ $lastDuration = 0;
+ $minDuration = 2; // We want the dump to take at least this many seconds
+ $checkpointAfter = 0.5; // Generate checkpoint after this many seconds
+
+
+ // Until a dump takes at least $minDuration seconds, perform a dump and check
+ // duration. If the dump did not take long enough increase the iteration
+ // count, to generate a bigger stub file next time.
+ while ( $lastDuration < $minDuration ) {
+
+ // Setting up the dump
+ wfRecursiveRemoveDir( $nameOutputDir );
+ $this->assertTrue( wfMkdirParents( $nameOutputDir ),
+ "Creating temporary output directory " );
+ $this->setUpStub( $nameStub, $iterations );
+ $dumper = new TextPassDumper( array ( "--stub=file:" . $nameStub,
+ "--output=" . $checkpointFormat . ":" . $nameOutputDir . "/full",
+ "--maxtime=1" /*This is in minutes. Fixup is below*/,
+ "--checkpointfile=checkpoint-%s-%s.xml.gz" ) );
+ $dumper->setDb( $this->db );
+ $dumper->maxTimeAllowed = $checkpointAfter; // Patching maxTime from 1 minute
+ $dumper->stderr = $stderr;
+
+ // The actual dump and taking time
+ $ts_before = wfTime();
+ $dumper->dump( WikiExporter::FULL, WikiExporter::TEXT );
+ $ts_after = wfTime();
+ $lastDuration = $ts_after - $ts_before;
+
+ // Handling increasing the iteration count for the stubs
+ if ( $lastDuration < $minDuration ) {
+ $old_iterations = $iterations;
+ if ( $lastDuration > 0.2 ) {
+ // lastDuration is big enough, to allow an educated guess
+ $factor = ( $minDuration + 0.5 ) / $lastDuration;
+ if ( ( $factor > 1.1 ) && ( $factor < 100 ) ) {
+ // educated guess is reasonable
+ $iterations = (int)( $iterations * $factor );
+ }
+ }
+
+ if ( $old_iterations == $iterations ) {
+ // Heuristics were not applied, so we just *2.
+ $iterations *= 2;
+ }
+
+ $this->assertLessThan( 50000, $iterations,
+ "Emergency stop against infinitely increasing iteration "
+ . "count ( last duration: $lastDuration )" );
+ }
+ }
+
+ // The dump (hopefully) did take long enough to produce more than one
+ // checkpoint file.
+ //
+ // We now check all the checkpoint files for validity.
+
+ $files = scandir( $nameOutputDir );
+ $this->assertTrue( asort( $files ), "Sorting files in temporary directory" );
+ $fileOpened = false;
+ $lookingForPage = 1;
+ $checkpointFiles = 0;
+
+ // Each run of the following loop body tries to handle exactly 1 /page/ (not
+ // iteration of stub content). $i is only increased after having treated page 4.
+ for ( $i = 0 ; $i < $iterations ; ) {
+
+ // 1. Assuring a file is opened and ready. Skipping across header if
+ // necessary.
+ if ( ! $fileOpened ) {
+ $this->assertNotEmpty( $files, "No more existing dump files, "
+ . "but not yet all pages found" );
+ $fname = array_shift( $files );
+ while ( $fname == "." || $fname == ".." ) {
+ $this->assertNotEmpty( $files, "No more existing dump"
+ . " files, but not yet all pages found" );
+ $fname = array_shift( $files );
+ }
+ if ( $checkpointFormat == "gzip" ) {
+ $this->gunzip( $nameOutputDir . "/" . $fname );
+ }
+ $this->assertDumpStart( $nameOutputDir . "/" . $fname );
+ $fileOpened = true;
+ $checkpointFiles++;
+ }
+
+ // 2. Performing a single page check
+ switch ( $lookingForPage ) {
+ case 1:
+ // Page 1
+ $this->assertPageStart( $this->pageId1 + $i * 4, NS_MAIN,
+ "BackupDumperTestP1" );
+ $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
+ $this->textId1_1, false, "0bolhl6ol7i6x0e7yq91gxgaan39j87",
+ "BackupDumperTestP1Text1" );
+ $this->assertPageEnd();
+
+ $lookingForPage = 2;
+ break;
+
+ case 2:
+ // Page 2
+ $this->assertPageStart( $this->pageId2 + $i * 4, NS_MAIN,
+ "BackupDumperTestP2" );
+ $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1",
+ $this->textId2_1, false, "jprywrymfhysqllua29tj3sc7z39dl2",
+ "BackupDumperTestP2Text1" );
+ $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2",
+ $this->textId2_2, false, "b7vj5ks32po5m1z1t1br4o7scdwwy95",
+ "BackupDumperTestP2Text2" );
+ $this->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3",
+ $this->textId2_3, false, "jfunqmh1ssfb8rs43r19w98k28gg56r",
+ "BackupDumperTestP2Text3" );
+ $this->assertRevision( $this->revId2_4,
+ "BackupDumperTestP2Summary4 extra",
+ $this->textId2_4, false, "6o1ciaxa6pybnqprmungwofc4lv00wv",
+ "BackupDumperTestP2Text4 some additional Text" );
+ $this->assertPageEnd();
+
+ $lookingForPage = 4;
+ break;
+
+ case 4:
+ // Page 4
+ $this->assertPageStart( $this->pageId4 + $i * 4, NS_TALK,
+ "Talk:BackupDumperTestP1" );
+ $this->assertRevision( $this->revId4_1,
+ "Talk BackupDumperTestP1 Summary1",
+ $this->textId4_1, false, "nktofwzd0tl192k3zfepmlzxoax1lpe",
+ "Talk about BackupDumperTestP1 Text1" );
+ $this->assertPageEnd();
+
+ $lookingForPage = 1;
+
+ // We dealt with the whole iteration.
+ $i++;
+ break;
+
+ default:
+ $this->fail( "Bad setting for lookingForPage ($lookingForPage)" );
+ }
+
+ // 3. Checking for the end of the current checkpoint file
+ if ( $this->xml->nodeType == XMLReader::END_ELEMENT
+ && $this->xml->name == "mediawiki" ) {
+
+ $this->assertDumpEnd();
+ $fileOpened = false;
+ }
+ }
+
+ // Assuring we completely read all files ...
+ $this->assertFalse( $fileOpened, "Currently read file still open?" );
+ $this->assertEmpty( $files, "Remaining unchecked files" );
+
+ // ... and have dealt with more than one checkpoint file
+ $this->assertGreaterThan( 1, $checkpointFiles, "# of checkpoint files" );
+
+ $this->expectETAOutput();
+ }
+
+ /**
+ * @group large
+ */
+ function testCheckpointPlain() {
+ $this->checkpointHelper();
+ }
+
+ /**
+ * tests for working checkpoint generation in gzip format work.
+ *
+ * We keep this test in addition to the simpler self::testCheckpointPlain, as there
+ * were once problems when the used sinks were DumpPipeOutputs.
+ *
+ * xmldumps-backup typically uses bzip2 instead of gzip. However, as bzip2 requires
+ * PHP extensions, we go for gzip instead, which triggers the same relevant code
+ * paths while still being testable on more systems.
+ *
+ * @group large
+ */
+ function testCheckpointGzip() {
+ $this->checkpointHelper( "gzip" );
+ }
+
+
+ /**
+ * Creates a stub file that is used for testing the text pass of dumps
+ *
+ * @param $fname string: (Optional) Absolute name of the file to write
+ * the stub into. If this parameter is null, a new temporary
+ * file is generated that is automatically removed upon
+ * tearDown.
+ * @param $iterations integer: (Optional) specifies how often the block
+ * of 3 pages should go into the stub file. The page id
+ * increase further and further, while the revision and text
+ * ids of the first iteration are reused. The pages of
+ * iteration > 1 have no corresponding representation in the
+ * database.
+ * @return string absolute filename of the stub
+ */
+ private function setUpStub( $fname = null, $iterations = 1 ) {
+ if ( $fname === null ) {
+ $fname = $this->getNewTempFile();
+ }
+ $header = '<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.6/" '
+ . 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
+ . 'xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.6/ '
+ . 'http://www.mediawiki.org/xml/export-0.6.xsd" version="0.6" xml:lang="en">
+ <siteinfo>
+ <sitename>wikisvn</sitename>
+ <base>http://localhost/wiki-svn/index.php/Main_Page</base>
+ <generator>MediaWiki 1.20alpha</generator>
+ <case>first-letter</case>
+ <namespaces>
+ <namespace key="-2" case="first-letter">Media</namespace>
+ <namespace key="-1" case="first-letter">Special</namespace>
+ <namespace key="0" case="first-letter" />
+ <namespace key="1" case="first-letter">Talk</namespace>
+ <namespace key="2" case="first-letter">User</namespace>
+ <namespace key="3" case="first-letter">User talk</namespace>
+ <namespace key="4" case="first-letter">Wikisvn</namespace>
+ <namespace key="5" case="first-letter">Wikisvn talk</namespace>
+ <namespace key="6" case="first-letter">File</namespace>
+ <namespace key="7" case="first-letter">File talk</namespace>
+ <namespace key="8" case="first-letter">MediaWiki</namespace>
+ <namespace key="9" case="first-letter">MediaWiki talk</namespace>
+ <namespace key="10" case="first-letter">Template</namespace>
+ <namespace key="11" case="first-letter">Template talk</namespace>
+ <namespace key="12" case="first-letter">Help</namespace>
+ <namespace key="13" case="first-letter">Help talk</namespace>
+ <namespace key="14" case="first-letter">Category</namespace>
+ <namespace key="15" case="first-letter">Category talk</namespace>
+ </namespaces>
+ </siteinfo>
+';
+ $tail = '</mediawiki>
+';
+
+ $content = $header;
+ $iterations = intval( $iterations );
+ for ( $i = 0; $i < $iterations; $i++ ) {
+
+ $page1 = ' <page>
+ <title>BackupDumperTestP1</title>
+ <ns>0</ns>
+ <id>' . ( $this->pageId1 + $i * 4 ) . '</id>
+ <revision>
+ <id>' . $this->revId1_1 . '</id>
+ <timestamp>2012-04-01T16:46:05Z</timestamp>
+ <contributor>
+ <ip>127.0.0.1</ip>
+ </contributor>
+ <comment>BackupDumperTestP1Summary1</comment>
+ <text id="' . $this->textId1_1 . '" bytes="23" />
+ <sha1>0bolhl6ol7i6x0e7yq91gxgaan39j87</sha1>
+ </revision>
+ </page>
+';
+ $page2 = ' <page>
+ <title>BackupDumperTestP2</title>
+ <ns>0</ns>
+ <id>' . ( $this->pageId2 + $i * 4 ) . '</id>
+ <revision>
+ <id>' . $this->revId2_1 . '</id>
+ <timestamp>2012-04-01T16:46:05Z</timestamp>
+ <contributor>
+ <ip>127.0.0.1</ip>
+ </contributor>
+ <comment>BackupDumperTestP2Summary1</comment>
+ <text id="' . $this->textId2_1 . '" bytes="23" />
+ <sha1>jprywrymfhysqllua29tj3sc7z39dl2</sha1>
+ </revision>
+ <revision>
+ <id>' . $this->revId2_2 . '</id>
+ <timestamp>2012-04-01T16:46:05Z</timestamp>
+ <contributor>
+ <ip>127.0.0.1</ip>
+ </contributor>
+ <comment>BackupDumperTestP2Summary2</comment>
+ <text id="' . $this->textId2_2 . '" bytes="23" />
+ <sha1>b7vj5ks32po5m1z1t1br4o7scdwwy95</sha1>
+ </revision>
+ <revision>
+ <id>' . $this->revId2_3 . '</id>
+ <timestamp>2012-04-01T16:46:05Z</timestamp>
+ <contributor>
+ <ip>127.0.0.1</ip>
+ </contributor>
+ <comment>BackupDumperTestP2Summary3</comment>
+ <text id="' . $this->textId2_3 . '" bytes="23" />
+ <sha1>jfunqmh1ssfb8rs43r19w98k28gg56r</sha1>
+ </revision>
+ <revision>
+ <id>' . $this->revId2_4 . '</id>
+ <timestamp>2012-04-01T16:46:05Z</timestamp>
+ <contributor>
+ <ip>127.0.0.1</ip>
+ </contributor>
+ <comment>BackupDumperTestP2Summary4 extra</comment>
+ <text id="' . $this->textId2_4 . '" bytes="44" />
+ <sha1>6o1ciaxa6pybnqprmungwofc4lv00wv</sha1>
+ </revision>
+ </page>
+';
+ // page 3 not in stub
+
+ $page4 = ' <page>
+ <title>Talk:BackupDumperTestP1</title>
+ <ns>1</ns>
+ <id>' . ( $this->pageId4 + $i * 4 ) . '</id>
+ <revision>
+ <id>' . $this->revId4_1 . '</id>
+ <timestamp>2012-04-01T16:46:05Z</timestamp>
+ <contributor>
+ <ip>127.0.0.1</ip>
+ </contributor>
+ <comment>Talk BackupDumperTestP1 Summary1</comment>
+ <text id="' . $this->textId4_1 . '" bytes="35" />
+ <sha1>nktofwzd0tl192k3zfepmlzxoax1lpe</sha1>
+ </revision>
+ </page>
+';
+ $content .= $page1 . $page2 . $page4;
+ }
+ $content .= $tail;
+ $this->assertEquals( strlen( $content ), file_put_contents(
+ $fname, $content ), "Length of prepared stub" );
+ return $fname;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Tests for log dumps of BackupDumper
+ *
+ * @group Database
+ * @group Dump
+ */
+class BackupDumperLoggerTest extends DumpTestCase {
+
+
+ // We'll add several log entries and users for this test. The following
+ // variables hold the corresponding ids.
+ private $userId1, $userId2;
+ private $logId1, $logId2, $logId3;
+
+ /**
+ * adds a log entry to the database.
+ *
+ * @param $type string: type of the log entry
+ * @param $subtype string: subtype of the log entry
+ * @param $user User: user that performs the logged operation
+ * @param $ns int: number of the namespace for the entry's target's title
+ * @param $title string: title of the entry's target
+ * @param $comment string: comment of the log entry
+ * @param $parameters Array: (optional) accompanying data that is attached
+ * to the entry
+ *
+ * @return int id of the added log entry
+ */
+ private function addLogEntry( $type, $subtype, User $user, $ns, $title,
+ $comment = null, $parameters = null ) {
+
+ $logEntry = new ManualLogEntry( $type, $subtype );
+ $logEntry->setPerformer( $user );
+ $logEntry->setTarget( Title::newFromText( $title, $ns ) );
+ if ( $comment !== null ) {
+ $logEntry->setComment( $comment );
+ }
+ if ( $parameters !== null ) {
+ $logEntry->setParameters( $parameters );
+ }
+ return $logEntry->insert();
+ }
+
+ function addDBData() {
+ $this->tablesUsed[] = 'logging';
+ $this->tablesUsed[] = 'user';
+
+ try {
+ $user1 = User::newFromName( 'BackupDumperLogUserA' );
+ $this->userId1 = $user1->getId();
+ if ( $this->userId1 === 0 ) {
+ $user1->addToDatabase();
+ $this->userId1 = $user1->getId();
+ }
+ $this->assertGreaterThan( 0, $this->userId1 );
+
+ $user2 = User::newFromName( 'BackupDumperLogUserB' );
+ $this->userId2 = $user2->getId();
+ if ( $this->userId2 === 0 ) {
+ $user2->addToDatabase();
+ $this->userId2 = $user2->getId();
+ }
+ $this->assertGreaterThan( 0, $this->userId2 );
+
+ $this->logId1 = $this->addLogEntry( 'type', 'subtype',
+ $user1, NS_MAIN, "PageA" );
+ $this->assertGreaterThan( 0, $this->logId1 );
+
+ $this->logId2 = $this->addLogEntry( 'supress', 'delete',
+ $user2, NS_TALK, "PageB", "SomeComment" );
+ $this->assertGreaterThan( 0, $this->logId2 );
+
+ $this->logId3 = $this->addLogEntry( 'move', 'delete',
+ $user2, NS_MAIN, "PageA", "SomeOtherComment",
+ array( 'key1' => 1, 3 => 'value3' ) );
+ $this->assertGreaterThan( 0, $this->logId3 );
+
+ } catch ( Exception $e ) {
+ // We'd love to pass $e directly. However, ... see
+ // documentation of exceptionFromAddDBData in
+ // DumpTestCase
+ $this->exceptionFromAddDBData = $e;
+ }
+
+ }
+
+
+ /**
+ * asserts that the xml reader is at the beginning of a log entry and skips over
+ * it while analyzing it.
+ *
+ * @param $id int: id of the log entry
+ * @param $user_name string: user name of the log entry's performer
+ * @param $user_id int: user id of the log entry 's performer
+ * @param $comment string|null: comment of the log entry. If null, the comment
+ * text is ignored.
+ * @param $type string: type of the log entry
+ * @param $subtype string: subtype of the log entry
+ * @param $title string: title of the log entry's target
+ * @param $parameters array: (optional) unserialized data accompanying the log entry
+ */
+ private function assertLogItem( $id, $user_name, $user_id, $comment, $type,
+ $subtype, $title, $parameters = array() ) {
+
+ $this->assertNodeStart( "logitem" );
+ $this->skipWhitespace();
+
+ $this->assertTextNode( "id", $id );
+ $this->assertTextNode( "timestamp", false );
+
+ $this->assertNodeStart( "contributor" );
+ $this->skipWhitespace();
+ $this->assertTextNode( "username", $user_name );
+ $this->assertTextNode( "id", $user_id );
+ $this->assertNodeEnd( "contributor" );
+ $this->skipWhitespace();
+
+ if ( $comment !== null ) {
+ $this->assertTextNode( "comment", $comment );
+ }
+ $this->assertTextNode( "type", $type );
+ $this->assertTextNode( "action", $subtype );
+ $this->assertTextNode( "logtitle", $title );
+
+ $this->assertNodeStart( "params" );
+ $parameters_xml = unserialize( $this->xml->value );
+ $this->assertEquals( $parameters, $parameters_xml );
+ $this->assertTrue( $this->xml->read(), "Skipping past processed text of params" );
+ $this->assertNodeEnd( "params" );
+ $this->skipWhitespace();
+
+ $this->assertNodeEnd( "logitem" );
+ $this->skipWhitespace();
+ }
+
+ function testPlain () {
+ global $wgContLang;
+
+ // Preparing the dump
+ $fname = $this->getNewTempFile();
+ $dumper = new BackupDumper( array ( "--output=file:" . $fname ) );
+ $dumper->startId = $this->logId1;
+ $dumper->endId = $this->logId3 + 1;
+ $dumper->reporting = false;
+ $dumper->setDb( $this->db );
+
+ // Performing the dump
+ $dumper->dump( WikiExporter::LOGS, WikiExporter::TEXT );
+
+ // Analyzing the dumped data
+ $this->assertDumpStart( $fname );
+
+ $this->assertLogItem( $this->logId1, "BackupDumperLogUserA",
+ $this->userId1, null, "type", "subtype", "PageA" );
+
+ $this->assertNotNull( $wgContLang, "Content language object validation" );
+ $namespace = $wgContLang->getNsText( NS_TALK );
+ $this->assertInternalType( 'string', $namespace );
+ $this->assertGreaterThan( 0, strlen( $namespace ) );
+ $this->assertLogItem( $this->logId2, "BackupDumperLogUserB",
+ $this->userId2, "SomeComment", "supress", "delete",
+ $namespace . ":PageB" );
+
+ $this->assertLogItem( $this->logId3, "BackupDumperLogUserB",
+ $this->userId2, "SomeOtherComment", "move", "delete",
+ "PageA", array( 'key1' => 1, 3 => 'value3' ) );
+
+ $this->assertDumpEnd();
+ }
+
+ function testXmlDumpsBackupUseCaseLogging() {
+ global $wgContLang;
+
+ // Preparing the dump
+ $fname = $this->getNewTempFile();
+ $dumper = new BackupDumper( array ( "--output=gzip:" . $fname,
+ "--reporting=2" ) );
+ $dumper->startId = $this->logId1;
+ $dumper->endId = $this->logId3 + 1;
+ $dumper->setDb( $this->db );
+
+ // xmldumps-backup demands reporting, although this is currently not
+ // implemented in BackupDumper, when dumping logging data. We
+ // nevertheless capture the output of the dump process already now,
+ // to be able to alert (once dumping produces reports) that this test
+ // needs updates.
+ $dumper->stderr = fopen( 'php://output', 'a' );
+ if ( $dumper->stderr === FALSE ) {
+ $this->fail( "Could not open stream for stderr" );
+ }
+
+ // Performing the dump
+ $dumper->dump( WikiExporter::LOGS, WikiExporter::TEXT );
+
+ $this->assertTrue( fclose( $dumper->stderr ), "Closing stderr handle" );
+
+ // Analyzing the dumped data
+ $this->gunzip( $fname );
+
+ $this->assertDumpStart( $fname );
+
+ $this->assertLogItem( $this->logId1, "BackupDumperLogUserA",
+ $this->userId1, null, "type", "subtype", "PageA" );
+
+ $this->assertNotNull( $wgContLang, "Content language object validation" );
+ $namespace = $wgContLang->getNsText( NS_TALK );
+ $this->assertInternalType( 'string', $namespace );
+ $this->assertGreaterThan( 0, strlen( $namespace ) );
+ $this->assertLogItem( $this->logId2, "BackupDumperLogUserB",
+ $this->userId2, "SomeComment", "supress", "delete",
+ $namespace . ":PageB" );
+
+ $this->assertLogItem( $this->logId3, "BackupDumperLogUserB",
+ $this->userId2, "SomeOtherComment", "move", "delete",
+ "PageA", array( 'key1' => 1, 3 => 'value3' ) );
+
+ $this->assertDumpEnd();
+
+ // Currently, no reporting is implemented. Alert via failure, once
+ // this changes.
+ // If reporting for log dumps has been implemented, please update
+ // the following statement to catch good output
+ $this->expectOutputString( '' );
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Tests for page dumps of BackupDumper
+ *
+ * @group Database
+ * @group Dump
+ */
+class BackupDumperPageTest extends DumpTestCase {
+
+ // We'll add several pages, revision and texts. The following variables hold the
+ // corresponding ids.
+ private $pageId1, $pageId2, $pageId3, $pageId4, $pageId5;
+ private $revId1_1, $textId1_1;
+ private $revId2_1, $textId2_1, $revId2_2, $textId2_2;
+ private $revId2_3, $textId2_3, $revId2_4, $textId2_4;
+ private $revId3_1, $textId3_1, $revId3_2, $textId3_2;
+ private $revId4_1, $textId4_1;
+
+ function addDBData() {
+ $this->tablesUsed[] = 'page';
+ $this->tablesUsed[] = 'revision';
+ $this->tablesUsed[] = 'text';
+
+ try {
+ $title = Title::newFromText( 'BackupDumperTestP1' );
+ $page = WikiPage::factory( $title );
+ list( $this->revId1_1, $this->textId1_1 ) = $this->addRevision( $page,
+ "BackupDumperTestP1Text1", "BackupDumperTestP1Summary1" );
+ $this->pageId1 = $page->getId();
+
+ $title = Title::newFromText( 'BackupDumperTestP2' );
+ $page = WikiPage::factory( $title );
+ list( $this->revId2_1, $this->textId2_1 ) = $this->addRevision( $page,
+ "BackupDumperTestP2Text1", "BackupDumperTestP2Summary1" );
+ list( $this->revId2_2, $this->textId2_2 ) = $this->addRevision( $page,
+ "BackupDumperTestP2Text2", "BackupDumperTestP2Summary2" );
+ list( $this->revId2_3, $this->textId2_3 ) = $this->addRevision( $page,
+ "BackupDumperTestP2Text3", "BackupDumperTestP2Summary3" );
+ list( $this->revId2_4, $this->textId2_4 ) = $this->addRevision( $page,
+ "BackupDumperTestP2Text4 some additional Text ",
+ "BackupDumperTestP2Summary4 extra " );
+ $this->pageId2 = $page->getId();
+
+ $title = Title::newFromText( 'BackupDumperTestP3' );
+ $page = WikiPage::factory( $title );
+ list( $this->revId3_1, $this->textId3_1 ) = $this->addRevision( $page,
+ "BackupDumperTestP3Text1", "BackupDumperTestP2Summary1" );
+ list( $this->revId3_2, $this->textId3_2 ) = $this->addRevision( $page,
+ "BackupDumperTestP3Text2", "BackupDumperTestP2Summary2" );
+ $this->pageId3 = $page->getId();
+ $page->doDeleteArticle( "Testing ;)" );
+
+ $title = Title::newFromText( 'BackupDumperTestP1', NS_TALK );
+ $page = WikiPage::factory( $title );
+ list( $this->revId4_1, $this->textId4_1 ) = $this->addRevision( $page,
+ "Talk about BackupDumperTestP1 Text1",
+ "Talk BackupDumperTestP1 Summary1" );
+ $this->pageId4 = $page->getId();
+ } catch ( Exception $e ) {
+ // We'd love to pass $e directly. However, ... see
+ // documentation of exceptionFromAddDBData in
+ // DumpTestCase
+ $this->exceptionFromAddDBData = $e;
+ }
+
+ }
+
+ protected function setUp() {
+ parent::setUp();
+
+ // Since we will restrict dumping by page ranges (to allow
+ // working tests, even if the db gets prepopulated by a base
+ // class), we have to assert, that the page id are consecutively
+ // increasing
+ $this->assertEquals(
+ array( $this->pageId2, $this->pageId3, $this->pageId4 ),
+ array( $this->pageId1 + 1, $this->pageId2 + 1, $this->pageId3 + 1 ),
+ "Page ids increasing without holes" );
+
+ }
+
+ function testFullTextPlain () {
+ // Preparing the dump
+ $fname = $this->getNewTempFile();
+ $dumper = new BackupDumper( array ( "--output=file:" . $fname ) );
+ $dumper->startId = $this->pageId1;
+ $dumper->endId = $this->pageId4 + 1;
+ $dumper->reporting = false;
+ $dumper->setDb( $this->db );
+
+ // Performing the dump
+ $dumper->dump( WikiExporter::FULL, WikiExporter::TEXT );
+
+ // Checking the dumped data
+ $this->assertDumpStart( $fname );
+
+ // Page 1
+ $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
+ $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
+ $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87",
+ "BackupDumperTestP1Text1" );
+ $this->assertPageEnd();
+
+ // Page 2
+ $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
+ $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1",
+ $this->textId2_1, 23, "jprywrymfhysqllua29tj3sc7z39dl2",
+ "BackupDumperTestP2Text1" );
+ $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2",
+ $this->textId2_2, 23, "b7vj5ks32po5m1z1t1br4o7scdwwy95",
+ "BackupDumperTestP2Text2" );
+ $this->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3",
+ $this->textId2_3, 23, "jfunqmh1ssfb8rs43r19w98k28gg56r",
+ "BackupDumperTestP2Text3" );
+ $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
+ $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv",
+ "BackupDumperTestP2Text4 some additional Text" );
+ $this->assertPageEnd();
+
+ // Page 3
+ // -> Page is marked deleted. Hence not visible
+
+ // Page 4
+ $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" );
+ $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
+ $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe",
+ "Talk about BackupDumperTestP1 Text1" );
+ $this->assertPageEnd();
+
+ $this->assertDumpEnd();
+ }
+
+ function testFullStubPlain () {
+ // Preparing the dump
+ $fname = $this->getNewTempFile();
+ $dumper = new BackupDumper( array ( "--output=file:" . $fname ) );
+ $dumper->startId = $this->pageId1;
+ $dumper->endId = $this->pageId4 + 1;
+ $dumper->reporting = false;
+ $dumper->setDb( $this->db );
+
+ // Performing the dump
+ $dumper->dump( WikiExporter::FULL, WikiExporter::STUB );
+
+ // Checking the dumped data
+ $this->assertDumpStart( $fname );
+
+ // Page 1
+ $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
+ $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
+ $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" );
+ $this->assertPageEnd();
+
+ // Page 2
+ $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
+ $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1",
+ $this->textId2_1, 23, "jprywrymfhysqllua29tj3sc7z39dl2" );
+ $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2",
+ $this->textId2_2, 23, "b7vj5ks32po5m1z1t1br4o7scdwwy95" );
+ $this->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3",
+ $this->textId2_3, 23, "jfunqmh1ssfb8rs43r19w98k28gg56r" );
+ $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
+ $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv" );
+ $this->assertPageEnd();
+
+ // Page 3
+ // -> Page is marked deleted. Hence not visible
+
+ // Page 4
+ $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" );
+ $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
+ $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" );
+ $this->assertPageEnd();
+
+ $this->assertDumpEnd();
+ }
+
+ function testCurrentStubPlain () {
+ // Preparing the dump
+ $fname = $this->getNewTempFile();
+ $dumper = new BackupDumper( array ( "--output=file:" . $fname ) );
+ $dumper->startId = $this->pageId1;
+ $dumper->endId = $this->pageId4 + 1;
+ $dumper->reporting = false;
+ $dumper->setDb( $this->db );
+
+ // Performing the dump
+ $dumper->dump( WikiExporter::CURRENT, WikiExporter::STUB );
+
+ // Checking the dumped data
+ $this->assertDumpStart( $fname );
+
+ // Page 1
+ $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
+ $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
+ $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" );
+ $this->assertPageEnd();
+
+ // Page 2
+ $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
+ $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
+ $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv" );
+ $this->assertPageEnd();
+
+ // Page 3
+ // -> Page is marked deleted. Hence not visible
+
+ // Page 4
+ $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" );
+ $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
+ $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" );
+ $this->assertPageEnd();
+
+ $this->assertDumpEnd();
+ }
+
+ function testCurrentStubGzip () {
+ // Preparing the dump
+ $fname = $this->getNewTempFile();
+ $dumper = new BackupDumper( array ( "--output=gzip:" . $fname ) );
+ $dumper->startId = $this->pageId1;
+ $dumper->endId = $this->pageId4 + 1;
+ $dumper->reporting = false;
+ $dumper->setDb( $this->db );
+
+ // Performing the dump
+ $dumper->dump( WikiExporter::CURRENT, WikiExporter::STUB );
+
+ // Checking the dumped data
+ $this->gunzip( $fname );
+ $this->assertDumpStart( $fname );
+
+ // Page 1
+ $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
+ $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
+ $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" );
+ $this->assertPageEnd();
+
+ // Page 2
+ $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
+ $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
+ $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv" );
+ $this->assertPageEnd();
+
+ // Page 3
+ // -> Page is marked deleted. Hence not visible
+
+ // Page 4
+ $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" );
+ $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
+ $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" );
+ $this->assertPageEnd();
+
+ $this->assertDumpEnd();
+ }
+
+
+
+ function testXmlDumpsBackupUseCase () {
+ // xmldumps-backup typically performs a single dump that that writes
+ // out three files
+ // * gzipped stubs of everything (meta-history)
+ // * gzipped stubs of latest revisions of all pages (meta-current)
+ // * gzipped stubs of latest revisions of all pages of namespage 0
+ // (articles)
+ //
+ // We reproduce such a setup with our mini fixture, although we omit
+ // chunks, and all the other gimmicks of xmldumps-backup.
+ //
+ $fnameMetaHistory = $this->getNewTempFile();
+ $fnameMetaCurrent = $this->getNewTempFile();
+ $fnameArticles = $this->getNewTempFile();
+
+ $dumper = new BackupDumper( array ( "--output=gzip:" . $fnameMetaHistory,
+ "--output=gzip:" . $fnameMetaCurrent, "--filter=latest",
+ "--output=gzip:" . $fnameArticles, "--filter=latest",
+ "--filter=notalk", "--filter=namespace:!NS_USER",
+ "--reporting=1000" ) );
+ $dumper->startId = $this->pageId1;
+ $dumper->endId = $this->pageId4 + 1;
+ $dumper->setDb( $this->db );
+
+ // xmldumps-backup uses reporting. We will not check the exact reported
+ // message, as they are dependent on the processing power of the used
+ // computer. We only check that reporting does not crash the dumping
+ // and that something is reported
+ $dumper->stderr = fopen( 'php://output', 'a' );
+ if ( $dumper->stderr === FALSE ) {
+ $this->fail( "Could not open stream for stderr" );
+ }
+
+ // Performing the dump
+ $dumper->dump( WikiExporter::FULL, WikiExporter::STUB );
+
+ $this->assertTrue( fclose( $dumper->stderr ), "Closing stderr handle" );
+
+ // Checking meta-history -------------------------------------------------
+
+ $this->gunzip( $fnameMetaHistory );
+ $this->assertDumpStart( $fnameMetaHistory );
+
+ // Page 1
+ $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
+ $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
+ $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" );
+ $this->assertPageEnd();
+
+ // Page 2
+ $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
+ $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1",
+ $this->textId2_1, 23, "jprywrymfhysqllua29tj3sc7z39dl2" );
+ $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2",
+ $this->textId2_2, 23, "b7vj5ks32po5m1z1t1br4o7scdwwy95" );
+ $this->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3",
+ $this->textId2_3, 23, "jfunqmh1ssfb8rs43r19w98k28gg56r" );
+ $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
+ $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv" );
+ $this->assertPageEnd();
+
+ // Page 3
+ // -> Page is marked deleted. Hence not visible
+
+ // Page 4
+ $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" );
+ $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
+ $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" );
+ $this->assertPageEnd();
+
+ $this->assertDumpEnd();
+
+ // Checking meta-current -------------------------------------------------
+
+ $this->gunzip( $fnameMetaCurrent );
+ $this->assertDumpStart( $fnameMetaCurrent );
+
+ // Page 1
+ $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
+ $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
+ $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" );
+ $this->assertPageEnd();
+
+ // Page 2
+ $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
+ $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
+ $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv" );
+ $this->assertPageEnd();
+
+ // Page 3
+ // -> Page is marked deleted. Hence not visible
+
+ // Page 4
+ $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" );
+ $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
+ $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" );
+ $this->assertPageEnd();
+
+ $this->assertDumpEnd();
+
+ // Checking articles -------------------------------------------------
+
+ $this->gunzip( $fnameArticles );
+ $this->assertDumpStart( $fnameArticles );
+
+ // Page 1
+ $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
+ $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
+ $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" );
+ $this->assertPageEnd();
+
+ // Page 2
+ $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
+ $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
+ $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv" );
+ $this->assertPageEnd();
+
+ // Page 3
+ // -> Page is marked deleted. Hence not visible
+
+ // Page 4
+ // -> Page is not in NS_MAIN. Hence not visible
+
+ $this->assertDumpEnd();
+
+ $this->expectETAOutput();
+ }
+
+
+
+}
--- /dev/null
+<?php
+global $IP;
+require_once( "$IP/maintenance/fetchText.php" );
+
+
+/**
+ * Mock for the input/output of FetchText
+ *
+ * FetchText internally tries to access stdin and stdout. We mock those aspects
+ * for testing.
+ */
+class SemiMockedFetchText extends FetchText {
+
+ /**
+ * @var String|null Text to pass as stdin
+ */
+ private $mockStdinText = null;
+
+ /**
+ * @var bool Whether or not a text for stdin has been provided
+ */
+ private $mockSetUp = False;
+
+ /**
+ * @var Array Invocation counters for the mocked aspects
+ */
+ private $mockInvocations = array( 'getStdin' => 0 );
+
+
+
+ /**
+ * Data for the fake stdin
+ *
+ * @param $stdin String The string to be used instead of stdin
+ */
+ function mockStdin( $stdin )
+ {
+ $this->mockStdinText = $stdin;
+ $this->mockSetUp = True;
+ }
+
+ /**
+ * Gets invocation counters for mocked methods.
+ *
+ * @return Array An array, whose keys are function names. The corresponding values
+ * denote the number of times the function has been invoked.
+ */
+ function mockGetInvocations()
+ {
+ return $this->mockInvocations;
+ }
+
+ // -----------------------------------------------------------------
+ // Mocked functions from FetchText follow.
+
+ function getStdin( $len = null )
+ {
+ $this->mockInvocations['getStdin']++;
+ if ( $len !== null ) {
+ throw new PHPUnit_Framework_ExpectationFailedException(
+ "Tried to get stdin with non null parameter" );
+ }
+
+ if ( ! $this->mockSetUp ) {
+ throw new PHPUnit_Framework_ExpectationFailedException(
+ "Tried to get stdin before setting up rerouting" );
+ }
+
+ return fopen( 'data://text/plain,' . $this->mockStdinText, 'r' );
+ }
+
+}
+
+/**
+ * TestCase for FetchText
+ *
+ * @group Database
+ * @group Dump
+ */
+class FetchTextTest extends MediaWikiTestCase {
+
+ // We add 5 Revisions for this test. Their corresponding text id's
+ // are stored in the following 5 variables.
+ private $textId1;
+ private $textId2;
+ private $textId3;
+ private $textId4;
+ private $textId5;
+
+
+ /**
+ * @var Exception|null As the current MediaWikiTestCase::run is not
+ * robust enough to recover from thrown exceptions directly, we cannot
+ * throw frow within addDBData, although it would be appropriate. Hence,
+ * we catch the exception and store it until we are in setUp and may
+ * finally rethrow the exception without crashing the test suite.
+ */
+ private $exceptionFromAddDBData;
+
+ /**
+ * @var FetchText the (mocked) FetchText that is to test
+ */
+ private $fetchText;
+
+ /**
+ * Adds a revision to a page, while returning the resuting text's id
+ *
+ * @param $page WikiPage The page to add the revision to
+ * @param $text String The revisions text
+ * @param $text String The revisions summare
+ *
+ * @throws MWExcepion
+ */
+ private function addRevision( $page, $text, $summary ) {
+ $status = $page->doEdit( $text, $summary );
+ if ( $status->isGood() ) {
+ $value = $status->getValue();
+ $revision = $value['revision'];
+ $id = $revision->getTextId();
+ if ( $id > 0 ) {
+ return $id;
+ }
+ }
+ throw new MWException( "Could not determine text id" );
+ }
+
+
+ function addDBData() {
+ $this->tablesUsed[] = 'page';
+ $this->tablesUsed[] = 'revision';
+ $this->tablesUsed[] = 'text';
+
+ try {
+ $title = Title::newFromText( 'FetchTextTestPage1' );
+ $page = WikiPage::factory( $title );
+ $this->textId1 = $this->addRevision( $page, "FetchTextTestPage1Text1", "FetchTextTestPage1Summary1" );
+
+ $title = Title::newFromText( 'FetchTextTestPage2' );
+ $page = WikiPage::factory( $title );
+ $this->textId2 = $this->addRevision( $page, "FetchTextTestPage2Text1", "FetchTextTestPage2Summary1" );
+ $this->textId3 = $this->addRevision( $page, "FetchTextTestPage2Text2", "FetchTextTestPage2Summary2" );
+ $this->textId4 = $this->addRevision( $page, "FetchTextTestPage2Text3", "FetchTextTestPage2Summary3" );
+ $this->textId5 = $this->addRevision( $page, "FetchTextTestPage2Text4 some additional Text ", "FetchTextTestPage2Summary4 extra " );
+ } catch ( Exception $e ) {
+ // We'd love to pass $e directly. However, ... see
+ // documentation of exceptionFromAddDBData
+ $this->exceptionFromAddDBData = $e;
+ }
+ }
+
+
+ protected function setUp() {
+ parent::setUp();
+
+ // Check if any Exception is stored for rethrowing from addDBData
+ if ( $this->exceptionFromAddDBData !== null ) {
+ throw $this->exceptionFromAddDBData;
+ }
+
+ $this->fetchText = new SemiMockedFetchText();
+ }
+
+
+ /**
+ * Helper to relate FetchText's input and output
+ */
+ private function assertFilter( $input, $expectedOutput ) {
+ $this->fetchText->mockStdin( $input );
+ $this->fetchText->execute();
+ $invocations = $this->fetchText->mockGetInvocations();
+ $this->assertEquals( 1, $invocations['getStdin'],
+ "getStdin invocation counter" );
+ $this->expectOutputString( $expectedOutput );
+ }
+
+
+
+ // Instead of the following functions, a data provider would be great.
+ // However, as data providers are evaluated /before/ addDBData, a data
+ // provider would not know the required ids.
+
+ function testExistingSimple() {
+ $this->assertFilter( $this->textId2,
+ $this->textId2 . "\n23\nFetchTextTestPage2Text1" );
+ }
+
+ function testExistingSimpleWithNewline() {
+ $this->assertFilter( $this->textId2 . "\n",
+ $this->textId2 . "\n23\nFetchTextTestPage2Text1" );
+ }
+
+ function testExistingSeveral() {
+ $this->assertFilter( "$this->textId1\n$this->textId5\n"
+ . "$this->textId3\n$this->textId3",
+ implode( "", array(
+ $this->textId1 . "\n23\nFetchTextTestPage1Text1",
+ $this->textId5 . "\n44\nFetchTextTestPage2Text4 "
+ . "some additional Text",
+ $this->textId3 . "\n23\nFetchTextTestPage2Text2",
+ $this->textId3 . "\n23\nFetchTextTestPage2Text2"
+ ) ) );
+ }
+
+ function testEmpty() {
+ $this->assertFilter( "", null );
+ }
+
+ function testNonExisting() {
+ $this->assertFilter( $this->textId5 + 10, ( $this->textId5 + 10 ) . "\n-1\n" );
+ }
+
+ function testNegativeInteger() {
+ $this->assertFilter( "-42", "-42\n-1\n" );
+ }
+
+ function testFloatingPointNumberExisting() {
+ // float -> int -> revision
+ $this->assertFilter( $this->textId3 + 0.14159,
+ $this->textId3 . "\n23\nFetchTextTestPage2Text2" );
+ }
+
+ function testFloatingPointNumberNonExisting() {
+ $this->assertFilter( $this->textId5 + 3.14159,
+ ( $this->textId5 + 3 ) . "\n-1\n" );
+ }
+
+ function testCharacters() {
+ $this->assertFilter( "abc", "0\n-1\n" );
+ }
+
+ function testMix() {
+ $this->assertFilter( "ab\n" . $this->textId4 . ".5cd\n\nefg\n" . $this->textId2
+ . "\n" . $this->textId3,
+ implode( "", array(
+ "0\n-1\n",
+ $this->textId4 . "\n23\nFetchTextTestPage2Text3",
+ "0\n-1\n",
+ "0\n-1\n",
+ $this->textId2 . "\n23\nFetchTextTestPage2Text1",
+ $this->textId3 . "\n23\nFetchTextTestPage2Text2"
+ ) ) );
+ }
+
+}
--- /dev/null
+<?php
+global $IP;
+require_once( "$IP/maintenance/getSlaveServer.php" );
+
+/**
+ * Tests for getSlaveServer
+ *
+ * @group Database
+ */
+class GetSlaveServerTest extends MediaWikiTestCase {
+
+ /**
+ * Yields a regular expression that matches a good DB server name
+ *
+ * It matches IPs or hostnames, both optionally followed by a
+ * port specification
+ *
+ * @return String the regular expression
+ */
+ private function getServerRE() {
+ if ( $this->db->getType() === 'sqlite' ) {
+ // for SQLite, only the empty string is a good server name
+ return '';
+ }
+
+ $octet = '([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])';
+ $ip = "(($octet\.){3}$octet)";
+
+ $label = '([a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)';
+ $hostname = "($label(\.$label)*)";
+
+ return "($ip|$hostname)(:[0-9]{1,5})?";
+ }
+
+ function testPlain() {
+ $gss = new GetSlaveServer();
+ $gss->execute();
+
+ $this->expectOutputRegex( "/^" . self::getServerRE() . "\n$/D" );
+ }
+
+ function testXmlDumpsBackupUseCase() {
+ global $wgDBprefix;
+
+ global $argv;
+ $argv = array( null, "--globals" );
+
+ $gss = new GetSlaveServer();
+ $gss->loadParamsAndArgs();
+ $gss->execute();
+ $gss->globals();
+
+ // The main answer
+ $output = $this->getActualOutput();
+ $firstLineEndPos = strpos( $output,"\n");
+ if ( $firstLineEndPos === FALSE ) {
+ $this->fail( "Could not find end of first line of output" );
+ }
+ $firstLine = substr( $output, 0 , $firstLineEndPos );
+ $this->assertRegExp( "/^" . self::getServerRE() . "$/D",
+ $firstLine, "DB Server" );
+
+ // xmldumps-backup relies on the wgDBprefix in the output.
+ $this->expectOutputRegex( "/^[[:space:]]*\[wgDBprefix\][[:space:]]*=> "
+ . $wgDBprefix . "$/m" );
+ }
+
+
+}