From 63601400e476c6cf43d985f3e7b9864681695ed4 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Fri, 18 Jan 2013 16:46:04 +0100 Subject: Update to MediaWiki 1.20.2 this update includes: * adjusted Arch Linux skin * updated FluxBBAuthPlugin * patch for https://bugzilla.wikimedia.org/show_bug.cgi?id=44024 --- tests/phpunit/includes/ArticleTablesTest.php | 4 +- tests/phpunit/includes/BlockTest.php | 107 ++- tests/phpunit/includes/CdbTest.php | 2 +- tests/phpunit/includes/DiffHistoryBlobTest.php | 40 ++ tests/phpunit/includes/EditPageTest.php | 9 +- tests/phpunit/includes/ExtraParserTest.php | 4 +- .../includes/GlobalFunctions/GlobalTest.php | 46 +- .../includes/GlobalFunctions/wfGetCallerTest.php | 35 + tests/phpunit/includes/HtmlTest.php | 361 ++++++++-- tests/phpunit/includes/IPTest.php | 40 +- tests/phpunit/includes/LinksUpdateTest.php | 154 ++++ tests/phpunit/includes/LocalisationCacheTest.php | 31 + tests/phpunit/includes/MWNamespaceTest.php | 43 +- tests/phpunit/includes/MessageTest.php | 2 + tests/phpunit/includes/PreferencesTest.php | 75 ++ tests/phpunit/includes/RecentChangeTest.php | 273 +++++++ tests/phpunit/includes/RevisionStorageTest.php | 408 +++++++++++ tests/phpunit/includes/SampleTest.php | 2 +- tests/phpunit/includes/SanitizerTest.php | 36 +- tests/phpunit/includes/TemplateCategoriesTest.php | 16 +- tests/phpunit/includes/TestUser.php | 58 ++ tests/phpunit/includes/TimestampTest.php | 72 ++ tests/phpunit/includes/TitleMethodsTest.php | 131 +++- tests/phpunit/includes/TitleTest.php | 75 ++ tests/phpunit/includes/UserTest.php | 28 + tests/phpunit/includes/WebRequestTest.php | 39 +- tests/phpunit/includes/WikiPageTest.php | 784 +++++++++++++++++++++ tests/phpunit/includes/XmlTest.php | 55 +- tests/phpunit/includes/ZipDirectoryReaderTest.php | 2 +- tests/phpunit/includes/api/ApiBlockTest.php | 51 +- tests/phpunit/includes/api/ApiEditPageTest.php | 84 +++ tests/phpunit/includes/api/ApiOptionsTest.php | 276 ++++++++ tests/phpunit/includes/api/ApiPurgeTest.php | 1 + tests/phpunit/includes/api/ApiQueryTest.php | 1 + tests/phpunit/includes/api/ApiTest.php | 1 + tests/phpunit/includes/api/ApiTestCase.php | 59 +- tests/phpunit/includes/api/ApiTestUser.php | 59 -- tests/phpunit/includes/api/ApiUploadTest.php | 1 + tests/phpunit/includes/api/ApiWatchTest.php | 88 ++- .../phpunit/includes/api/PrefixUniquenessTest.php | 24 + .../phpunit/includes/api/RandomImageGenerator.php | 2 +- .../phpunit/includes/api/generateRandomImages.php | 8 +- tests/phpunit/includes/cache/GenderCacheTest.php | 101 +++ .../phpunit/includes/cache/ProcessCacheLRUTest.php | 239 +++++++ tests/phpunit/includes/db/DatabaseSQLTest.php | 147 ++++ tests/phpunit/includes/db/DatabaseSqliteTest.php | 10 + tests/phpunit/includes/db/ORMRowTest.php | 234 ++++++ tests/phpunit/includes/db/TestORMRowTest.php | 174 +++++ tests/phpunit/includes/debug/MWDebugTest.php | 7 +- .../phpunit/includes/filerepo/FileBackendTest.php | 754 ++++++++++++++++---- tests/phpunit/includes/filerepo/FileRepoTest.php | 8 +- tests/phpunit/includes/filerepo/StoreBatchTest.php | 1 + tests/phpunit/includes/libs/CSSJanusTest.php | 560 +++++++++++++++ tests/phpunit/includes/libs/CSSMinTest.php | 142 ++++ .../includes/libs/GenericArrayObjectTest.php | 245 +++++++ .../includes/libs/JavaScriptMinifierTest.php | 66 +- .../includes/media/BitmapMetadataHandlerTest.php | 5 +- tests/phpunit/includes/media/ExifRotationTest.php | 20 +- tests/phpunit/includes/media/ExifTest.php | 22 +- .../phpunit/includes/media/FormatMetadataTest.php | 2 +- .../includes/media/GIFMetadataExtractorTest.php | 2 +- tests/phpunit/includes/media/GIFTest.php | 2 +- .../includes/media/JpegMetadataExtractorTest.php | 2 +- tests/phpunit/includes/media/JpegTest.php | 2 +- .../includes/media/PNGMetadataExtractorTest.php | 2 +- tests/phpunit/includes/media/PNGTest.php | 2 +- .../includes/media/SVGMetadataExtractorTest.php | 24 +- tests/phpunit/includes/media/TiffTest.php | 2 +- tests/phpunit/includes/media/XMPTest.php | 11 +- .../includes/mobile/DeviceDetectionTest.php | 40 ++ .../includes/parser/MediaWikiParserTest.php | 2 +- tests/phpunit/includes/parser/NewParserTest.php | 18 +- .../phpunit/includes/parser/ParserMethodsTest.php | 33 + tests/phpunit/includes/parser/PreprocessorTest.php | 4 +- .../includes/specials/SpecialSearchTest.php | 8 + .../phpunit/includes/upload/UploadFromUrlTest.php | 14 +- tests/phpunit/includes/upload/UploadStashTest.php | 6 +- tests/phpunit/includes/upload/UploadTest.php | 4 +- 78 files changed, 5956 insertions(+), 546 deletions(-) create mode 100644 tests/phpunit/includes/DiffHistoryBlobTest.php create mode 100644 tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php create mode 100644 tests/phpunit/includes/LinksUpdateTest.php create mode 100644 tests/phpunit/includes/LocalisationCacheTest.php create mode 100644 tests/phpunit/includes/PreferencesTest.php create mode 100644 tests/phpunit/includes/RecentChangeTest.php create mode 100644 tests/phpunit/includes/RevisionStorageTest.php create mode 100644 tests/phpunit/includes/TestUser.php create mode 100644 tests/phpunit/includes/TimestampTest.php create mode 100644 tests/phpunit/includes/WikiPageTest.php create mode 100644 tests/phpunit/includes/api/ApiEditPageTest.php create mode 100644 tests/phpunit/includes/api/ApiOptionsTest.php delete mode 100644 tests/phpunit/includes/api/ApiTestUser.php create mode 100644 tests/phpunit/includes/api/PrefixUniquenessTest.php create mode 100644 tests/phpunit/includes/cache/GenderCacheTest.php create mode 100644 tests/phpunit/includes/cache/ProcessCacheLRUTest.php create mode 100644 tests/phpunit/includes/db/DatabaseSQLTest.php create mode 100644 tests/phpunit/includes/db/ORMRowTest.php create mode 100644 tests/phpunit/includes/db/TestORMRowTest.php create mode 100644 tests/phpunit/includes/libs/CSSJanusTest.php create mode 100644 tests/phpunit/includes/libs/CSSMinTest.php create mode 100644 tests/phpunit/includes/libs/GenericArrayObjectTest.php create mode 100644 tests/phpunit/includes/mobile/DeviceDetectionTest.php create mode 100644 tests/phpunit/includes/parser/ParserMethodsTest.php (limited to 'tests/phpunit/includes') diff --git a/tests/phpunit/includes/ArticleTablesTest.php b/tests/phpunit/includes/ArticleTablesTest.php index 02571b55..17cee6e8 100644 --- a/tests/phpunit/includes/ArticleTablesTest.php +++ b/tests/phpunit/includes/ArticleTablesTest.php @@ -17,14 +17,14 @@ class ArticleTablesTest extends MediaWikiLangTestCase { $wgLang = Language::factory( 'fr' ); $status = $page->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', 0, false, $user ); - $templates1 = $page->getUsedTemplates(); + $templates1 = $title->getTemplateLinksFrom(); $wgLang = Language::factory( 'de' ); $page->mPreparedEdit = false; // In order to force the rerendering of the same wikitext // We need an edit, a purge is not enough to regenerate the tables $status = $page->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', EDIT_UPDATE, false, $user ); - $templates2 = $page->getUsedTemplates(); + $templates2 = $title->getTemplateLinksFrom(); $this->assertEquals( $templates1, $templates2 ); $this->assertEquals( $templates1[0]->getFullText(), 'Historial' ); diff --git a/tests/phpunit/includes/BlockTest.php b/tests/phpunit/includes/BlockTest.php index 749f40b4..0c95b8d1 100644 --- a/tests/phpunit/includes/BlockTest.php +++ b/tests/phpunit/includes/BlockTest.php @@ -67,7 +67,7 @@ class BlockTest extends MediaWikiLangTestCase { // $this->dumpBlocks(); $this->assertTrue( $this->block->equals( Block::newFromTarget('UTBlockee') ), "newFromTarget() returns the same block as the one that was made"); - + $this->assertTrue( $this->block->equals( Block::newFromID( $this->blockId ) ), "newFromID() returns the same block as the one that was made"); } @@ -122,4 +122,109 @@ class BlockTest extends MediaWikiLangTestCase { array( false ) ); } + + function testBlockedUserCanNotCreateAccount() { + $username = 'BlockedUserToCreateAccountWith'; + $u = User::newFromName( $username ); + $u->setPassword( 'NotRandomPass' ); + $u->addToDatabase(); + unset( $u ); + + + // Sanity check + $this->assertNull( + Block::newFromTarget( $username ), + "$username should not be blocked" + ); + + // Reload user + $u = User::newFromName( $username ); + $this->assertFalse( + $u->isBlockedFromCreateAccount(), + "Our sandbox user should be able to create account before being blocked" + ); + + // Foreign perspective (blockee not on current wiki)... + $block = new Block( + /* $address */ $username, + /* $user */ 14146, + /* $by */ 0, + /* $reason */ 'crosswiki block...', + /* $timestamp */ wfTimestampNow(), + /* $auto */ false, + /* $expiry */ $this->db->getInfinity(), + /* anonOnly */ false, + /* $createAccount */ true, + /* $enableAutoblock */ true, + /* $hideName (ipb_deleted) */ true, + /* $blockEmail */ true, + /* $allowUsertalk */ false, + /* $byName */ 'MetaWikiUser' + ); + $block->insert(); + + // Reload block from DB + $userBlock = Block::newFromTarget( $username ); + $this->assertTrue( + (bool) $block->prevents( 'createaccount' ), + "Block object in DB should prevents 'createaccount'" + ); + + $this->assertInstanceOf( + 'Block', + $userBlock, + "'$username' block block object should be existent" + ); + + // Reload user + $u = User::newFromName( $username ); + $this->assertTrue( + (bool) $u->isBlockedFromCreateAccount(), + "Our sandbox user '$username' should NOT be able to create account" + ); + } + + function testCrappyCrossWikiBlocks() { + // Delete the last round's block if it's still there + $oldBlock = Block::newFromTarget( 'UserOnForeignWiki' ); + if ( $oldBlock ) { + // An old block will prevent our new one from saving. + $oldBlock->delete(); + } + + // Foreign perspective (blockee not on current wiki)... + $block = new Block( + /* $address */ 'UserOnForeignWiki', + /* $user */ 14146, + /* $by */ 0, + /* $reason */ 'crosswiki block...', + /* $timestamp */ wfTimestampNow(), + /* $auto */ false, + /* $expiry */ $this->db->getInfinity(), + /* anonOnly */ false, + /* $createAccount */ true, + /* $enableAutoblock */ true, + /* $hideName (ipb_deleted) */ true, + /* $blockEmail */ true, + /* $allowUsertalk */ false, + /* $byName */ 'MetaWikiUser' + ); + + $res = $block->insert( $this->db ); + $this->assertTrue( (bool)$res['id'], 'Block succeeded' ); + + // Local perspective (blockee on current wiki)... + $user = User::newFromName( 'UserOnForeignWiki' ); + $user->addToDatabase(); + // Set user ID to match the test value + $this->db->update( 'user', array( 'user_id' => 14146 ), array( 'user_id' => $user->getId() ) ); + $user = null; // clear + + $block = Block::newFromID( $res['id'] ); + $this->assertEquals( 'UserOnForeignWiki', $block->getTarget()->getName(), 'Correct blockee name' ); + $this->assertEquals( '14146', $block->getTarget()->getId(), 'Correct blockee id' ); + $this->assertEquals( 'MetaWikiUser', $block->getBlocker(), 'Correct blocker name' ); + $this->assertEquals( 'MetaWikiUser', $block->getByName(), 'Correct blocker name' ); + $this->assertEquals( 0, $block->getBy(), 'Correct blocker id' ); + } } diff --git a/tests/phpunit/includes/CdbTest.php b/tests/phpunit/includes/CdbTest.php index 6c3e6664..b5418dd7 100644 --- a/tests/phpunit/includes/CdbTest.php +++ b/tests/phpunit/includes/CdbTest.php @@ -8,7 +8,7 @@ class CdbTest extends MediaWikiTestCase { public function setUp() { if ( !CdbReader::haveExtension() ) { - $this->markTestIncomplete( 'This test requires native CDB support to be present.' ); + $this->markTestSkipped( 'Native CDB support is not available' ); } } diff --git a/tests/phpunit/includes/DiffHistoryBlobTest.php b/tests/phpunit/includes/DiffHistoryBlobTest.php new file mode 100644 index 00000000..cdb6ed2f --- /dev/null +++ b/tests/phpunit/includes/DiffHistoryBlobTest.php @@ -0,0 +1,40 @@ +markTestSkipped( 'The xdiff extension is not available' ); + return; + } + if ( !function_exists( 'xdiff_string_rabdiff' ) ) { + $this->markTestSkipped( 'The version of xdiff extension is lower than 1.5.0' ); + return; + } + if ( !extension_loaded( 'hash' ) && !extension_loaded( 'mhash' ) ) { + $this->markTestSkipped( 'Neither the hash nor mhash extension is available' ); + return; + } + } + + /** + * Test for DiffHistoryBlob::xdiffAdler32() + * @dataProvider provideXdiffAdler32 + */ + function testXdiffAdler32( $input ) { + $xdiffHash = substr( xdiff_string_rabdiff( $input, '' ), 0, 4 ); + $dhb = new DiffHistoryBlob; + $myHash = $dhb->xdiffAdler32( $input ); + $this->assertSame( bin2hex( $xdiffHash ), bin2hex( $myHash ), + "Hash of " . addcslashes( $input, "\0..\37!@\@\177..\377" ) ); + } + + function provideXdiffAdler32() { + return array( + array( '', 'Empty string' ), + array( "\0", 'Null' ), + array( "\0\0\0", "Several nulls" ), + array( "Hello", "An ASCII string" ), + array( str_repeat( "x", 6000 ), "A string larger than xdiff's NMAX (5552)" ) + ); + } +} diff --git a/tests/phpunit/includes/EditPageTest.php b/tests/phpunit/includes/EditPageTest.php index e98e9707..8ecfd7e5 100644 --- a/tests/phpunit/includes/EditPageTest.php +++ b/tests/phpunit/includes/EditPageTest.php @@ -1,5 +1,8 @@ options = new ParserOptions; $this->options->setTemplateCallback( array( __CLASS__, 'statelessFetchTemplate' ) ); $this->parser = new Parser; + + MagicWord::clearCache(); } // Bug 8689 - Long numeric lines kill the parser @@ -146,7 +148,7 @@ class ExtraParserTest extends MediaWikiTestCase { */ function testTrackingCategory() { $title = Title::newFromText( __FUNCTION__ ); - $catName = wfMsgForContent( 'broken-file-category' ); + $catName = wfMessage( 'broken-file-category' )->inContentLanguage()->text(); $cat = Title::makeTitleSafe( NS_CATEGORY, $catName ); $expected = array( $cat->getDBkey() ); $parserOutput = $this->parser->parse( "[[file:nonexistent]]" , $title, $this->options ); diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php index 3cb42f12..9097d301 100644 --- a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php +++ b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php @@ -55,6 +55,12 @@ class GlobalTest extends MediaWikiTestCase { wfUrlencode( "\xE7\x89\xB9\xE5\x88\xA5:Contributions/Foobar" ) ); } + function testExpandIRI() { + $this->assertEquals( + "https://te.wikibooks.org/wiki/ఉబుంటు_వాడుకరి_మార్గదర్శని", + wfExpandIRI( "https://te.wikibooks.org/wiki/%E0%B0%89%E0%B0%AC%E0%B1%81%E0%B0%82%E0%B0%9F%E0%B1%81_%E0%B0%B5%E0%B0%BE%E0%B0%A1%E0%B1%81%E0%B0%95%E0%B0%B0%E0%B0%BF_%E0%B0%AE%E0%B0%BE%E0%B0%B0%E0%B1%8D%E0%B0%97%E0%B0%A6%E0%B0%B0%E0%B1%8D%E0%B0%B6%E0%B0%A8%E0%B0%BF" ) ); + } + function testReadOnlyEmpty() { global $wgReadOnly; $wgReadOnly = null; @@ -305,7 +311,7 @@ class GlobalTest extends MediaWikiTestCase { function testDebugFunctionTest() { - global $wgDebugLogFile, $wgOut, $wgShowDebug, $wgDebugTimestamps; + global $wgDebugLogFile, $wgDebugTimestamps; $old_log_file = $wgDebugLogFile; $wgDebugLogFile = tempnam( wfTempDir(), 'mw-' ); @@ -327,33 +333,7 @@ class GlobalTest extends MediaWikiTestCase { wfDebug( "\00305This has böth UTF and control chars\003" ); $this->assertEquals( " 05This has böth UTF and control chars ", file_get_contents( $wgDebugLogFile ) ); unlink( $wgDebugLogFile ); - - - - $old_wgOut = $wgOut; - $old_wgShowDebug = $wgShowDebug; - - $wgOut = new MockOutputPage; - - $wgShowDebug = true; - - $message = "\00305This has böth UTF and control chars\003"; - - wfDebug( $message ); - - if( $wgOut->message == "JAJA is a stupid error message. Anyway, here's your message: $message" ) { - $this->assertTrue( true, 'MockOutputPage called, set the proper message.' ); - } - else { - $this->assertTrue( false, 'MockOutputPage was not called.' ); - } - - $wgOut = $old_wgOut; - $wgShowDebug = $old_wgShowDebug; - unlink( $wgDebugLogFile ); - - - + wfDebugMem(); $this->assertGreaterThan( 5000, preg_replace( '/\D/', '', file_get_contents( $wgDebugLogFile ) ) ); unlink( $wgDebugLogFile ); @@ -616,13 +596,3 @@ class GlobalTest extends MediaWikiTestCase { /* TODO: many more! */ } - -class MockOutputPage { - - public $message; - - function debug( $message ) { - $this->message = "JAJA is a stupid error message. Anyway, here's your message: $message"; - } -} - diff --git a/tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php b/tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php new file mode 100644 index 00000000..4c4c4c04 --- /dev/null +++ b/tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php @@ -0,0 +1,35 @@ +assertEquals( __METHOD__, wfGetCaller( 1 ) ); + } + + function callerOne() { + return wfGetCaller(); + } + + function testOne() { + $this->assertEquals( "wfGetCaller::testOne", self::callerOne() ); + } + + function intermediateFunction( $level = 2, $n = 0 ) { + if ( $n > 0 ) + return self::intermediateFunction( $level, $n - 1 ); + return wfGetCaller( $level ); + } + + function testTwo() { + $this->assertEquals( "wfGetCaller::testTwo", self::intermediateFunction() ); + } + + function testN() { + $this->assertEquals( "wfGetCaller::testN", self::intermediateFunction( 2, 0 ) ); + $this->assertEquals( "wfGetCaller::intermediateFunction", self::intermediateFunction( 1, 0 ) ); + + for ($i=0; $i < 10; $i++) + $this->assertEquals( "wfGetCaller::intermediateFunction", self::intermediateFunction( $i + 1, $i ) ); + } +} + diff --git a/tests/phpunit/includes/HtmlTest.php b/tests/phpunit/includes/HtmlTest.php index 67b60d32..a18f7922 100644 --- a/tests/phpunit/includes/HtmlTest.php +++ b/tests/phpunit/includes/HtmlTest.php @@ -6,15 +6,18 @@ class HtmlTest extends MediaWikiTestCase { private static $oldContLang; private static $oldLanguageCode; private static $oldNamespaces; + private static $oldHTML5; public function setUp() { - global $wgLang, $wgContLang, $wgLanguageCode; - + global $wgLang, $wgContLang, $wgLanguageCode, $wgHTML5; + + // Save globals self::$oldLang = $wgLang; self::$oldContLang = $wgContLang; self::$oldNamespaces = $wgContLang->getNamespaces(); self::$oldLanguageCode = $wgLanguageCode; - + self::$oldHTML5 = $wgHTML5; + $wgLanguageCode = 'en'; $wgContLang = $wgLang = Language::factory( $wgLanguageCode ); @@ -36,18 +39,41 @@ class HtmlTest extends MediaWikiTestCase { 9 => 'MediaWiki_talk', 10 => 'Template', 11 => 'Template_talk', + 14 => 'Category', + 15 => 'Category_talk', 100 => 'Custom', 101 => 'Custom_talk', ) ); } - + public function tearDown() { - global $wgLang, $wgContLang, $wgLanguageCode; + global $wgLang, $wgContLang, $wgLanguageCode, $wgHTML5; + // Restore globals $wgContLang->setNamespaces( self::$oldNamespaces ); $wgLang = self::$oldLang; $wgContLang = self::$oldContLang; $wgLanguageCode = self::$oldLanguageCode; + $wgHTML5 = self::$oldHTML5; + } + + /** + * Wrapper to easily set $wgHTML5 = true. + * Original value will be restored after test completion. + * @todo Move to MediaWikiTestCase + */ + public function enableHTML5() { + global $wgHTML5; + $wgHTML5 = true; + } + /** + * Wrapper to easily set $wgHTML5 = false + * Original value will be restored after test completion. + * @todo Move to MediaWikiTestCase + */ + public function disableHTML5() { + global $wgHTML5; + $wgHTML5 = false; } public function testExpandAttributesSkipsNullAndFalse() { @@ -213,7 +239,7 @@ class HtmlTest extends MediaWikiTestCase { function testNamespaceSelector() { $this->assertEquals( - '' . "\n" . '' . "\n" . '' . "\n" . '' . "\n" . @@ -226,12 +252,15 @@ class HtmlTest extends MediaWikiTestCase { '' . "\n" . '' . "\n" . '' . "\n" . +'' . "\n" . +'' . "\n" . '' . "\n" . '' . "\n" . '', Html::namespaceSelector(), 'Basic namespace selector without custom options' ); + $this->assertEquals( ' ' . '', @@ -257,77 +288,281 @@ class HtmlTest extends MediaWikiTestCase { ), 'Basic namespace selector with custom values' ); - } - function testNamespaceSelectorIdAndNameDefaultsAttributes() { - - $this->assertNsSelectorIdAndName( - 'namespace', 'namespace', - Html::namespaceSelector( array(), array( - # neither 'id' nor 'name' key given - )), - "Neither 'id' nor 'name' key given" + $this->assertEquals( + ' ' . +'', + Html::namespaceSelector( + array( 'label' => 'Select a namespace:' ) + ), + 'Basic namespace selector with a custom label but no id attribtue for the ' . "\n" . +'' . "\n" . +'' . "\n" . +'' . "\n" . +'' . "\n" . +'' . "\n" . +'' . "\n" . +'' . "\n" . +'' . "\n" . +'' . "\n" . +'' . "\n" . +'' . "\n" . +'', + Html::namespaceSelector( + array( 'exclude' => array( 0, 1, 3, 100, 101 ) ) + ), + 'Namespace selector namespace filtering.' ); + } - $this->assertNsSelectorIdAndName( - 'select_id', 'namespace', - Html::namespaceSelector( array(), array( - 'id' => 'select_id', - # no 'name' key given - )), - "'id' given, no 'name' key given" + function testCanDisableANamespaces() { + $this->assertEquals( +'', + Html::namespaceSelector( array( + 'disable' => array( 0, 1, 2, 3, 4 ) + ) ), + 'Namespace selector namespace disabling' ); + } - $this->assertNsSelectorIdAndName( - 'select_id', 'select_name', - Html::namespaceSelector( array(), array( - 'id' => 'select_id', - 'name' => 'select_name', - )), - "Both 'id' and 'name' given" + /** + * @dataProvider providesHtml5InputTypes + */ + function testHtmlElementAcceptsNewHtml5TypesInHtml5Mode( $HTML5InputType ) { + $this->enableHTML5(); + $this->assertEquals( + '', + HTML::element( 'input', array( 'type' => $HTML5InputType ) ), + 'In HTML5, HTML::element() should accept type="' . $HTML5InputType . '"' ); } /** - * Helper to verify id attribute value - * @param String $expectedName ', + 'input', array( 'formaction' => 'GET' ) + ); + $cases[] = array( '', + 'input', array( 'type' => 'text' ) + ); + + $cases[] = array( '', + 'keygen', array( 'keytype' => 'rsa' ) ); + + $cases[] = array( '', + 'link', array( 'media' => 'all' ) + ); + + $cases[] = array( '', + 'menu', array( 'type' => 'list' ) + ); + + $cases[] = array( '', + 'script', array( 'type' => 'text/javascript' ) + ); + + $cases[] = array( '', + 'style', array( 'media' => 'all' ) + ); + $cases[] = array( '', + 'style', array( 'type' => 'text/css' ) + ); + + $cases[] = array( '', + 'textarea', array( 'wrap' => 'soft' ) + ); + + ### SPECIFIC CASES + + # + $cases[] = array( '', + 'link', array( 'type' => 'text/css' ) + ); + + # specific handling + $cases[] = array( '', + 'input', array( 'type' => 'checkbox', 'value' => 'on' ), + 'Default value "on" is stripped of checkboxes', + ); + $cases[] = array( '', + 'input', array( 'type' => 'radio', 'value' => 'on' ), + 'Default value "on" is stripped of radio buttons', + ); + $cases[] = array( '', + 'input', array( 'type' => 'submit', 'value' => 'Submit' ), + 'Default value "Submit" is kept on submit buttons (for possible l10n issues)', + ); + $cases[] = array( '', + 'input', array( 'type' => 'color', 'value' => '' ), + ); + $cases[] = array( '', + 'input', array( 'type' => 'range', 'value' => '' ), + ); + + # ', + 'select', array( 'size' => '4', 'multiple' => true ), + ); + # .. with numeric value + $cases[] = array( '', + 'select', array( 'size' => 4, 'multiple' => true ), + ); + $cases[] = array( '', + 'select', array( 'size' => '1', 'multiple' => false ), + ); + # .. with numeric value + $cases[] = array( '', + 'select', array( 'size' => 1, 'multiple' => false ), + ); + + # Passing an array as value + $cases[] = array( '', + 'a', array( 'class' => array( 'css-class-one', 'css-class-two' ) ), + "dropDefaults accepts values given as an array" + ); + + # FIXME: doDropDefault should remove defaults given in an array + # Expected should be '' + $cases[] = array( '', + 'a', array( 'class' => array( '', '' ) ), + "dropDefaults accepts values given as an array" + ); + + + # Craft the Html elements + $ret = array(); + foreach( $cases as $case ) { + $ret[] = array( + $case[0], + Html::element( $case[1], $case[2] ) + ); + } + return $ret; } } diff --git a/tests/phpunit/includes/IPTest.php b/tests/phpunit/includes/IPTest.php index 4397b879..f50b2fe9 100644 --- a/tests/phpunit/includes/IPTest.php +++ b/tests/phpunit/includes/IPTest.php @@ -1,6 +1,7 @@ assertFalse( IP::isIPAddress( "" ), 'Empty string is not an IP' ); $this->assertFalse( IP::isIPAddress( 'abc' ), 'Garbage IP string' ); $this->assertFalse( IP::isIPAddress( ':' ), 'Single ":" is not an IP' ); - $this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::1'), 'IPv6 with a double :: occurence' ); - $this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::'), 'IPv6 with a double :: occurence, last at end' ); - $this->assertFalse( IP::isIPAddress( '::2001:0DB8::5:1'), 'IPv6 with a double :: occurence, firt at beginning' ); + $this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::1'), 'IPv6 with a double :: occurrence' ); + $this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::'), 'IPv6 with a double :: occurrence, last at end' ); + $this->assertFalse( IP::isIPAddress( '::2001:0DB8::5:1'), 'IPv6 with a double :: occurrence, firt at beginning' ); $this->assertFalse( IP::isIPAddress( '124.24.52' ), 'IPv4 not enough quads' ); $this->assertFalse( IP::isIPAddress( '24.324.52.13' ), 'IPv4 out of range' ); $this->assertFalse( IP::isIPAddress( '.24.52.13' ), 'IPv4 starts with period' ); @@ -505,4 +506,37 @@ class IPTest extends MediaWikiTestCase { array( '0:c1:A2:3:4:5:c6:7', '0:C1:A2:3:4:5:C6:7', 'IPv6 non range' ), ); } + + /** + * Test for IP::prettifyIP() + * @dataProvider provideIPsToPrettify + */ + function testPrettifyIP( $ip, $prettified ) { + $this->assertEquals( $prettified, IP::prettifyIP( $ip ), "Prettify of $ip" ); + } + + /** + * Provider for IP::testPrettifyIP() + */ + function provideIPsToPrettify() { + return array( + array( '0:0:0:0:0:0:0:0', '::' ), + array( '0:0:0::0:0:0', '::' ), + array( '0:0:0:1:0:0:0:0', '0:0:0:1::' ), + array( '0:0::f', '::f' ), + array( '0::0:0:0:33:fef:b', '::33:fef:b' ), + array( '3f:535:0:0:0:0:e:fbb', '3f:535::e:fbb' ), + array( '0:0:fef:0:0:0:e:fbb', '0:0:fef::e:fbb' ), + array( 'abbc:2004::0:0:0:0', 'abbc:2004::' ), + array( 'cebc:2004:f:0:0:0:0:0', 'cebc:2004:f::' ), + array( '0:0:0:0:0:0:0:0/16', '::/16' ), + array( '0:0:0::0:0:0/64', '::/64' ), + array( '0:0::f/52', '::f/52' ), + array( '::0:0:33:fef:b/52', '::33:fef:b/52' ), + array( '3f:535:0:0:0:0:e:fbb/48', '3f:535::e:fbb/48' ), + array( '0:0:fef:0:0:0:e:fbb/96', '0:0:fef::e:fbb/96' ), + array( 'abbc:2004:0:0::0:0/40', 'abbc:2004::/40' ), + array( 'aebc:2004:f:0:0:0:0:0/80', 'aebc:2004:f::/80' ), + ); + } } diff --git a/tests/phpunit/includes/LinksUpdateTest.php b/tests/phpunit/includes/LinksUpdateTest.php new file mode 100644 index 00000000..49462001 --- /dev/null +++ b/tests/phpunit/includes/LinksUpdateTest.php @@ -0,0 +1,154 @@ +tablesUsed = array_merge ( $this->tablesUsed, + array( 'interwiki', + + 'page_props', + 'pagelinks', + 'categorylinks', + 'langlinks', + 'externallinks', + 'imagelinks', + 'templatelinks', + 'iwlinks' ) ); + } + + function setUp() { + $dbw = wfGetDB( DB_MASTER ); + $dbw->replace( 'interwiki', + array('iw_prefix'), + array( 'iw_prefix' => 'linksupdatetest', + 'iw_url' => 'http://testing.com/wiki/$1', + 'iw_api' => 'http://testing.com/w/api.php', + 'iw_local' => 0, + 'iw_trans' => 0, + 'iw_wikiid' => 'linksupdatetest', + ) ); + } + + protected function makeTitleAndParserOutput( $name, $id ) { + $t = Title::newFromText( $name ); + $t->mArticleID = $id; # XXX: this is fugly + + $po = new ParserOutput(); + $po->setTitleText( $t->getPrefixedText() ); + + return array( $t, $po ); + } + + public function testUpdate_pagelinks() { + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + + $po->addLink( Title::newFromText( "Foo" ) ); + $po->addLink( Title::newFromText( "Special:Foo" ) ); // special namespace should be ignored + $po->addLink( Title::newFromText( "linksupdatetest:Foo" ) ); // interwiki link should be ignored + $po->addLink( Title::newFromText( "#Foo" ) ); // hash link should be ignored + + $this->assertLinksUpdate( $t, $po, 'pagelinks', 'pl_namespace, pl_title', 'pl_from = 111', array( + array( NS_MAIN, 'Foo' ), + ) ); + + $po = new ParserOutput(); + $po->setTitleText( $t->getPrefixedText() ); + + $po->addLink( Title::newFromText( "Bar" ) ); + + $this->assertLinksUpdate( $t, $po, 'pagelinks', 'pl_namespace, pl_title', 'pl_from = 111', array( + array( NS_MAIN, 'Bar' ), + ) ); + } + + public function testUpdate_externallinks() { + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + + $po->addExternalLink( "http://testing.com/wiki/Foo" ); + + $this->assertLinksUpdate( $t, $po, 'externallinks', 'el_to, el_index', 'el_from = 111', array( + array( 'http://testing.com/wiki/Foo', 'http://com.testing./wiki/Foo' ), + ) ); + } + + public function testUpdate_categorylinks() { + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + + $po->addCategory( "Foo", "FOO" ); + + $this->assertLinksUpdate( $t, $po, 'categorylinks', 'cl_to, cl_sortkey', 'cl_from = 111', array( + array( 'Foo', "FOO\nTESTING" ), + ) ); + } + + public function testUpdate_iwlinks() { + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + + $target = Title::makeTitleSafe( NS_MAIN, "Foo", '', 'linksupdatetest' ); + $po->addInterwikiLink( $target ); + + $this->assertLinksUpdate( $t, $po, 'iwlinks', 'iwl_prefix, iwl_title', 'iwl_from = 111', array( + array( 'linksupdatetest', 'Foo' ), + ) ); + } + + public function testUpdate_templatelinks() { + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + + $po->addTemplate( Title::newFromText( "Template:Foo" ), 23, 42 ); + + $this->assertLinksUpdate( $t, $po, 'templatelinks', 'tl_namespace, tl_title', 'tl_from = 111', array( + array( NS_TEMPLATE, 'Foo' ), + ) ); + } + + public function testUpdate_imagelinks() { + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + + $po->addImage( "Foo.png" ); + + + $this->assertLinksUpdate( $t, $po, 'imagelinks', 'il_to', 'il_from = 111', array( + array( 'Foo.png' ), + ) ); + } + + public function testUpdate_langlinks() { + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + + $po->addLanguageLink( Title::newFromText( "en:Foo" ) ); + + + $this->assertLinksUpdate( $t, $po, 'langlinks', 'll_lang, ll_title', 'll_from = 111', array( + array( 'En', 'Foo' ), + ) ); + } + + public function testUpdate_page_props() { + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + + $po->setProperty( "foo", "bar" ); + + $this->assertLinksUpdate( $t, $po, 'page_props', 'pp_propname, pp_value', 'pp_page = 111', array( + array( 'foo', 'bar' ), + ) ); + } + + #@todo: test recursive, too! + + protected function assertLinksUpdate( Title $title, ParserOutput $parserOutput, $table, $fields, $condition, Array $expectedRows ) { + $update = new LinksUpdate( $title, $parserOutput ); + + $update->doUpdate(); + + $this->assertSelect( $table, $fields, $condition, $expectedRows ); + } +} + diff --git a/tests/phpunit/includes/LocalisationCacheTest.php b/tests/phpunit/includes/LocalisationCacheTest.php new file mode 100644 index 00000000..356db87c --- /dev/null +++ b/tests/phpunit/includes/LocalisationCacheTest.php @@ -0,0 +1,31 @@ +assertEquals( + $cache->getItem( 'ru', 'pluralRules' ), + $cache->getItem( 'os', 'pluralRules' ), + 'os plural rules (undefined) fallback to ru (defined)' + ); + + $this->assertEquals( + $cache->getItem( 'ru', 'compiledPluralRules' ), + $cache->getItem( 'os', 'compiledPluralRules' ), + 'os compiled plural rules (undefined) fallback to ru (defined)' + ); + + $this->assertNotEquals( + $cache->getItem( 'ksh', 'pluralRules' ), + $cache->getItem( 'de', 'pluralRules' ), + 'ksh plural rules (defined) dont fallback to de (defined)' + ); + + $this->assertNotEquals( + $cache->getItem( 'ksh', 'compiledPluralRules' ), + $cache->getItem( 'de', 'compiledPluralRules' ), + 'ksh compiled plural rules (defined) dont fallback to de (defined)' + ); + } +} diff --git a/tests/phpunit/includes/MWNamespaceTest.php b/tests/phpunit/includes/MWNamespaceTest.php index 6b231fc5..3b05d675 100644 --- a/tests/phpunit/includes/MWNamespaceTest.php +++ b/tests/phpunit/includes/MWNamespaceTest.php @@ -53,10 +53,6 @@ class MWNamespaceTest extends MediaWikiTestCase { $this->assertIsNotSubject( NS_TALK ); $this->assertIsNotSubject( NS_USER_TALK ); $this->assertIsNotSubject( 101 ); # user defined - - // Back compat - $this->assertTrue( MWNamespace::isMain( NS_MAIN ) == MWNamespace::isSubject( NS_MAIN ) ); - $this->assertTrue( MWNamespace::isMain( NS_USER_TALK ) == MWNamespace::isSubject( NS_USER_TALK ) ); } /** @@ -441,6 +437,36 @@ class MWNamespaceTest extends MediaWikiTestCase { $wgContentNamespaces = $saved; } + /** + */ + public function testGetSubjectNamespaces() { + $subjectsNS = MWNamespace::getSubjectNamespaces(); + $this->assertContains( NS_MAIN, $subjectsNS, + "Talk namespaces should have NS_MAIN" ); + $this->assertNotContains( NS_TALK, $subjectsNS, + "Talk namespaces should have NS_TALK" ); + + $this->assertNotContains( NS_MEDIA, $subjectsNS, + "Talk namespaces should not have NS_MEDIA" ); + $this->assertNotContains( NS_SPECIAL, $subjectsNS, + "Talk namespaces should not have NS_SPECIAL" ); + } + + /** + */ + public function testGetTalkNamespaces() { + $talkNS = MWNamespace::getTalkNamespaces(); + $this->assertContains( NS_TALK, $talkNS, + "Subject namespaces should have NS_TALK" ); + $this->assertNotContains( NS_MAIN, $talkNS, + "Subject namespaces should not have NS_MAIN" ); + + $this->assertNotContains( NS_MEDIA, $talkNS, + "Subject namespaces should not have NS_MEDIA" ); + $this->assertNotContains( NS_SPECIAL, $talkNS, + "Subject namespaces should not have NS_SPECIAL" ); + } + /** * Some namespaces are always capitalized per code definition * in MWNamespace::$alwaysCapitalizedNamespaces @@ -550,6 +576,15 @@ class MWNamespaceTest extends MediaWikiTestCase { } + public function testIsNonincludable() { + global $wgNonincludableNamespaces; + $wgNonincludableNamespaces = array( NS_USER ); + + $this->assertTrue( MWNamespace::isNonincludable( NS_USER ) ); + + $this->assertFalse( MWNamespace::isNonincludable( NS_TEMPLATE ) ); + } + ####### HELPERS ########################################################### function __call( $method, $args ) { // Call the real method if it exists diff --git a/tests/phpunit/includes/MessageTest.php b/tests/phpunit/includes/MessageTest.php index 295b6d74..20181fd4 100644 --- a/tests/phpunit/includes/MessageTest.php +++ b/tests/phpunit/includes/MessageTest.php @@ -16,6 +16,8 @@ class MessageTest extends MediaWikiLangTestCase { $this->assertInstanceOf( 'Message', wfMessage( 'i-dont-exist-evar' ) ); $this->assertEquals( 'Main Page', wfMessage( 'mainpage' )->text() ); $this->assertEquals( '<i-dont-exist-evar>', wfMessage( 'i-dont-exist-evar' )->text() ); + $this->assertEquals( '', wfMessage( 'i-dont-exist-evar' )->plain() ); + $this->assertEquals( '<i-dont-exist-evar>', wfMessage( 'i-dont-exist-evar' )->escaped() ); } function testInLanguage() { diff --git a/tests/phpunit/includes/PreferencesTest.php b/tests/phpunit/includes/PreferencesTest.php new file mode 100644 index 00000000..0e123177 --- /dev/null +++ b/tests/phpunit/includes/PreferencesTest.php @@ -0,0 +1,75 @@ +prefUsers['noemail'] = new User; + + $this->prefUsers['notauth'] = new User; + $this->prefUsers['notauth'] + ->setEmail( 'noauth@example.org' ); + + $this->prefUsers['auth'] = new User; + $this->prefUsers['auth'] + ->setEmail( 'noauth@example.org' ); + $this->prefUsers['auth'] + ->setEmailAuthenticationTimestamp( 1330946623 ); + + $this->context = new RequestContext; + $this->context->setTitle( Title::newFromText('PreferencesTest') ); + + //some tests depends on email setting + $wgEnableEmail = true; + } + + /** + * Placeholder to verify bug 34302 + * @covers Preferences::profilePreferences + */ + function testEmailFieldsWhenUserHasNoEmail() { + $prefs = $this->prefsFor( 'noemail' ); + $this->assertArrayHasKey( 'cssclass', + $prefs['emailaddress'] + ); + $this->assertEquals( 'mw-email-none', $prefs['emailaddress']['cssclass'] ); + } + /** + * Placeholder to verify bug 34302 + * @covers Preferences::profilePreferences + */ + function testEmailFieldsWhenUserEmailNotAuthenticated() { + $prefs = $this->prefsFor( 'notauth' ); + $this->assertArrayHasKey( 'cssclass', + $prefs['emailaddress'] + ); + $this->assertEquals( 'mw-email-not-authenticated', $prefs['emailaddress']['cssclass'] ); + } + /** + * Placeholder to verify bug 34302 + * @covers Preferences::profilePreferences + */ + function testEmailFieldsWhenUserEmailIsAuthenticated() { + $prefs = $this->prefsFor( 'auth' ); + $this->assertArrayHasKey( 'cssclass', + $prefs['emailaddress'] + ); + $this->assertEquals( 'mw-email-authenticated', $prefs['emailaddress']['cssclass'] ); + } + + /** Helper */ + function prefsFor( $user_key ) { + $preferences = array(); + Preferences::profilePreferences( + $this->prefUsers[$user_key] + , $this->context + , $preferences + ); + return $preferences; + } +} diff --git a/tests/phpunit/includes/RecentChangeTest.php b/tests/phpunit/includes/RecentChangeTest.php new file mode 100644 index 00000000..fbf271cc --- /dev/null +++ b/tests/phpunit/includes/RecentChangeTest.php @@ -0,0 +1,273 @@ +title = Title::newFromText( 'SomeTitle' ); + $this->target = Title::newFromText( 'TestTarget' ); + $this->user = User::newFromName( 'UserName' ); + + $this->user_comment = ''; + $this->context = RequestContext::newExtraneousContext( $this->title ); + } + + /** + * The testIrcMsgForAction* tests are supposed to cover the hacky + * LogFormatter::getIRCActionText / bug 34508 + * + * Third parties bots listen to those messages. They are clever enough + * to fetch the i18n messages from the wiki and then analyze the IRC feed + * to reverse engineer the $1, $2 messages. + * One thing bots can not detect is when MediaWiki change the meaning of + * a message like what happened when we deployed 1.19. $1 became the user + * performing the action which broke basically all bots around. + * + * Should cover the following log actions (which are most commonly used by bots): + * - block/block + * - block/unblock + * - delete/delete + * - delete/restore + * - newusers/create + * - newusers/create2 + * - newusers/autocreate + * - move/move + * - move/move_redir + * - protect/protect + * - protect/modifyprotect + * - protect/unprotect + * - upload/upload + * + * As well as the following Auto Edit Summaries: + * - blank + * - replace + * - rollback + * - undo + */ + + /** + * @covers LogFormatter::getIRCActionText + */ + function testIrcMsgForLogTypeBlock() { + # block/block + $this->assertIRCComment( + $this->context->msg( 'blocklogentry', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + 'block', 'block', + array(), + $this->user_comment + ); + # block/unblock + $this->assertIRCComment( + $this->context->msg( 'unblocklogentry', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + 'block', 'unblock', + array(), + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionText + */ + function testIrcMsgForLogTypeDelete() { + # delete/delete + $this->assertIRCComment( + $this->context->msg( 'deletedarticle', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + 'delete', 'delete', + array(), + $this->user_comment + ); + + # delete/restore + $this->assertIRCComment( + $this->context->msg( 'undeletedarticle', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + 'delete', 'restore', + array(), + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionText + */ + function testIrcMsgForLogTypeNewusers() { + $this->assertIRCComment( + 'New user account', + 'newusers', 'newusers', + array() + ); + $this->assertIRCComment( + 'New user account', + 'newusers', 'create', + array() + ); + $this->assertIRCComment( + 'created new account SomeTitle', + 'newusers', 'create2', + array() + ); + $this->assertIRCComment( + 'Account created automatically', + 'newusers', 'autocreate', + array() + ); + } + + /** + * @covers LogFormatter::getIRCActionText + */ + function testIrcMsgForLogTypeMove() { + $move_params = array( + '4::target' => $this->target->getPrefixedText(), + '5::noredir' => 0, + ); + + # move/move + $this->assertIRCComment( + $this->context->msg( '1movedto2', 'SomeTitle', 'TestTarget' )->plain() . ': ' . $this->user_comment, + 'move', 'move', + $move_params, + $this->user_comment + ); + + # move/move_redir + $this->assertIRCComment( + $this->context->msg( '1movedto2_redir', 'SomeTitle', 'TestTarget' )->plain() . ': ' . $this->user_comment, + 'move', 'move_redir', + $move_params, + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionText + */ + function testIrcMsgForLogTypePatrol() { + # patrol/patrol + $this->assertIRCComment( + $this->context->msg( 'patrol-log-line', 'revision 777', '[[SomeTitle]]', '' )->plain(), + 'patrol', 'patrol', + array( + '4::curid' => '777', + '5::previd' => '666', + '6::auto' => 0, + ) + ); + } + + /** + * @covers LogFormatter::getIRCActionText + */ + function testIrcMsgForLogTypeProtect() { + $protectParams = array( + '[edit=sysop] (indefinite) ‎[move=sysop] (indefinite)' + ); + + # protect/protect + $this->assertIRCComment( + $this->context->msg( 'protectedarticle', 'SomeTitle ' . $protectParams[0] )->plain() . ': ' . $this->user_comment, + 'protect', 'protect', + $protectParams, + $this->user_comment + ); + + # protect/unprotect + $this->assertIRCComment( + $this->context->msg( 'unprotectedarticle', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + 'protect', 'unprotect', + array(), + $this->user_comment + ); + + # protect/modify + $this->assertIRCComment( + $this->context->msg( 'modifiedarticleprotection', 'SomeTitle ' . $protectParams[0] )->plain() . ': ' . $this->user_comment, + 'protect', 'modify', + $protectParams, + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionText + */ + function testIrcMsgForLogTypeUpload() { + # upload/upload + $this->assertIRCComment( + $this->context->msg( 'uploadedimage', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + 'upload', 'upload', + array(), + $this->user_comment + ); + + # upload/overwrite + $this->assertIRCComment( + $this->context->msg( 'overwroteimage', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + 'upload', 'overwrite', + array(), + $this->user_comment + ); + } + + /** + * @todo: Emulate these edits somehow and extract + * raw edit summary from RecentChange object + * -- + + function testIrcMsgForBlankingAES() { + // $this->context->msg( 'autosumm-blank', .. ); + } + + function testIrcMsgForReplaceAES() { + // $this->context->msg( 'autosumm-replace', .. ); + } + + function testIrcMsgForRollbackAES() { + // $this->context->msg( 'revertpage', .. ); + } + + function testIrcMsgForUndoAES() { + // $this->context->msg( 'undo-summary', .. ); + } + + * -- + */ + + /** + * @param $expected String Expected IRC text without colors codes + * @param $type String Log type (move, delete, suppress, patrol ...) + * @param $action String A log type action + * @param $comment String (optional) A comment for the log action + * @param $msg String (optional) A message for PHPUnit :-) + */ + function assertIRCComment( $expected, $type, $action, $params, $comment = null, $msg = '' ) { + + $logEntry = new ManualLogEntry( $type, $action ); + $logEntry->setPerformer( $this->user ); + $logEntry->setTarget( $this->title ); + if ( $comment !== null ) { + $logEntry->setComment( $comment ); + } + $logEntry->setParameters( $params ); + + $formatter = LogFormatter::newFromEntry( $logEntry ); + $formatter->setContext( $this->context ); + + // Apply the same transformation as done in RecentChange::getIRCLine for rc_comment + $ircRcComment = RecentChange::cleanupForIRC( $formatter->getIRCActionComment() ); + + $this->assertEquals( + $expected, + $ircRcComment, + $msg + ); + } + +} diff --git a/tests/phpunit/includes/RevisionStorageTest.php b/tests/phpunit/includes/RevisionStorageTest.php new file mode 100644 index 00000000..8a7facec --- /dev/null +++ b/tests/phpunit/includes/RevisionStorageTest.php @@ -0,0 +1,408 @@ +tablesUsed = array_merge( $this->tablesUsed, + array( 'page', + 'revision', + 'text', + + 'recentchanges', + 'logging', + + 'page_props', + 'pagelinks', + 'categorylinks', + 'langlinks', + 'externallinks', + 'imagelinks', + 'templatelinks', + 'iwlinks' ) ); + } + + public function setUp() { + if ( !$this->the_page ) { + $this->the_page = $this->createPage( 'RevisionStorageTest_the_page', "just a dummy page" ); + } + } + + protected function makeRevision( $props = null ) { + if ( $props === null ) $props = array(); + + if ( !isset( $props['content'] ) && !isset( $props['text'] ) ) $props['text'] = 'Lorem Ipsum'; + if ( !isset( $props['comment'] ) ) $props['comment'] = 'just a test'; + if ( !isset( $props['page'] ) ) $props['page'] = $this->the_page->getId(); + + $rev = new Revision( $props ); + + $dbw = wfgetDB( DB_MASTER ); + $rev->insertOn( $dbw ); + + return $rev; + } + + protected function createPage( $page, $text, $model = null ) { + if ( is_string( $page ) ) $page = Title::newFromText( $page ); + if ( $page instanceof Title ) $page = new WikiPage( $page ); + + if ( $page->exists() ) { + $page->doDeleteArticle( "done" ); + } + + $page->doEdit( $text, "testing", EDIT_NEW ); + + return $page; + } + + protected function assertRevEquals( Revision $orig, Revision $rev = null ) { + $this->assertNotNull( $rev, 'missing revision' ); + + $this->assertEquals( $orig->getId(), $rev->getId() ); + $this->assertEquals( $orig->getPage(), $rev->getPage() ); + $this->assertEquals( $orig->getTimestamp(), $rev->getTimestamp() ); + $this->assertEquals( $orig->getUser(), $rev->getUser() ); + $this->assertEquals( $orig->getSha1(), $rev->getSha1() ); + } + + /** + * @covers Revision::__construct + */ + public function testConstructFromRow() + { + $orig = $this->makeRevision(); + + $dbr = wfgetDB( DB_SLAVE ); + $res = $dbr->select( 'revision', '*', array( 'rev_id' => $orig->getId() ) ); + $this->assertTrue( is_object( $res ), 'query failed' ); + + $row = $res->fetchObject(); + $res->free(); + + $rev = new Revision( $row ); + + $this->assertRevEquals( $orig, $rev ); + } + + /** + * @covers Revision::newFromRow + */ + public function testNewFromRow() + { + $orig = $this->makeRevision(); + + $dbr = wfgetDB( DB_SLAVE ); + $res = $dbr->select( 'revision', '*', array( 'rev_id' => $orig->getId() ) ); + $this->assertTrue( is_object( $res ), 'query failed' ); + + $row = $res->fetchObject(); + $res->free(); + + $rev = Revision::newFromRow( $row ); + + $this->assertRevEquals( $orig, $rev ); + } + + + /** + * @covers Revision::newFromArchiveRow + */ + public function testNewFromArchiveRow() + { + $page = $this->createPage( 'RevisionStorageTest_testNewFromArchiveRow', 'Lorem Ipsum' ); + $orig = $page->getRevision(); + $page->doDeleteArticle( 'test Revision::newFromArchiveRow' ); + + $dbr = wfgetDB( DB_SLAVE ); + $res = $dbr->select( 'archive', '*', array( 'ar_rev_id' => $orig->getId() ) ); + $this->assertTrue( is_object( $res ), 'query failed' ); + + $row = $res->fetchObject(); + $res->free(); + + $rev = Revision::newFromArchiveRow( $row ); + + $this->assertRevEquals( $orig, $rev ); + } + + /** + * @covers Revision::newFromId + */ + public function testNewFromId() + { + $orig = $this->makeRevision(); + + $rev = Revision::newFromId( $orig->getId() ); + + $this->assertRevEquals( $orig, $rev ); + } + + /** + * @covers Revision::fetchRevision + */ + public function testFetchRevision() + { + $page = $this->createPage( 'RevisionStorageTest_testFetchRevision', 'one' ); + $id1 = $page->getRevision()->getId(); + + $page->doEdit( 'two', 'second rev' ); + $id2 = $page->getRevision()->getId(); + + $res = Revision::fetchRevision( $page->getTitle() ); + + #note: order is unspecified + $rows = array(); + while ( ( $row = $res->fetchObject() ) ) { + $rows[ $row->rev_id ]= $row; + } + + $row = $res->fetchObject(); + $this->assertEquals( 1, count($rows), 'expected exactly one revision' ); + $this->assertArrayHasKey( $id2, $rows, 'missing revision with id ' . $id2 ); + } + + /** + * @covers Revision::selectFields + */ + public function testSelectFields() + { + $fields = Revision::selectFields(); + + $this->assertTrue( in_array( 'rev_id', $fields ), 'missing rev_id in list of fields'); + $this->assertTrue( in_array( 'rev_page', $fields ), 'missing rev_page in list of fields'); + $this->assertTrue( in_array( 'rev_timestamp', $fields ), 'missing rev_timestamp in list of fields'); + $this->assertTrue( in_array( 'rev_user', $fields ), 'missing rev_user in list of fields'); + } + + /** + * @covers Revision::getPage + */ + public function testGetPage() + { + $page = $this->the_page; + + $orig = $this->makeRevision( array( 'page' => $page->getId() ) ); + $rev = Revision::newFromId( $orig->getId() ); + + $this->assertEquals( $page->getId(), $rev->getPage() ); + } + + /** + * @covers Revision::getText + */ + public function testGetText() + { + $orig = $this->makeRevision( array( 'text' => 'hello hello.' ) ); + $rev = Revision::newFromId( $orig->getId() ); + + $this->assertEquals( 'hello hello.', $rev->getText() ); + } + + /** + * @covers Revision::revText + */ + public function testRevText() + { + $this->hideDeprecated( 'Revision::revText' ); + $orig = $this->makeRevision( array( 'text' => 'hello hello rev.' ) ); + $rev = Revision::newFromId( $orig->getId() ); + + $this->assertEquals( 'hello hello rev.', $rev->revText() ); + } + + /** + * @covers Revision::getRawText + */ + public function testGetRawText() + { + $orig = $this->makeRevision( array( 'text' => 'hello hello raw.' ) ); + $rev = Revision::newFromId( $orig->getId() ); + + $this->assertEquals( 'hello hello raw.', $rev->getRawText() ); + } + /** + * @covers Revision::isCurrent + */ + public function testIsCurrent() + { + $page = $this->createPage( 'RevisionStorageTest_testIsCurrent', 'Lorem Ipsum' ); + $rev1 = $page->getRevision(); + + # @todo: find out if this should be true + # $this->assertTrue( $rev1->isCurrent() ); + + $rev1x = Revision::newFromId( $rev1->getId() ); + $this->assertTrue( $rev1x->isCurrent() ); + + $page->doEdit( 'Bla bla', 'second rev' ); + $rev2 = $page->getRevision(); + + # @todo: find out if this should be true + # $this->assertTrue( $rev2->isCurrent() ); + + $rev1x = Revision::newFromId( $rev1->getId() ); + $this->assertFalse( $rev1x->isCurrent() ); + + $rev2x = Revision::newFromId( $rev2->getId() ); + $this->assertTrue( $rev2x->isCurrent() ); + } + + /** + * @covers Revision::getPrevious + */ + public function testGetPrevious() + { + $page = $this->createPage( 'RevisionStorageTest_testGetPrevious', 'Lorem Ipsum testGetPrevious' ); + $rev1 = $page->getRevision(); + + $this->assertNull( $rev1->getPrevious() ); + + $page->doEdit( 'Bla bla', 'second rev testGetPrevious' ); + $rev2 = $page->getRevision(); + + $this->assertNotNull( $rev2->getPrevious() ); + $this->assertEquals( $rev1->getId(), $rev2->getPrevious()->getId() ); + } + + /** + * @covers Revision::getNext + */ + public function testGetNext() + { + $page = $this->createPage( 'RevisionStorageTest_testGetNext', 'Lorem Ipsum testGetNext' ); + $rev1 = $page->getRevision(); + + $this->assertNull( $rev1->getNext() ); + + $page->doEdit( 'Bla bla', 'second rev testGetNext' ); + $rev2 = $page->getRevision(); + + $this->assertNotNull( $rev1->getNext() ); + $this->assertEquals( $rev2->getId(), $rev1->getNext()->getId() ); + } + + /** + * @covers Revision::newNullRevision + */ + public function testNewNullRevision() + { + $page = $this->createPage( 'RevisionStorageTest_testNewNullRevision', 'some testing text' ); + $orig = $page->getRevision(); + + $dbw = wfGetDB( DB_MASTER ); + $rev = Revision::newNullRevision( $dbw, $page->getId(), 'a null revision', false ); + + $this->assertNotEquals( $orig->getId(), $rev->getId(), 'new null revision shold have a different id from the original revision' ); + $this->assertEquals( $orig->getTextId(), $rev->getTextId(), 'new null revision shold have the same text id as the original revision' ); + $this->assertEquals( 'some testing text', $rev->getText() ); + } + + public function dataUserWasLastToEdit() { + return array( + array( #0 + 3, true, # actually the last edit + ), + array( #1 + 2, true, # not the current edit, but still by this user + ), + array( #2 + 1, false, # edit by another user + ), + array( #3 + 0, false, # first edit, by this user, but another user edited in the mean time + ), + ); + } + + /** + * @dataProvider dataUserWasLastToEdit + */ + public function testUserWasLastToEdit( $sinceIdx, $expectedLast ) { + $userA = \User::newFromName( "RevisionStorageTest_userA" ); + $userB = \User::newFromName( "RevisionStorageTest_userB" ); + + if ( $userA->getId() === 0 ) { + $userA = \User::createNew( $userA->getName() ); + } + + if ( $userB->getId() === 0 ) { + $userB = \User::createNew( $userB->getName() ); + } + + $dbw = wfGetDB( DB_MASTER ); + $revisions = array(); + + // create revisions ----------------------------- + $page = WikiPage::factory( Title::newFromText( 'RevisionStorageTest_testUserWasLastToEdit' ) ); + + # zero + $revisions[0] = new Revision( array( + 'page' => $page->getId(), + 'timestamp' => '20120101000000', + 'user' => $userA->getId(), + 'text' => 'zero', + 'summary' => 'edit zero' + ) ); + $revisions[0]->insertOn( $dbw ); + + # one + $revisions[1] = new Revision( array( + 'page' => $page->getId(), + 'timestamp' => '20120101000100', + 'user' => $userA->getId(), + 'text' => 'one', + 'summary' => 'edit one' + ) ); + $revisions[1]->insertOn( $dbw ); + + # two + $revisions[2] = new Revision( array( + 'page' => $page->getId(), + 'timestamp' => '20120101000200', + 'user' => $userB->getId(), + 'text' => 'two', + 'summary' => 'edit two' + ) ); + $revisions[2]->insertOn( $dbw ); + + # three + $revisions[3] = new Revision( array( + 'page' => $page->getId(), + 'timestamp' => '20120101000300', + 'user' => $userA->getId(), + 'text' => 'three', + 'summary' => 'edit three' + ) ); + $revisions[3]->insertOn( $dbw ); + + # four + $revisions[4] = new Revision( array( + 'page' => $page->getId(), + 'timestamp' => '20120101000200', + 'user' => $userA->getId(), + 'text' => 'zero', + 'summary' => 'edit four' + ) ); + $revisions[4]->insertOn( $dbw ); + + // test it --------------------------------- + $since = $revisions[ $sinceIdx ]->getTimestamp(); + + $wasLast = Revision::userWasLastToEdit( $dbw, $page->getId(), $userA->getId(), $since ); + + $this->assertEquals( $expectedLast, $wasLast ); + } +} diff --git a/tests/phpunit/includes/SampleTest.php b/tests/phpunit/includes/SampleTest.php index 77a371d5..59ba0a04 100644 --- a/tests/phpunit/includes/SampleTest.php +++ b/tests/phpunit/includes/SampleTest.php @@ -47,7 +47,7 @@ class TestSample extends MediaWikiLangTestCase { array( 'Text', null, 'Text' ), array( 'text', null, 'Text' ), array( 'Text', NS_USER, 'User:Text' ), - array( 'Photo.jpg', NS_IMAGE, 'File:Photo.jpg' ) + array( 'Photo.jpg', NS_FILE, 'File:Photo.jpg' ) ); } diff --git a/tests/phpunit/includes/SanitizerTest.php b/tests/phpunit/includes/SanitizerTest.php index b76aa5c7..66af2581 100644 --- a/tests/phpunit/includes/SanitizerTest.php +++ b/tests/phpunit/includes/SanitizerTest.php @@ -110,21 +110,27 @@ class SanitizerTest extends MediaWikiTestCase { $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=&foobar;' ), array( 'foo' => '&foobar;' ), 'Entity-like items are accepted' ); } - function testDeprecatedAttributes() { - $GLOBALS['wgCleanupPresentationalAttributes'] = true; - $this->assertEquals( Sanitizer::fixTagAttributes( 'clear="left"', 'br' ), ' style="clear: left;"', 'Deprecated attributes are converted to styles when enabled.' ); - $this->assertEquals( Sanitizer::fixTagAttributes( 'clear="all"', 'br' ), ' style="clear: both;"', 'clear=all is converted to clear: both; not clear: all;' ); - $this->assertEquals( Sanitizer::fixTagAttributes( 'CLEAR="ALL"', 'br' ), ' style="clear: both;"', 'clear=ALL is not treated differently from clear=all' ); - $this->assertEquals( Sanitizer::fixTagAttributes( 'width="100"', 'td' ), ' style="width: 100px;"', 'Numeric sizes use pixels instead of numbers.' ); - $this->assertEquals( Sanitizer::fixTagAttributes( 'width="100%"', 'td' ), ' style="width: 100%;"', 'Units are allowed in sizes.' ); - $this->assertEquals( Sanitizer::fixTagAttributes( 'WIDTH="100%"', 'td' ), ' style="width: 100%;"', 'Uppercase WIDTH is treated as lowercase width.' ); - $this->assertEquals( Sanitizer::fixTagAttributes( 'WiDTh="100%"', 'td' ), ' style="width: 100%;"', 'Mixed case does not break WiDTh.' ); - $this->assertEquals( Sanitizer::fixTagAttributes( 'nowrap="true"', 'td' ), ' style="white-space: nowrap;"', 'nowrap attribute is output as white-space: nowrap; not something else.' ); - $this->assertEquals( Sanitizer::fixTagAttributes( 'nowrap=""', 'td' ), ' style="white-space: nowrap;"', 'nowrap="" is considered true, not false' ); - $this->assertEquals( Sanitizer::fixTagAttributes( 'NOWRAP="true"', 'td' ), ' style="white-space: nowrap;"', 'nowrap attribute works when uppercase.' ); - $this->assertEquals( Sanitizer::fixTagAttributes( 'NoWrAp="true"', 'td' ), ' style="white-space: nowrap;"', 'nowrap attribute works when mixed-case.' ); - $GLOBALS['wgCleanupPresentationalAttributes'] = false; - $this->assertEquals( Sanitizer::fixTagAttributes( 'clear="left"', 'br' ), ' clear="left"', 'Deprecated attributes are not converted to styles when enabled.' ); + /** + * @dataProvider provideDeprecatedAttributes + */ + function testDeprecatedAttributesUnaltered( $inputAttr, $inputEl ) { + $this->assertEquals( " $inputAttr", Sanitizer::fixTagAttributes( $inputAttr, $inputEl ) ); + } + + public static function provideDeprecatedAttributes() { + return array( + array( 'clear="left"', 'br' ), + array( 'clear="all"', 'br' ), + array( 'width="100"', 'td' ), + array( 'nowrap="true"', 'td' ), + array( 'nowrap=""', 'td' ), + array( 'align="right"', 'td' ), + array( 'align="center"', 'table' ), + array( 'align="left"', 'tr' ), + array( 'align="center"', 'div' ), + array( 'align="left"', 'h1' ), + array( 'align="left"', 'span' ), + ); } /** diff --git a/tests/phpunit/includes/TemplateCategoriesTest.php b/tests/phpunit/includes/TemplateCategoriesTest.php index de9d6dc6..39ce6e31 100644 --- a/tests/phpunit/includes/TemplateCategoriesTest.php +++ b/tests/phpunit/includes/TemplateCategoriesTest.php @@ -3,26 +3,24 @@ /** * @group Database */ -require dirname( __FILE__ ) . "/../../../maintenance/runJobs.php"; +require __DIR__ . "/../../../maintenance/runJobs.php"; class TemplateCategoriesTest extends MediaWikiLangTestCase { function testTemplateCategories() { - global $wgUser; - $title = Title::newFromText( "Categorized from template" ); - $article = new Article( $title ); - $wgUser = new User(); - $wgUser->mRights['*'] = array( 'createpage', 'edit', 'purge' ); + $page = WikiPage::factory( $title ); + $user = new User(); + $user->mRights = array( 'createpage', 'edit', 'purge' ); - $status = $article->doEdit( '{{Categorising template}}', 'Create a page with a template', 0 ); + $status = $page->doEdit( '{{Categorising template}}', 'Create a page with a template', 0, false, $user ); $this->assertEquals( array() , $title->getParentCategories() ); - $template = new Article( Title::newFromText( 'Template:Categorising template' ) ); - $status = $template->doEdit( '[[Category:Solved bugs]]', 'Add a category through a template', 0 ); + $template = WikiPage::factory( Title::newFromText( 'Template:Categorising template' ) ); + $status = $template->doEdit( '[[Category:Solved bugs]]', 'Add a category through a template', 0, false, $user ); // Run the job queue $jobs = new RunJobs; diff --git a/tests/phpunit/includes/TestUser.php b/tests/phpunit/includes/TestUser.php new file mode 100644 index 00000000..c4d89455 --- /dev/null +++ b/tests/phpunit/includes/TestUser.php @@ -0,0 +1,58 @@ +username = $username; + $this->realname = $realname; + $this->email = $email; + $this->groups = $groups; + + // don't allow user to hardcode or select passwords -- people sometimes run tests + // on live wikis. Sometimes we create sysop users in these tests. A sysop user with + // a known password would be a Bad Thing. + $this->password = User::randomPassword(); + + $this->user = User::newFromName( $this->username ); + $this->user->load(); + + // In an ideal world we'd have a new wiki (or mock data store) for every single test. + // But for now, we just need to create or update the user with the desired properties. + // we particularly need the new password, since we just generated it randomly. + // In core MediaWiki, there is no functionality to delete users, so this is the best we can do. + if ( !$this->user->getID() ) { + // create the user + $this->user = User::createNew( + $this->username, array( + "email" => $this->email, + "real_name" => $this->realname + ) + ); + if ( !$this->user ) { + throw new Exception( "error creating user" ); + } + } + + // update the user to use the new random password and other details + $this->user->setPassword( $this->password ); + $this->user->setEmail( $this->email ); + $this->user->setRealName( $this->realname ); + // remove all groups, replace with any groups specified + foreach ( $this->user->getGroups() as $group ) { + $this->user->removeGroup( $group ); + } + if ( count( $this->groups ) ) { + foreach ( $this->groups as $group ) { + $this->user->addGroup( $group ); + } + } + $this->user->saveSettings(); + + } +} diff --git a/tests/phpunit/includes/TimestampTest.php b/tests/phpunit/includes/TimestampTest.php new file mode 100644 index 00000000..231228f5 --- /dev/null +++ b/tests/phpunit/includes/TimestampTest.php @@ -0,0 +1,72 @@ +assertEquals( $expected, $timestamp->getTimestamp( TS_MW ) ); + } + + /** + * Test outputting valid timestamps to different formats. + * @dataProvider provideValidTimestamps + */ + function testValidOutput( $format, $expected, $original ) { + $timestamp = new MWTimestamp( $original ); + $this->assertEquals( $expected, (string) $timestamp->getTimestamp( $format ) ); + } + + /** + * Test an invalid timestamp. + * @expectedException TimestampException + */ + function testInvalidParse() { + $timestamp = new MWTimestamp( "This is not a timestamp." ); + } + + /** + * Test requesting an invalid output format. + * @expectedException TimestampException + */ + function testInvalidOutput() { + $timestamp = new MWTimestamp( '1343761268' ); + $timestamp->getTimestamp( 98 ); + } + + /** + * Test human readable timestamp format. + */ + function testHumanOutput() { + $timestamp = new MWTimestamp( time() - 3600 ); + $this->assertEquals( "1 hour ago", $timestamp->getHumanTimestamp()->toString() ); + } + + /** + * Returns a list of valid timestamps in the format: + * array( type, timestamp_of_type, timestamp_in_MW ) + */ + function provideValidTimestamps() { + return array( + // Various formats + array( TS_UNIX, '1343761268', '20120731190108' ), + array( TS_MW, '20120731190108', '20120731190108' ), + array( TS_DB, '2012-07-31 19:01:08', '20120731190108' ), + array( TS_ISO_8601, '2012-07-31T19:01:08Z', '20120731190108' ), + array( TS_ISO_8601_BASIC, '20120731T190108Z', '20120731190108' ), + array( TS_EXIF, '2012:07:31 19:01:08', '20120731190108' ), + array( TS_RFC2822, 'Tue, 31 Jul 2012 19:01:08 GMT', '20120731190108' ), + array( TS_ORACLE, '31-07-2012 19:01:08.000000', '20120731190108' ), + array( TS_POSTGRES, '2012-07-31 19:01:08 GMT', '20120731190108' ), + array( TS_DB2, '2012-07-31 19:01:08', '20120731190108' ), + // Some extremes and weird values + array( TS_ISO_8601, '9999-12-31T23:59:59Z', '99991231235959' ), + array( TS_UNIX, '-62135596801', '00001231235959' ) + ); + } +} diff --git a/tests/phpunit/includes/TitleMethodsTest.php b/tests/phpunit/includes/TitleMethodsTest.php index 2f1103e8..aed658ba 100644 --- a/tests/phpunit/includes/TitleMethodsTest.php +++ b/tests/phpunit/includes/TitleMethodsTest.php @@ -21,8 +21,8 @@ class TitleMethodsTest extends MediaWikiTestCase { $titleA = Title::newFromText( $titleA ); $titleB = Title::newFromText( $titleB ); - $this->assertEquals( $titleA->equals( $titleB ), $expectedBool ); - $this->assertEquals( $titleB->equals( $titleA ), $expectedBool ); + $this->assertEquals( $expectedBool, $titleA->equals( $titleB ) ); + $this->assertEquals( $expectedBool, $titleB->equals( $titleA ) ); } public function dataInNamespace() { @@ -43,7 +43,7 @@ class TitleMethodsTest extends MediaWikiTestCase { */ public function testInNamespace( $title, $ns, $expectedBool ) { $title = Title::newFromText( $title ); - $this->assertEquals( $title->inNamespace( $ns ), $expectedBool ); + $this->assertEquals( $expectedBool, $title->inNamespace( $ns ) ); } public function testInNamespaces() { @@ -72,7 +72,130 @@ class TitleMethodsTest extends MediaWikiTestCase { */ public function testHasSubjectNamespace( $title, $ns, $expectedBool ) { $title = Title::newFromText( $title ); - $this->assertEquals( $title->hasSubjectNamespace( $ns ), $expectedBool ); + $this->assertEquals( $expectedBool, $title->hasSubjectNamespace( $ns ) ); + } + + public function dataIsCssOrJsPage() { + return array( + array( 'Foo', false ), + array( 'Foo.js', false ), + array( 'Foo/bar.js', false ), + array( 'User:Foo', false ), + array( 'User:Foo.js', false ), + array( 'User:Foo/bar.js', false ), + array( 'User:Foo/bar.css', false ), + array( 'User talk:Foo/bar.css', false ), + array( 'User:Foo/bar.js.xxx', false ), + array( 'User:Foo/bar.xxx', false ), + array( 'MediaWiki:Foo.js', true ), + array( 'MediaWiki:Foo.css', true ), + array( 'MediaWiki:Foo.JS', false ), + array( 'MediaWiki:Foo.CSS', false ), + array( 'MediaWiki:Foo.css.xxx', false ), + ); + } + + /** + * @dataProvider dataIsCssOrJsPage + */ + public function testIsCssOrJsPage( $title, $expectedBool ) { + $title = Title::newFromText( $title ); + $this->assertEquals( $expectedBool, $title->isCssOrJsPage() ); + } + + + public function dataIsCssJsSubpage() { + return array( + array( 'Foo', false ), + array( 'Foo.js', false ), + array( 'Foo/bar.js', false ), + array( 'User:Foo', false ), + array( 'User:Foo.js', false ), + array( 'User:Foo/bar.js', true ), + array( 'User:Foo/bar.css', true ), + array( 'User talk:Foo/bar.css', false ), + array( 'User:Foo/bar.js.xxx', false ), + array( 'User:Foo/bar.xxx', false ), + array( 'MediaWiki:Foo.js', false ), + array( 'User:Foo/bar.JS', false ), + array( 'User:Foo/bar.CSS', false ), + ); + } + + /** + * @dataProvider dataIsCssJsSubpage + */ + public function testIsCssJsSubpage( $title, $expectedBool ) { + $title = Title::newFromText( $title ); + $this->assertEquals( $expectedBool, $title->isCssJsSubpage() ); + } + + public function dataIsCssSubpage() { + return array( + array( 'Foo', false ), + array( 'Foo.css', false ), + array( 'User:Foo', false ), + array( 'User:Foo.js', false ), + array( 'User:Foo.css', false ), + array( 'User:Foo/bar.js', false ), + array( 'User:Foo/bar.css', true ), + ); + } + + /** + * @dataProvider dataIsCssSubpage + */ + public function testIsCssSubpage( $title, $expectedBool ) { + $title = Title::newFromText( $title ); + $this->assertEquals( $expectedBool, $title->isCssSubpage() ); + } + + public function dataIsJsSubpage() { + return array( + array( 'Foo', false ), + array( 'Foo.css', false ), + array( 'User:Foo', false ), + array( 'User:Foo.js', false ), + array( 'User:Foo.css', false ), + array( 'User:Foo/bar.js', true ), + array( 'User:Foo/bar.css', false ), + ); + } + + /** + * @dataProvider dataIsJsSubpage + */ + public function testIsJsSubpage( $title, $expectedBool ) { + $title = Title::newFromText( $title ); + $this->assertEquals( $expectedBool, $title->isJsSubpage() ); + } + + public function dataIsWikitextPage() { + return array( + array( 'Foo', true ), + array( 'Foo.js', true ), + array( 'Foo/bar.js', true ), + array( 'User:Foo', true ), + array( 'User:Foo.js', true ), + array( 'User:Foo/bar.js', false ), + array( 'User:Foo/bar.css', false ), + array( 'User talk:Foo/bar.css', true ), + array( 'User:Foo/bar.js.xxx', true ), + array( 'User:Foo/bar.xxx', true ), + array( 'MediaWiki:Foo.js', false ), + array( 'MediaWiki:Foo.css', false ), + array( 'MediaWiki:Foo/bar.css', false ), + array( 'User:Foo/bar.JS', true ), + array( 'User:Foo/bar.CSS', true ), + ); + } + + /** + * @dataProvider dataIsWikitextPage + */ + public function testIsWikitextPage( $title, $expectedBool ) { + $title = Title::newFromText( $title ); + $this->assertEquals( $expectedBool, $title->isWikitextPage() ); } } diff --git a/tests/phpunit/includes/TitleTest.php b/tests/phpunit/includes/TitleTest.php index 1c8be5f9..f61652df 100644 --- a/tests/phpunit/includes/TitleTest.php +++ b/tests/phpunit/includes/TitleTest.php @@ -77,4 +77,79 @@ class TitleTest extends MediaWikiTestCase { } + /** + * @dataProvider provideCasesForGetpageviewlanguage + */ + function testGetpageviewlanguage( $expected, $titleText, $contLang, $lang, $variant, $msg='' ) { + // Save globals + global $wgContLang, $wgLang, $wgAllowUserJs, $wgLanguageCode, $wgDefaultLanguageVariant; + $save['wgContLang'] = $wgContLang; + $save['wgLang'] = $wgLang; + $save['wgAllowUserJs'] = $wgAllowUserJs; + $save['wgLanguageCode'] = $wgLanguageCode; + $save['wgDefaultLanguageVariant'] = $wgDefaultLanguageVariant; + + // Setup test environnement: + $wgContLang = Language::factory( $contLang ); + $wgLang = Language::factory( $lang ); + # To test out .js titles: + $wgAllowUserJs = true; + $wgLanguageCode = $contLang; + $wgDefaultLanguageVariant = $variant; + + $title = Title::newFromText( $titleText ); + $this->assertInstanceOf( 'Title', $title, + "Test must be passed a valid title text, you gave '$titleText'" + ); + $this->assertEquals( $expected, + $title->getPageViewLanguage()->getCode(), + $msg + ); + + // Restore globals + $wgContLang = $save['wgContLang']; + $wgLang = $save['wgLang']; + $wgAllowUserJs = $save['wgAllowUserJs']; + $wgLanguageCode = $save['wgLanguageCode']; + $wgDefaultLanguageVariant = $save['wgDefaultLanguageVariant']; + } + + function provideCasesForGetpageviewlanguage() { + # Format: + # - expected + # - Title name + # - wgContLang (expected in most case) + # - wgLang (on some specific pages) + # - wgDefaultLanguageVariant + # - Optional message + return array( + array( 'fr', 'Main_page', 'fr', 'fr', false ), + array( 'es', 'Main_page', 'es', 'zh-tw', false ), + array( 'zh', 'Main_page', 'zh', 'zh-tw', false ), + + array( 'es', 'Main_page', 'es', 'zh-tw', 'zh-cn' ), + array( 'es', 'MediaWiki:About', 'es', 'zh-tw', 'zh-cn' ), + array( 'es', 'MediaWiki:About/', 'es', 'zh-tw', 'zh-cn' ), + array( 'de', 'MediaWiki:About/de', 'es', 'zh-tw', 'zh-cn' ), + array( 'en', 'MediaWiki:Common.js', 'es', 'zh-tw', 'zh-cn' ), + array( 'en', 'MediaWiki:Common.css', 'es', 'zh-tw', 'zh-cn' ), + array( 'en', 'User:JohnDoe/Common.js', 'es', 'zh-tw', 'zh-cn' ), + array( 'en', 'User:JohnDoe/Monobook.css', 'es', 'zh-tw', 'zh-cn' ), + + array( 'zh-cn', 'Main_page', 'zh', 'zh-tw', 'zh-cn' ), + array( 'zh', 'MediaWiki:About', 'zh', 'zh-tw', 'zh-cn' ), + array( 'zh', 'MediaWiki:About/', 'zh', 'zh-tw', 'zh-cn' ), + array( 'de', 'MediaWiki:About/de', 'zh', 'zh-tw', 'zh-cn' ), + array( 'zh-cn', 'MediaWiki:About/zh-cn', 'zh', 'zh-tw', 'zh-cn' ), + array( 'zh-tw', 'MediaWiki:About/zh-tw', 'zh', 'zh-tw', 'zh-cn' ), + array( 'en', 'MediaWiki:Common.js', 'zh', 'zh-tw', 'zh-cn' ), + array( 'en', 'MediaWiki:Common.css', 'zh', 'zh-tw', 'zh-cn' ), + array( 'en', 'User:JohnDoe/Common.js', 'zh', 'zh-tw', 'zh-cn' ), + array( 'en', 'User:JohnDoe/Monobook.css', 'zh', 'zh-tw', 'zh-cn' ), + + array( 'zh-tw', 'Special:NewPages', 'es', 'zh-tw', 'zh-cn' ), + array( 'zh-tw', 'Special:NewPages', 'zh', 'zh-tw', 'zh-cn' ), + + ); + } } diff --git a/tests/phpunit/includes/UserTest.php b/tests/phpunit/includes/UserTest.php index ef03e835..7a424aef 100644 --- a/tests/phpunit/includes/UserTest.php +++ b/tests/phpunit/includes/UserTest.php @@ -140,4 +140,32 @@ class UserTest extends MediaWikiTestCase { array( 'Ab cd', false, ' Ideographic space' ), ); } + + /** + * Test, if for all rights a right- message exist, + * which is used on Special:ListGroupRights as help text + * Extensions and core + */ + public function testAllRightsWithMessage() { + //Getting all user rights, for core: User::$mCoreRights, for extensions: $wgAvailableRights + $allRights = User::getAllRights(); + $allMessageKeys = Language::getMessageKeysFor( 'en' ); + + $rightsWithMessage = array(); + foreach ( $allMessageKeys as $message ) { + // === 0: must be at beginning of string (position 0) + if ( strpos( $message, 'right-' ) === 0 ) { + $rightsWithMessage[] = substr( $message, strlen( 'right-' ) ); + } + } + + sort( $allRights ); + sort( $rightsWithMessage ); + + $this->assertEquals( + $allRights, + $rightsWithMessage, + 'Each user rights (core/extensions) has a corresponding right- message.' + ); + } } diff --git a/tests/phpunit/includes/WebRequestTest.php b/tests/phpunit/includes/WebRequestTest.php index e72408f6..1fc0b4b3 100644 --- a/tests/phpunit/includes/WebRequestTest.php +++ b/tests/phpunit/includes/WebRequestTest.php @@ -1,14 +1,22 @@ assertEquals( $expected, $result, $description ); } @@ -91,13 +99,11 @@ class WebRequestTest extends MediaWikiTestCase { */ function testGetIP( $expected, $input, $squid, $private, $description ) { global $wgSquidServersNoPurge, $wgUsePrivateIPs; - $oldServer = $_SERVER; $_SERVER = $input; $wgSquidServersNoPurge = $squid; $wgUsePrivateIPs = $private; $request = new WebRequest(); $result = $request->getIP(); - $_SERVER = $oldServer; $this->assertEquals( $expected, $result, $description ); } @@ -182,4 +188,29 @@ class WebRequestTest extends MediaWikiTestCase { # Next call throw an exception about lacking an IP $request->getIP(); } + + function languageProvider() { + return array( + array( '', array(), 'Empty Accept-Language header' ), + array( 'en', array( 'en' => 1 ), 'One language' ), + array( 'en, ar', array( 'en' => 1, 'ar' => 1 ), 'Two languages listed in appearance order.' ), + array( 'zh-cn,zh-tw', array( 'zh-cn' => 1, 'zh-tw' => 1 ), 'Two equally prefered languages, listed in appearance order per rfc3282. Checks c9119' ), + array( 'es, en; q=0.5', array( 'es' => 1, 'en' => '0.5' ), 'Spanish as first language and English and second' ), + array( 'en; q=0.5, es', array( 'es' => 1, 'en' => '0.5' ), 'Less prefered language first' ), + array( 'fr, en; q=0.5, es', array( 'fr' => 1, 'es' => 1, 'en' => '0.5' ), 'Three languages' ), + array( 'en; q=0.5, es', array( 'es' => 1, 'en' => '0.5' ), 'Two languages' ), + array( 'en, zh;q=0', array( 'en' => 1 ), "It's Chinese to me" ), + array( 'es; q=1, pt;q=0.7, it; q=0.6, de; q=0.1, ru;q=0', array( 'es' => '1', 'pt' => '0.7', 'it' => '0.6', 'de' => '0.1' ), 'Preference for romance languages' ), + array( 'en-gb, en-us; q=1', array( 'en-gb' => 1, 'en-us' => '1' ), 'Two equally prefered English variants' ), + ); + } + + /** + * @dataProvider languageProvider + */ + function testAcceptLang($acceptLanguageHeader, $expectedLanguages, $description) { + $_SERVER = array( 'HTTP_ACCEPT_LANGUAGE' => $acceptLanguageHeader ); + $request = new WebRequest(); + $this->assertSame( $request->getAcceptLang(), $expectedLanguages, $description); + } } diff --git a/tests/phpunit/includes/WikiPageTest.php b/tests/phpunit/includes/WikiPageTest.php new file mode 100644 index 00000000..0e1e1ce8 --- /dev/null +++ b/tests/phpunit/includes/WikiPageTest.php @@ -0,0 +1,784 @@ +tablesUsed = array_merge ( $this->tablesUsed, + array( 'page', + 'revision', + 'text', + + 'recentchanges', + 'logging', + + 'page_props', + 'pagelinks', + 'categorylinks', + 'langlinks', + 'externallinks', + 'imagelinks', + 'templatelinks', + 'iwlinks' ) ); + } + + public function setUp() { + parent::setUp(); + $this->pages_to_delete = array(); + } + + public function tearDown() { + foreach ( $this->pages_to_delete as $p ) { + /* @var $p WikiPage */ + + try { + if ( $p->exists() ) { + $p->doDeleteArticle( "testing done." ); + } + } catch ( MWException $ex ) { + // fail silently + } + } + parent::tearDown(); + } + + protected function newPage( $title ) { + if ( is_string( $title ) ) $title = Title::newFromText( $title ); + + $p = new WikiPage( $title ); + + $this->pages_to_delete[] = $p; + + return $p; + } + + protected function createPage( $page, $text, $model = null ) { + if ( is_string( $page ) ) $page = Title::newFromText( $page ); + if ( $page instanceof Title ) $page = $this->newPage( $page ); + + $page->doEdit( $text, "testing", EDIT_NEW ); + + return $page; + } + + public function testDoEdit() { + $title = Title::newFromText( "WikiPageTest_testDoEdit" ); + + $page = $this->newPage( $title ); + + $text = "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam " + . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat."; + + $page->doEdit( $text, "testing 1" ); + + $this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" ); + $this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" ); + + $id = $page->getId(); + + # ------------------------ + $page = new WikiPage( $title ); + + $retrieved = $page->getText(); + $this->assertEquals( $text, $retrieved, 'retrieved text doesn\'t equal original' ); + + # ------------------------ + $text = "At vero eos et accusam et justo duo [[dolores]] et ea rebum. " + . "Stet clita kasd [[gubergren]], no sea takimata sanctus est."; + + $page->doEdit( $text, "testing 2" ); + + # ------------------------ + $page = new WikiPage( $title ); + + $retrieved = $page->getText(); + $this->assertEquals( $text, $retrieved, 'retrieved text doesn\'t equal original' ); + + # ------------------------ + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); + $n = $res->numRows(); + $res->free(); + + $this->assertEquals( 2, $n, 'pagelinks should contain two links from the page' ); + } + + public function testDoQuickEdit() { + global $wgUser; + + $page = $this->createPage( "WikiPageTest_testDoQuickEdit", "original text" ); + + $text = "quick text"; + $page->doQuickEdit( $text, $wgUser, "testing q" ); + + # --------------------- + $page = new WikiPage( $page->getTitle() ); + $this->assertEquals( $text, $page->getText() ); + } + + public function testDoDeleteArticle() { + $page = $this->createPage( "WikiPageTest_testDoDeleteArticle", "[[original text]] foo" ); + $id = $page->getId(); + + $page->doDeleteArticle( "testing deletion" ); + + $this->assertFalse( $page->exists(), "WikiPage::exists should return false after page was deleted" ); + $this->assertFalse( $page->getText(), "WikiPage::getText should return false after page was deleted" ); + + $t = Title::newFromText( $page->getTitle()->getPrefixedText() ); + $this->assertFalse( $t->exists(), "Title::exists should return false after page was deleted" ); + + # ------------------------ + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); + $n = $res->numRows(); + $res->free(); + + $this->assertEquals( 0, $n, 'pagelinks should contain no more links from the page' ); + } + + public function testDoDeleteUpdates() { + $page = $this->createPage( "WikiPageTest_testDoDeleteArticle", "[[original text]] foo" ); + $id = $page->getId(); + + $page->doDeleteUpdates( $id ); + + # ------------------------ + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); + $n = $res->numRows(); + $res->free(); + + $this->assertEquals( 0, $n, 'pagelinks should contain no more links from the page' ); + } + + public function testGetRevision() { + $page = $this->newPage( "WikiPageTest_testGetRevision" ); + + $rev = $page->getRevision(); + $this->assertNull( $rev ); + + # ----------------- + $this->createPage( $page, "some text" ); + + $rev = $page->getRevision(); + + $this->assertEquals( $page->getLatest(), $rev->getId() ); + $this->assertEquals( "some text", $rev->getText() ); + } + + public function testGetText() { + $page = $this->newPage( "WikiPageTest_testGetText" ); + + $text = $page->getText(); + $this->assertFalse( $text ); + + # ----------------- + $this->createPage( $page, "some text" ); + + $text = $page->getText(); + $this->assertEquals( "some text", $text ); + } + + public function testGetRawText() { + $page = $this->newPage( "WikiPageTest_testGetRawText" ); + + $text = $page->getRawText(); + $this->assertFalse( $text ); + + # ----------------- + $this->createPage( $page, "some text" ); + + $text = $page->getRawText(); + $this->assertEquals( "some text", $text ); + } + + + public function testExists() { + $page = $this->newPage( "WikiPageTest_testExists" ); + $this->assertFalse( $page->exists() ); + + # ----------------- + $this->createPage( $page, "some text" ); + $this->assertTrue( $page->exists() ); + + $page = new WikiPage( $page->getTitle() ); + $this->assertTrue( $page->exists() ); + + # ----------------- + $page->doDeleteArticle( "done testing" ); + $this->assertFalse( $page->exists() ); + + $page = new WikiPage( $page->getTitle() ); + $this->assertFalse( $page->exists() ); + } + + public function dataHasViewableContent() { + return array( + array( 'WikiPageTest_testHasViewableContent', false, true ), + array( 'Special:WikiPageTest_testHasViewableContent', false ), + array( 'MediaWiki:WikiPageTest_testHasViewableContent', false ), + array( 'Special:Userlogin', true ), + array( 'MediaWiki:help', true ), + ); + } + + /** + * @dataProvider dataHasViewableContent + */ + public function testHasViewableContent( $title, $viewable, $create = false ) { + $page = $this->newPage( $title ); + $this->assertEquals( $viewable, $page->hasViewableContent() ); + + if ( $create ) { + $this->createPage( $page, "some text" ); + $this->assertTrue( $page->hasViewableContent() ); + + $page = new WikiPage( $page->getTitle() ); + $this->assertTrue( $page->hasViewableContent() ); + } + } + + public function dataGetRedirectTarget() { + return array( + array( 'WikiPageTest_testGetRedirectTarget_1', "hello world", null ), + array( 'WikiPageTest_testGetRedirectTarget_2', "#REDIRECT [[hello world]]", "Hello world" ), + ); + } + + /** + * @dataProvider dataGetRedirectTarget + */ + public function testGetRedirectTarget( $title, $text, $target ) { + $page = $this->createPage( $title, $text ); + + # now, test the actual redirect + $t = $page->getRedirectTarget(); + $this->assertEquals( $target, is_null( $t ) ? null : $t->getPrefixedText() ); + } + + /** + * @dataProvider dataGetRedirectTarget + */ + public function testIsRedirect( $title, $text, $target ) { + $page = $this->createPage( $title, $text ); + $this->assertEquals( !is_null( $target ), $page->isRedirect() ); + } + + public function dataIsCountable() { + return array( + + // any + array( 'WikiPageTest_testIsCountable', + '', + 'any', + true + ), + array( 'WikiPageTest_testIsCountable', + 'Foo', + 'any', + true + ), + + // comma + array( 'WikiPageTest_testIsCountable', + 'Foo', + 'comma', + false + ), + array( 'WikiPageTest_testIsCountable', + 'Foo, bar', + 'comma', + true + ), + + // link + array( 'WikiPageTest_testIsCountable', + 'Foo', + 'link', + false + ), + array( 'WikiPageTest_testIsCountable', + 'Foo [[bar]]', + 'link', + true + ), + + // redirects + array( 'WikiPageTest_testIsCountable', + '#REDIRECT [[bar]]', + 'any', + false + ), + array( 'WikiPageTest_testIsCountable', + '#REDIRECT [[bar]]', + 'comma', + false + ), + array( 'WikiPageTest_testIsCountable', + '#REDIRECT [[bar]]', + 'link', + false + ), + + // not a content namespace + array( 'Talk:WikiPageTest_testIsCountable', + 'Foo', + 'any', + false + ), + array( 'Talk:WikiPageTest_testIsCountable', + 'Foo, bar', + 'comma', + false + ), + array( 'Talk:WikiPageTest_testIsCountable', + 'Foo [[bar]]', + 'link', + false + ), + + // not a content namespace, different model + array( 'MediaWiki:WikiPageTest_testIsCountable.js', + 'Foo', + 'any', + false + ), + array( 'MediaWiki:WikiPageTest_testIsCountable.js', + 'Foo, bar', + 'comma', + false + ), + array( 'MediaWiki:WikiPageTest_testIsCountable.js', + 'Foo [[bar]]', + 'link', + false + ), + ); + } + + + /** + * @dataProvider dataIsCountable + */ + public function testIsCountable( $title, $text, $mode, $expected ) { + global $wgArticleCountMethod; + + $old = $wgArticleCountMethod; + $wgArticleCountMethod = $mode; + + $page = $this->createPage( $title, $text ); + $editInfo = $page->prepareTextForEdit( $page->getText() ); + + $v = $page->isCountable(); + $w = $page->isCountable( $editInfo ); + $wgArticleCountMethod = $old; + + $this->assertEquals( $expected, $v, "isCountable( null ) returned unexpected value " . var_export( $v, true ) + . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" ); + + $this->assertEquals( $expected, $w, "isCountable( \$editInfo ) returned unexpected value " . var_export( $v, true ) + . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" ); + } + + public function dataGetParserOutput() { + return array( + array("hello ''world''\n", "

hello world

"), + // @todo: more...? + ); + } + + /** + * @dataProvider dataGetParserOutput + */ + public function testGetParserOutput( $text, $expectedHtml ) { + $page = $this->createPage( 'WikiPageTest_testGetParserOutput', $text ); + + $opt = new ParserOptions(); + $po = $page->getParserOutput( $opt ); + $text = $po->getText(); + + $text = trim( preg_replace( '//sm', '', $text ) ); # strip injected comments + $text = preg_replace( '!\s*(

)!sm', '\1', $text ); # don't let tidy confuse us + + $this->assertEquals( $expectedHtml, $text ); + return $po; + } + + static $sections = + + "Intro + +== stuff == +hello world + +== test == +just a test + +== foo == +more stuff +"; + + + public function dataReplaceSection() { + return array( + array( 'WikiPageTest_testReplaceSection', + WikiPageTest::$sections, + "0", + "No more", + null, + trim( preg_replace( '/^Intro/sm', 'No more', WikiPageTest::$sections ) ) + ), + array( 'WikiPageTest_testReplaceSection', + WikiPageTest::$sections, + "", + "No more", + null, + "No more" + ), + array( 'WikiPageTest_testReplaceSection', + WikiPageTest::$sections, + "2", + "== TEST ==\nmore fun", + null, + trim( preg_replace( '/^== test ==.*== foo ==/sm', "== TEST ==\nmore fun\n\n== foo ==", WikiPageTest::$sections ) ) + ), + array( 'WikiPageTest_testReplaceSection', + WikiPageTest::$sections, + "8", + "No more", + null, + trim( WikiPageTest::$sections ) + ), + array( 'WikiPageTest_testReplaceSection', + WikiPageTest::$sections, + "new", + "No more", + "New", + trim( WikiPageTest::$sections ) . "\n\n== New ==\n\nNo more" + ), + ); + } + + /** + * @dataProvider dataReplaceSection + */ + public function testReplaceSection( $title, $text, $section, $with, $sectionTitle, $expected ) { + $page = $this->createPage( $title, $text ); + $text = $page->replaceSection( $section, $with, $sectionTitle ); + $text = trim( $text ); + + $this->assertEquals( $expected, $text ); + } + + /* @todo FIXME: fix this! + public function testGetUndoText() { + global $wgDiff3; + + wfSuppressWarnings(); + $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 ); + wfRestoreWarnings(); + + if( !$haveDiff3 ) { + $this->markTestSkipped( "diff3 not installed or not found" ); + return; + } + + $text = "one"; + $page = $this->createPage( "WikiPageTest_testGetUndoText", $text ); + $rev1 = $page->getRevision(); + + $text .= "\n\ntwo"; + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section two"); + $rev2 = $page->getRevision(); + + $text .= "\n\nthree"; + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section three"); + $rev3 = $page->getRevision(); + + $text .= "\n\nfour"; + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section four"); + $rev4 = $page->getRevision(); + + $text .= "\n\nfive"; + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section five"); + $rev5 = $page->getRevision(); + + $text .= "\n\nsix"; + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section six"); + $rev6 = $page->getRevision(); + + $undo6 = $page->getUndoText( $rev6 ); + if ( $undo6 === false ) $this->fail( "getUndoText failed for rev6" ); + $this->assertEquals( "one\n\ntwo\n\nthree\n\nfour\n\nfive", $undo6 ); + + $undo3 = $page->getUndoText( $rev4, $rev2 ); + if ( $undo3 === false ) $this->fail( "getUndoText failed for rev4..rev2" ); + $this->assertEquals( "one\n\ntwo\n\nfive", $undo3 ); + + $undo2 = $page->getUndoText( $rev2 ); + if ( $undo2 === false ) $this->fail( "getUndoText failed for rev2" ); + $this->assertEquals( "one\n\nfive", $undo2 ); + } + */ + + /** + * @todo FIXME: this is a better rollback test than the one below, but it keeps failing in jenkins for some reason. + */ + public function broken_testDoRollback() { + $admin = new User(); + $admin->setName("Admin"); + + $text = "one"; + $page = $this->newPage( "WikiPageTest_testDoRollback" ); + $page->doEdit( $text, "section one", EDIT_NEW, false, $admin ); + + $user1 = new User(); + $user1->setName( "127.0.1.11" ); + $text .= "\n\ntwo"; + $page = new WikiPage( $page->getTitle() ); + $page->doEdit( $text, "adding section two", 0, false, $user1 ); + + $user2 = new User(); + $user2->setName( "127.0.2.13" ); + $text .= "\n\nthree"; + $page = new WikiPage( $page->getTitle() ); + $page->doEdit( $text, "adding section three", 0, false, $user2 ); + + # we are having issues with doRollback spuriously failing. apparently the last revision somehow goes missing + # or not committed under some circumstances. so, make sure the last revision has the right user name. + $dbr = wfGetDB( DB_SLAVE ); + $this->assertEquals( 3, Revision::countByPageId( $dbr, $page->getId() ) ); + + $page = new WikiPage( $page->getTitle() ); + $rev3 = $page->getRevision(); + $this->assertEquals( '127.0.2.13', $rev3->getUserText() ); + + $rev2 = $rev3->getPrevious(); + $this->assertEquals( '127.0.1.11', $rev2->getUserText() ); + + $rev1 = $rev2->getPrevious(); + $this->assertEquals( 'Admin', $rev1->getUserText() ); + + # now, try the actual rollback + $admin->addGroup( "sysop" ); #XXX: make the test user a sysop... + $token = $admin->getEditToken( array( $page->getTitle()->getPrefixedText(), $user2->getName() ), null ); + $errors = $page->doRollback( $user2->getName(), "testing revert", $token, false, $details, $admin ); + + if ( $errors ) { + $this->fail( "Rollback failed:\n" . print_r( $errors, true ) . ";\n" . print_r( $details, true ) ); + } + + $page = new WikiPage( $page->getTitle() ); + $this->assertEquals( $rev2->getSha1(), $page->getRevision()->getSha1(), "rollback did not revert to the correct revision" ); + $this->assertEquals( "one\n\ntwo", $page->getText() ); + } + + /** + * @todo FIXME: the above rollback test is better, but it keeps failing in jenkins for some reason. + */ + public function testDoRollback() { + $admin = new User(); + $admin->setName("Admin"); + + $text = "one"; + $page = $this->newPage( "WikiPageTest_testDoRollback" ); + $page->doEdit( $text, "section one", EDIT_NEW, false, $admin ); + $rev1 = $page->getRevision(); + + $user1 = new User(); + $user1->setName( "127.0.1.11" ); + $text .= "\n\ntwo"; + $page = new WikiPage( $page->getTitle() ); + $page->doEdit( $text, "adding section two", 0, false, $user1 ); + + # now, try the rollback + $admin->addGroup( "sysop" ); #XXX: make the test user a sysop... + $token = $admin->getEditToken( array( $page->getTitle()->getPrefixedText(), $user1->getName() ), null ); + $errors = $page->doRollback( $user1->getName(), "testing revert", $token, false, $details, $admin ); + + if ( $errors ) { + $this->fail( "Rollback failed:\n" . print_r( $errors, true ) . ";\n" . print_r( $details, true ) ); + } + + $page = new WikiPage( $page->getTitle() ); + $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(), "rollback did not revert to the correct revision" ); + $this->assertEquals( "one", $page->getText() ); + } + + public function dataGetAutosummary( ) { + return array( + array( + 'Hello there, world!', + '#REDIRECT [[Foo]]', + 0, + '/^Redirected page .*Foo/' + ), + + array( + null, + 'Hello world!', + EDIT_NEW, + '/^Created page .*Hello/' + ), + + array( + 'Hello there, world!', + '', + 0, + '/^Blanked/' + ), + + array( + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut + labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et + ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.', + 'Hello world!', + 0, + '/^Replaced .*Hello/' + ), + + array( + 'foo', + 'bar', + 0, + '/^$/' + ), + ); + } + + /** + * @dataProvider dataGetAutoSummary + */ + public function testGetAutosummary( $old, $new, $flags, $expected ) { + $page = $this->newPage( "WikiPageTest_testGetAutosummary" ); + + $summary = $page->getAutosummary( $old, $new, $flags ); + + $this->assertTrue( (bool)preg_match( $expected, $summary ), "Autosummary didn't match expected pattern $expected: $summary" ); + } + + public function dataGetAutoDeleteReason( ) { + return array( + array( + array(), + false, + false + ), + + array( + array( + array( "first edit", null ), + ), + "/first edit.*only contributor/", + false + ), + + array( + array( + array( "first edit", null ), + array( "second edit", null ), + ), + "/second edit.*only contributor/", + true + ), + + array( + array( + array( "first edit", "127.0.2.22" ), + array( "second edit", "127.0.3.33" ), + ), + "/second edit/", + true + ), + + array( + array( + array( "first edit: " + . "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam " + . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. " + . "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea " + . "takimata sanctus est Lorem ipsum dolor sit amet.'", null ), + ), + '/first edit:.*\.\.\."/', + false + ), + + array( + array( + array( "first edit", "127.0.2.22" ), + array( "", "127.0.3.33" ), + ), + "/before blanking.*first edit/", + true + ), + + ); + } + + /** + * @dataProvider dataGetAutoDeleteReason + */ + public function testGetAutoDeleteReason( $edits, $expectedResult, $expectedHistory ) { + global $wgUser; + + $page = $this->newPage( "WikiPageTest_testGetAutoDeleteReason" ); + + $c = 1; + + foreach ( $edits as $edit ) { + $user = new User(); + + if ( !empty( $edit[1] ) ) $user->setName( $edit[1] ); + else $user = $wgUser; + + $page->doEdit( $edit[0], "test edit $c", $c < 2 ? EDIT_NEW : 0, false, $user ); + + $c += 1; + } + + $reason = $page->getAutoDeleteReason( $hasHistory ); + + if ( is_bool( $expectedResult ) || is_null( $expectedResult ) ) $this->assertEquals( $expectedResult, $reason ); + else $this->assertTrue( (bool)preg_match( $expectedResult, $reason ), "Autosummary didn't match expected pattern $expectedResult: $reason" ); + + $this->assertEquals( $expectedHistory, $hasHistory, "expected \$hasHistory to be " . var_export( $expectedHistory, true ) ); + + $page->doDeleteArticle( "done" ); + } + + public function dataPreSaveTransform() { + return array( + array( 'hello this is ~~~', + "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]", + ), + array( 'hello \'\'this\'\' is ~~~', + 'hello \'\'this\'\' is ~~~', + ), + ); + } + + /** + * @dataProvider dataPreSaveTransform + */ + public function testPreSaveTransform( $text, $expected ) { + $this->hideDeprecated( 'WikiPage::preSaveTransform' ); + $user = new User(); + $user->setName("127.0.0.1"); + + $page = $this->newPage( "WikiPageTest_testPreloadTransform" ); + $text = $page->preSaveTransform( $text, $user ); + + $this->assertEquals( $expected, $text ); + } + +} + diff --git a/tests/phpunit/includes/XmlTest.php b/tests/phpunit/includes/XmlTest.php index 1d9361f2..93ed3dc7 100644 --- a/tests/phpunit/includes/XmlTest.php +++ b/tests/phpunit/includes/XmlTest.php @@ -193,52 +193,6 @@ class XmlTest extends MediaWikiTestCase { ); } - function testNamespaceSelector() { - $this->assertEquals( - '', - Xml::namespaceSelector(), - 'Basic namespace selector without custom options' - ); - $this->assertEquals( - '' . -' ', - Xml::namespaceSelector( $selected = '2', $all = 'all', $element_name = 'myname', $label = 'Select a namespace:' ), - 'Basic namespace selector with custom values' - ); - } - - # # textarea # @@ -297,6 +251,15 @@ class XmlTest extends MediaWikiTestCase { ); } + function testLanguageSelector() { + $select = Xml::languageSelector( 'en', true, null, + array( 'id' => 'testlang' ), wfMessage( 'yourlanguage' ) ); + $this->assertEquals( + '', + $select[0] + ); + } + # # JS # diff --git a/tests/phpunit/includes/ZipDirectoryReaderTest.php b/tests/phpunit/includes/ZipDirectoryReaderTest.php index f7ca59e2..d90a6950 100644 --- a/tests/phpunit/includes/ZipDirectoryReaderTest.php +++ b/tests/phpunit/includes/ZipDirectoryReaderTest.php @@ -4,7 +4,7 @@ class ZipDirectoryReaderTest extends MediaWikiTestCase { var $zipDir, $entries; function setUp() { - $this->zipDir = dirname( __FILE__ ) . '/../data/zip'; + $this->zipDir = __DIR__ . '/../data/zip'; } function zipCallback( $entry ) { diff --git a/tests/phpunit/includes/api/ApiBlockTest.php b/tests/phpunit/includes/api/ApiBlockTest.php index b95d8214..5dfceee8 100644 --- a/tests/phpunit/includes/api/ApiBlockTest.php +++ b/tests/phpunit/includes/api/ApiBlockTest.php @@ -1,6 +1,7 @@ 'block', 'user' => 'UTApiBlockee', 'reason' => 'Some reason', - 'token' => $pageinfo['blocktoken'] ), $data, false, self::$users['sysop']->user ); + 'token' => $pageinfo['blocktoken'] ), null, false, self::$users['sysop']->user ); $block = Block::newFromTarget('UTApiBlockee'); @@ -69,4 +68,50 @@ class ApiBlockTest extends ApiTestCase { } + /** + * @dataProvider provideBlockUnblockAction + */ + function testGetTokenUsingABlockingAction( $action ) { + $data = $this->doApiRequest( + array( + 'action' => $action, + 'user' => 'UTApiBlockee', + 'gettoken' => '' ), + null, + false, + self::$users['sysop']->user + ); + $this->assertEquals( 34, strlen( $data[0][$action]["{$action}token"] ) ); + } + + /** + * Attempting to block without a token should give a UsageException with + * error message: + * "The token parameter must be set" + * + * @dataProvider provideBlockUnblockAction + * @expectedException UsageException + */ + function testBlockingActionWithNoToken( $action ) { + $this->doApiRequest( + array( + 'action' => $action, + 'user' => 'UTApiBlockee', + 'reason' => 'Some reason', + ), + null, + false, + self::$users['sysop']->user + ); + } + + /** + * Just provide the 'block' and 'unblock' action to test both API calls + */ + function provideBlockUnblockAction() { + return array( + array( 'block' ), + array( 'unblock' ), + ); + } } diff --git a/tests/phpunit/includes/api/ApiEditPageTest.php b/tests/phpunit/includes/api/ApiEditPageTest.php new file mode 100644 index 00000000..5297d6da --- /dev/null +++ b/tests/phpunit/includes/api/ApiEditPageTest.php @@ -0,0 +1,84 @@ +doLogin(); + } + + function testEdit( ) { + $name = 'ApiEditPageTest_testEdit'; + + // -- test new page -------------------------------------------- + $apiResult = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'text' => 'some text', ) ); + $apiResult = $apiResult[0]; + + # Validate API result data + $this->assertArrayHasKey( 'edit', $apiResult ); + $this->assertArrayHasKey( 'result', $apiResult['edit'] ); + $this->assertEquals( 'Success', $apiResult['edit']['result'] ); + + $this->assertArrayHasKey( 'new', $apiResult['edit'] ); + $this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] ); + + $this->assertArrayHasKey( 'pageid', $apiResult['edit'] ); + + // -- test existing page, no change ---------------------------- + $data = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'text' => 'some text', ) ); + + $this->assertEquals( 'Success', $data[0]['edit']['result'] ); + + $this->assertArrayNotHasKey( 'new', $data[0]['edit'] ); + $this->assertArrayHasKey( 'nochange', $data[0]['edit'] ); + + // -- test existing page, with change -------------------------- + $data = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'text' => 'different text' ) ); + + $this->assertEquals( 'Success', $data[0]['edit']['result'] ); + + $this->assertArrayNotHasKey( 'new', $data[0]['edit'] ); + $this->assertArrayNotHasKey( 'nochange', $data[0]['edit'] ); + + $this->assertArrayHasKey( 'oldrevid', $data[0]['edit'] ); + $this->assertArrayHasKey( 'newrevid', $data[0]['edit'] ); + $this->assertNotEquals( + $data[0]['edit']['newrevid'], + $data[0]['edit']['oldrevid'], + "revision id should change after edit" + ); + } + + function testEditAppend() { + $this->markTestIncomplete( "not yet implemented" ); + } + + function testEditSection() { + $this->markTestIncomplete( "not yet implemented" ); + } + + function testUndo() { + $this->markTestIncomplete( "not yet implemented" ); + } + + function testEditNonText() { + $this->markTestIncomplete( "not yet implemented" ); + } +} diff --git a/tests/phpunit/includes/api/ApiOptionsTest.php b/tests/phpunit/includes/api/ApiOptionsTest.php new file mode 100644 index 00000000..5243fca1 --- /dev/null +++ b/tests/phpunit/includes/api/ApiOptionsTest.php @@ -0,0 +1,276 @@ + 'success' ); + + function setUp() { + parent::setUp(); + + $this->mUserMock = $this->getMockBuilder( 'User' ) + ->disableOriginalConstructor() + ->getMock(); + + $this->mApiMainMock = $this->getMockBuilder( 'ApiBase' ) + ->disableOriginalConstructor() + ->getMock(); + + // Set up groups + $this->mUserMock->expects( $this->any() ) + ->method( 'getEffectiveGroups' )->will( $this->returnValue( array( '*', 'user')) ); + + // Create a new context + $this->mContext = new DerivativeContext( new RequestContext() ); + $this->mContext->getContext()->setTitle( Title::newFromText( 'Test' ) ); + $this->mContext->setUser( $this->mUserMock ); + + $this->mApiMainMock->expects( $this->any() ) + ->method( 'getContext' ) + ->will( $this->returnValue( $this->mContext ) ); + + $this->mApiMainMock->expects( $this->any() ) + ->method( 'getResult' ) + ->will( $this->returnValue( new ApiResult( $this->mApiMainMock ) ) ); + + + // Empty session + $this->mSession = array(); + + $this->mTested = new ApiOptions( $this->mApiMainMock, 'options' ); + + global $wgHooks; + if ( !isset( $wgHooks['GetPreferences'] ) ) { + $wgHooks['GetPreferences'] = array(); + } + $this->mOldGetPreferencesHooks = $wgHooks['GetPreferences']; + $wgHooks['GetPreferences'][] = array( $this, 'hookGetPreferences' ); + } + + public function tearDown() { + global $wgHooks; + + if ( $this->mOldGetPreferencesHooks !== false ) { + $wgHooks['GetPreferences'] = $this->mOldGetPreferencesHooks; + $this->mOldGetPreferencesHooks = false; + } + + parent::tearDown(); + } + + public function hookGetPreferences( $user, &$preferences ) { + foreach ( array( 'name', 'willBeNull', 'willBeEmpty', 'willBeHappy' ) as $k ) { + $preferences[$k] = array( + 'type' => 'text', + 'section' => 'test', + 'label' => ' ', + ); + } + + return true; + } + + private function getSampleRequest( $custom = array() ) { + $request = array( + 'token' => '123ABC', + 'change' => null, + 'optionname' => null, + 'optionvalue' => null, + ); + return array_merge( $request, $custom ); + } + + private function executeQuery( $request ) { + $this->mContext->setRequest( new FauxRequest( $request, true, $this->mSession ) ); + $this->mTested->execute(); + return $this->mTested->getResult()->getData(); + } + + /** + * @expectedException UsageException + */ + public function testNoToken() { + $request = $this->getSampleRequest( array( 'token' => null ) ); + + $this->executeQuery( $request ); + } + + public function testAnon() { + $this->mUserMock->expects( $this->once() ) + ->method( 'isAnon' ) + ->will( $this->returnValue( true ) ); + + try { + $request = $this->getSampleRequest(); + + $this->executeQuery( $request ); + } catch ( UsageException $e ) { + $this->assertEquals( 'notloggedin', $e->getCodeString() ); + $this->assertEquals( 'Anonymous users cannot change preferences', $e->getMessage() ); + return; + } + $this->fail( "UsageException was not thrown" ); + } + + public function testNoOptionname() { + try { + $request = $this->getSampleRequest( array( 'optionvalue' => '1' ) ); + + $this->executeQuery( $request ); + } catch ( UsageException $e ) { + $this->assertEquals( 'nooptionname', $e->getCodeString() ); + $this->assertEquals( 'The optionname parameter must be set', $e->getMessage() ); + return; + } + $this->fail( "UsageException was not thrown" ); + } + + public function testNoChanges() { + $this->mUserMock->expects( $this->never() ) + ->method( 'resetOptions' ); + + $this->mUserMock->expects( $this->never() ) + ->method( 'setOption' ); + + $this->mUserMock->expects( $this->never() ) + ->method( 'saveSettings' ); + + try { + $request = $this->getSampleRequest(); + + $this->executeQuery( $request ); + } catch ( UsageException $e ) { + $this->assertEquals( 'nochanges', $e->getCodeString() ); + $this->assertEquals( 'No changes were requested', $e->getMessage() ); + return; + } + $this->fail( "UsageException was not thrown" ); + } + + public function testReset() { + $this->mUserMock->expects( $this->once() ) + ->method( 'resetOptions' ); + + $this->mUserMock->expects( $this->never() ) + ->method( 'setOption' ); + + $this->mUserMock->expects( $this->once() ) + ->method( 'saveSettings' ); + + $request = $this->getSampleRequest( array( 'reset' => '' ) ); + + $response = $this->executeQuery( $request ); + + $this->assertEquals( self::$Success, $response ); + } + + public function testOptionWithValue() { + $this->mUserMock->expects( $this->never() ) + ->method( 'resetOptions' ); + + $this->mUserMock->expects( $this->once() ) + ->method( 'setOption' ) + ->with( $this->equalTo( 'name' ), $this->equalTo( 'value' ) ); + + $this->mUserMock->expects( $this->once() ) + ->method( 'saveSettings' ); + + $request = $this->getSampleRequest( array( 'optionname' => 'name', 'optionvalue' => 'value' ) ); + + $response = $this->executeQuery( $request ); + + $this->assertEquals( self::$Success, $response ); + } + + public function testOptionResetValue() { + $this->mUserMock->expects( $this->never() ) + ->method( 'resetOptions' ); + + $this->mUserMock->expects( $this->once() ) + ->method( 'setOption' ) + ->with( $this->equalTo( 'name' ), $this->equalTo( null ) ); + + $this->mUserMock->expects( $this->once() ) + ->method( 'saveSettings' ); + + $request = $this->getSampleRequest( array( 'optionname' => 'name' ) ); + $response = $this->executeQuery( $request ); + + $this->assertEquals( self::$Success, $response ); + } + + public function testChange() { + $this->mUserMock->expects( $this->never() ) + ->method( 'resetOptions' ); + + $this->mUserMock->expects( $this->at( 1 ) ) + ->method( 'getOptions' ); + + $this->mUserMock->expects( $this->at( 2 ) ) + ->method( 'setOption' ) + ->with( $this->equalTo( 'willBeNull' ), $this->equalTo( null ) ); + + $this->mUserMock->expects( $this->at( 3 ) ) + ->method( 'getOptions' ); + + $this->mUserMock->expects( $this->at( 4 ) ) + ->method( 'setOption' ) + ->with( $this->equalTo( 'willBeEmpty' ), $this->equalTo( '' ) ); + + $this->mUserMock->expects( $this->at( 5 ) ) + ->method( 'getOptions' ); + + $this->mUserMock->expects( $this->at( 6 ) ) + ->method( 'setOption' ) + ->with( $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) ); + + $this->mUserMock->expects( $this->once() ) + ->method( 'saveSettings' ); + + $request = $this->getSampleRequest( array( 'change' => 'willBeNull|willBeEmpty=|willBeHappy=Happy' ) ); + + $response = $this->executeQuery( $request ); + + $this->assertEquals( self::$Success, $response ); + } + + public function testResetChangeOption() { + $this->mUserMock->expects( $this->once() ) + ->method( 'resetOptions' ); + + $this->mUserMock->expects( $this->at( 2 ) ) + ->method( 'getOptions' ); + + $this->mUserMock->expects( $this->at( 3 ) ) + ->method( 'setOption' ) + ->with( $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) ); + + $this->mUserMock->expects( $this->at( 4 ) ) + ->method( 'getOptions' ); + + $this->mUserMock->expects( $this->at( 5 ) ) + ->method( 'setOption' ) + ->with( $this->equalTo( 'name' ), $this->equalTo( 'value' ) ); + + $this->mUserMock->expects( $this->once() ) + ->method( 'saveSettings' ); + + $args = array( + 'reset' => '', + 'change' => 'willBeHappy=Happy', + 'optionname' => 'name', + 'optionvalue' => 'value' + ); + + $response = $this->executeQuery( $this->getSampleRequest( $args ) ); + + $this->assertEquals( self::$Success, $response ); + } +} diff --git a/tests/phpunit/includes/api/ApiPurgeTest.php b/tests/phpunit/includes/api/ApiPurgeTest.php index 70c20746..2566c6cd 100644 --- a/tests/phpunit/includes/api/ApiPurgeTest.php +++ b/tests/phpunit/includes/api/ApiPurgeTest.php @@ -1,6 +1,7 @@ new ApiTestUser( + 'sysop' => new TestUser( 'Apitestsysop', 'Api Test Sysop', 'api_test_sysop@example.com', array( 'sysop' ) ), - 'uploader' => new ApiTestUser( + 'uploader' => new TestUser( 'Apitestuser', 'Api Test User', 'api_test_user@example.com', @@ -43,15 +39,31 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { } - protected function doApiRequest( $params, $session = null, $appendModule = false, $user = null ) { + protected function doApiRequest( Array $params, Array $session = null, $appendModule = false, User $user = null ) { + global $wgRequest, $wgUser; + if ( is_null( $session ) ) { - $session = array(); + # re-use existing global session by default + $session = $wgRequest->getSessionArray(); } - $context = $this->apiContext->newTestContext( $params, $session, $user ); + # set up global environment + if ( $user ) { + $wgUser = $user; + } + + $wgRequest = new FauxRequest( $params, true, $session ); + RequestContext::getMain()->setRequest( $wgRequest ); + + # set up local environment + $context = $this->apiContext->newTestContext( $wgRequest, $wgUser ); + $module = new ApiMain( $context, true ); + + # run it! $module->execute(); + # construct result $results = array( $module->getResultData(), $context->getRequest(), @@ -68,11 +80,17 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { * Add an edit token to the API request * This is cheating a bit -- we grab a token in the correct format and then add it to the pseudo-session and to the * request, without actually requesting a "real" edit token - * @param $params: key-value API params - * @param $session: session array - * @param $user String|null A User object for the context + * @param $params Array: key-value API params + * @param $session Array|null: session array + * @param $user User|null A User object for the context */ - protected function doApiRequestWithToken( $params, $session, $user = null ) { + protected function doApiRequestWithToken( Array $params, Array $session = null, User $user = null ) { + global $wgRequest; + + if ( $session === null ) { + $session = $wgRequest->getSessionArray(); + } + if ( $session['wsToken'] ) { // add edit token to fake session $session['wsEditToken'] = $session['wsToken']; @@ -97,17 +115,17 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { 'lgtoken' => $token, 'lgname' => self::$users['sysop']->username, 'lgpassword' => self::$users['sysop']->password - ), $data ); + ), $data[2] ); return $data; } - protected function getTokenList( $user ) { + protected function getTokenList( $user, $session = null ) { $data = $this->doApiRequest( array( 'action' => 'query', 'titles' => 'Main Page', - 'intoken' => 'edit|delete|protect|move|block|unblock', - 'prop' => 'info' ), false, $user->user ); + 'intoken' => 'edit|delete|protect|move|block|unblock|watch', + 'prop' => 'info' ), $session, false, $user->user ); return $data; } } @@ -154,14 +172,13 @@ class ApiTestContext extends RequestContext { /** * Returns a DerivativeContext with the request variables in place * - * @param $params Array key-value API params - * @param $session Array session data + * @param $request WebRequest request object including parameters and session * @param $user User or null * @return DerivativeContext */ - public function newTestContext( $params, $session, $user = null ) { + public function newTestContext( WebRequest $request, User $user = null ) { $context = new DerivativeContext( $this ); - $context->setRequest( new FauxRequest( $params, true, $session ) ); + $context->setRequest( $request ); if ( $user !== null ) { $context->setUser( $user ); } diff --git a/tests/phpunit/includes/api/ApiTestUser.php b/tests/phpunit/includes/api/ApiTestUser.php deleted file mode 100644 index 8d5f61a7..00000000 --- a/tests/phpunit/includes/api/ApiTestUser.php +++ /dev/null @@ -1,59 +0,0 @@ -username = $username; - $this->realname = $realname; - $this->email = $email; - $this->groups = $groups; - - // don't allow user to hardcode or select passwords -- people sometimes run tests - // on live wikis. Sometimes we create sysop users in these tests. A sysop user with - // a known password would be a Bad Thing. - $this->password = User::randomPassword(); - - $this->user = User::newFromName( $this->username ); - $this->user->load(); - - // In an ideal world we'd have a new wiki (or mock data store) for every single test. - // But for now, we just need to create or update the user with the desired properties. - // we particularly need the new password, since we just generated it randomly. - // In core MediaWiki, there is no functionality to delete users, so this is the best we can do. - if ( !$this->user->getID() ) { - // create the user - $this->user = User::createNew( - $this->username, array( - "email" => $this->email, - "real_name" => $this->realname - ) - ); - if ( !$this->user ) { - throw new Exception( "error creating user" ); - } - } - - // update the user to use the new random password and other details - $this->user->setPassword( $this->password ); - $this->user->setEmail( $this->email ); - $this->user->setRealName( $this->realname ); - // remove all groups, replace with any groups specified - foreach ( $this->user->getGroups() as $group ) { - $this->user->removeGroup( $group ); - } - if ( count( $this->groups ) ) { - foreach ( $this->groups as $group ) { - $this->user->addGroup( $group ); - } - } - $this->user->saveSettings(); - - } - -} diff --git a/tests/phpunit/includes/api/ApiUploadTest.php b/tests/phpunit/includes/api/ApiUploadTest.php index 7a700326..642fed05 100644 --- a/tests/phpunit/includes/api/ApiUploadTest.php +++ b/tests/phpunit/includes/api/ApiUploadTest.php @@ -1,6 +1,7 @@ doLogin(); } - + function getTokens() { - return $this->getTokenList( self::$users['sysop'] ); + $data = $this->getTokenList( self::$users['sysop'] ); + + $keys = array_keys( $data[0]['query']['pages'] ); + $key = array_pop( $keys ); + $pageinfo = $data[0]['query']['pages'][$key]; + + return $pageinfo; } /** - * @group Broken */ function testWatchEdit() { - - $data = $this->getTokens(); - - $keys = array_keys( $data[0]['query']['pages'] ); - $key = array_pop( $keys ); - $pageinfo = $data[0]['query']['pages'][$key]; + $pageinfo = $this->getTokens(); $data = $this->doApiRequest( array( 'action' => 'edit', 'title' => 'UTPage', 'text' => 'new text', 'token' => $pageinfo['edittoken'], - 'watchlist' => 'watch' ), $data ); + 'watchlist' => 'watch' ) ); $this->assertArrayHasKey( 'edit', $data[0] ); $this->assertArrayHasKey( 'result', $data[0]['edit'] ); $this->assertEquals( 'Success', $data[0]['edit']['result'] ); @@ -41,13 +42,14 @@ class ApiWatchTest extends ApiTestCase { /** * @depends testWatchEdit - * @group Broken */ function testWatchClear() { - + + $pageinfo = $this->getTokens(); + $data = $this->doApiRequest( array( 'action' => 'query', - 'list' => 'watchlist' ), $data ); + 'list' => 'watchlist' ) ); if ( isset( $data[0]['query']['watchlist'] ) ) { $wl = $data[0]['query']['watchlist']; @@ -56,7 +58,8 @@ class ApiWatchTest extends ApiTestCase { $data = $this->doApiRequest( array( 'action' => 'watch', 'title' => $page['title'], - 'unwatch' => true ), $data ); + 'unwatch' => true, + 'token' => $pageinfo['watchtoken'] ) ); } } $data = $this->doApiRequest( array( @@ -70,22 +73,17 @@ class ApiWatchTest extends ApiTestCase { } /** - * @group Broken - */ + */ function testWatchProtect() { - - $data = $this->getTokens(); - - $keys = array_keys( $data[0]['query']['pages'] ); - $key = array_pop( $keys ); - $pageinfo = $data[0]['query']['pages'][$key]; + + $pageinfo = $this->getTokens(); $data = $this->doApiRequest( array( 'action' => 'protect', 'token' => $pageinfo['protecttoken'], 'title' => 'UTPage', 'protections' => 'edit=sysop', - 'watchlist' => 'unwatch' ), $data ); + 'watchlist' => 'unwatch' ) ); $this->assertArrayHasKey( 'protect', $data[0] ); $this->assertArrayHasKey( 'protections', $data[0]['protect'] ); @@ -94,21 +92,20 @@ class ApiWatchTest extends ApiTestCase { } /** - * @group Broken */ function testGetRollbackToken() { - - $data = $this->getTokens(); - + + $pageinfo = $this->getTokens(); + if ( !Title::newFromText( 'UTPage' )->exists() ) { - $this->markTestIncomplete( "The article [[UTPage]] does not exist" ); + $this->markTestSkipped( "The article [[UTPage]] does not exist" ); //TODO: just create it? } $data = $this->doApiRequest( array( 'action' => 'query', 'prop' => 'revisions', 'titles' => 'UTPage', - 'rvtoken' => 'rollback' ), $data ); + 'rvtoken' => 'rollback' ) ); $this->assertArrayHasKey( 'query', $data[0] ); $this->assertArrayHasKey( 'pages', $data[0]['query'] ); @@ -116,7 +113,7 @@ class ApiWatchTest extends ApiTestCase { $key = array_pop( $keys ); if ( isset( $data[0]['query']['pages'][$key]['missing'] ) ) { - $this->markTestIncomplete( "Target page (UTPage) doesn't exist" ); + $this->markTestSkipped( "Target page (UTPage) doesn't exist" ); } $this->assertArrayHasKey( 'pageid', $data[0]['query']['pages'][$key] ); @@ -128,21 +125,27 @@ class ApiWatchTest extends ApiTestCase { } /** - * @depends testGetRollbackToken * @group Broken + * Broken because there is currently no revision info in the $pageinfo + * + * @depends testGetRollbackToken */ function testWatchRollback( $data ) { $keys = array_keys( $data[0]['query']['pages'] ); $key = array_pop( $keys ); - $pageinfo = $data[0]['query']['pages'][$key]['revisions'][0]; + $pageinfo = $data[0]['query']['pages'][$key]; + $revinfo = $pageinfo['revisions'][0]; try { $data = $this->doApiRequest( array( 'action' => 'rollback', 'title' => 'UTPage', - 'user' => $pageinfo['user'], + 'user' => $revinfo['user'], 'token' => $pageinfo['rollbacktoken'], - 'watchlist' => 'watch' ), $data ); + 'watchlist' => 'watch' ) ); + + $this->assertArrayHasKey( 'rollback', $data[0] ); + $this->assertArrayHasKey( 'title', $data[0]['rollback'] ); } catch( UsageException $ue ) { if( $ue->getCodeString() == 'onlyauthor' ) { $this->markTestIncomplete( "Only one author to 'UTPage', cannot test rollback" ); @@ -150,32 +153,23 @@ class ApiWatchTest extends ApiTestCase { $this->fail( "Received error '" . $ue->getCodeString() . "'" ); } } - - $this->assertArrayHasKey( 'rollback', $data[0] ); - $this->assertArrayHasKey( 'title', $data[0]['rollback'] ); } /** - * @group Broken */ function testWatchDelete() { - - $data = $this->getTokens(); - - $keys = array_keys( $data[0]['query']['pages'] ); - $key = array_pop( $keys ); - $pageinfo = $data[0]['query']['pages'][$key]; + $pageinfo = $this->getTokens(); $data = $this->doApiRequest( array( 'action' => 'delete', 'token' => $pageinfo['deletetoken'], - 'title' => 'UTPage' ), $data ); + 'title' => 'UTPage' ) ); $this->assertArrayHasKey( 'delete', $data[0] ); $this->assertArrayHasKey( 'title', $data[0]['delete'] ); $data = $this->doApiRequest( array( 'action' => 'query', - 'list' => 'watchlist' ), $data ); + 'list' => 'watchlist' ) ); $this->markTestIncomplete( 'This test needs to verify the deleted article was added to the users watchlist' ); } diff --git a/tests/phpunit/includes/api/PrefixUniquenessTest.php b/tests/phpunit/includes/api/PrefixUniquenessTest.php new file mode 100644 index 00000000..69b01ea7 --- /dev/null +++ b/tests/phpunit/includes/api/PrefixUniquenessTest.php @@ -0,0 +1,24 @@ +getModules(); + $prefixes = array(); + + foreach ( $modules as $name => $class ) { + $module = new $class( $main, $name ); + $prefix = $module->getModulePrefix(); + if ( isset( $prefixes[$prefix] ) ) { + $this->fail( "Module prefix '{$prefix}' is shared between {$class} and {$prefixes[$prefix]}" ); + } + $prefixes[$module->getModulePrefix()] = $class; + } + $this->assertTrue( true ); // dummy call to make this test non-incomplete + } +} diff --git a/tests/phpunit/includes/api/RandomImageGenerator.php b/tests/phpunit/includes/api/RandomImageGenerator.php index 86c0a828..8b6a3849 100644 --- a/tests/phpunit/includes/api/RandomImageGenerator.php +++ b/tests/phpunit/includes/api/RandomImageGenerator.php @@ -79,7 +79,7 @@ class RandomImageGenerator { foreach ( array( '/usr/share/dict/words', '/usr/dict/words', - dirname( __FILE__ ) . '/words.txt' ) + __DIR__ . '/words.txt' ) as $dictionaryFile ) { if ( is_file( $dictionaryFile ) and is_readable( $dictionaryFile ) ) { $this->dictionaryFile = $dictionaryFile; diff --git a/tests/phpunit/includes/api/generateRandomImages.php b/tests/phpunit/includes/api/generateRandomImages.php index f3a14e5b..ee345623 100644 --- a/tests/phpunit/includes/api/generateRandomImages.php +++ b/tests/phpunit/includes/api/generateRandomImages.php @@ -6,14 +6,18 @@ */ // Evaluate the include path relative to this file -$IP = dirname( dirname( dirname( dirname( dirname( __FILE__ ) ) ) ) ); +$IP = dirname( dirname( dirname( dirname( __DIR__ ) ) ) ); // Start up MediaWiki in command-line mode require_once( "$IP/maintenance/Maintenance.php" ); -require("RandomImageGenerator.php"); +require( __DIR__ . "/RandomImageGenerator.php" ); class GenerateRandomImages extends Maintenance { + public function getDbType() { + return Maintenance::DB_NONE; + } + public function execute() { $getOptSpec = array( diff --git a/tests/phpunit/includes/cache/GenderCacheTest.php b/tests/phpunit/includes/cache/GenderCacheTest.php new file mode 100644 index 00000000..a8b987e2 --- /dev/null +++ b/tests/phpunit/includes/cache/GenderCacheTest.php @@ -0,0 +1,101 @@ +getID() == 0 ) { + $user->addToDatabase(); + $user->setPassword( 'UTMalePassword' ); + } + //ensure the right gender + $user->setOption( 'gender', 'male' ); + $user->saveSettings(); + + $user = User::newFromName( 'UTFemale' ); + if( $user->getID() == 0 ) { + $user->addToDatabase(); + $user->setPassword( 'UTFemalePassword' ); + } + //ensure the right gender + $user->setOption( 'gender', 'female' ); + $user->saveSettings(); + + $user = User::newFromName( 'UTDefaultGender' ); + if( $user->getID() == 0 ) { + $user->addToDatabase(); + $user->setPassword( 'UTDefaultGenderPassword' ); + } + //ensure the default gender + $user->setOption( 'gender', null ); + $user->saveSettings(); + } + + /** + * test usernames + * + * @dataProvider dataUserName + */ + function testUserName( $username, $expectedGender ) { + $genderCache = GenderCache::singleton(); + $gender = $genderCache->getGenderOf( $username ); + $this->assertEquals( $gender, $expectedGender, "GenderCache normal" ); + } + + /** + * genderCache should work with user objects, too + * + * @dataProvider dataUserName + */ + function testUserObjects( $username, $expectedGender ) { + $genderCache = GenderCache::singleton(); + $user = User::newFromName( $username ); + $gender = $genderCache->getGenderOf( $user ); + $this->assertEquals( $gender, $expectedGender, "GenderCache normal" ); + } + + function dataUserName() { + return array( + array( 'UTMale', 'male' ), + array( 'UTFemale', 'female' ), + array( 'UTDefaultGender', 'unknown' ), + array( 'UTNotExist', 'unknown' ), + //some not valid user + array( '127.0.0.1', 'unknown' ), + array( 'user@test', 'unknown' ), + ); + } + + /** + * test strip of subpages to avoid unnecessary queries + * against the never existing username + * + * @dataProvider dataStripSubpages + */ + function testStripSubpages( $pageWithSubpage, $expectedGender ) { + $genderCache = GenderCache::singleton(); + $gender = $genderCache->getGenderOf( $pageWithSubpage ); + $this->assertEquals( $gender, $expectedGender, "GenderCache must strip of subpages" ); + } + + function dataStripSubpages() { + return array( + array( 'UTMale/subpage', 'male' ), + array( 'UTFemale/subpage', 'female' ), + array( 'UTDefaultGender/subpage', 'unknown' ), + array( 'UTNotExist/subpage', 'unknown' ), + array( '127.0.0.1/subpage', 'unknown' ), + ); + } +} diff --git a/tests/phpunit/includes/cache/ProcessCacheLRUTest.php b/tests/phpunit/includes/cache/ProcessCacheLRUTest.php new file mode 100644 index 00000000..30bfb124 --- /dev/null +++ b/tests/phpunit/includes/cache/ProcessCacheLRUTest.php @@ -0,0 +1,239 @@ +assertAttributeEquals( array(), 'cache', $cache, $msg ); + } + + /** + * Helper to fill a cache object passed by reference + */ + function fillCache( &$cache, $numEntries ) { + // Fill cache with three values + for( $i=1; $i<=$numEntries; $i++) { + $cache->set( "cache-key-$i", "prop-$i", "value-$i" ); + } + } + + /** + * Generates an array of what would be expected in cache for a given cache + * size and a number of entries filled in sequentially + */ + function getExpectedCache( $cacheMaxEntries, $entryToFill ) { + $expected = array(); + + if( $entryToFill === 0 ) { + # The cache is empty! + return array(); + } elseif( $entryToFill <= $cacheMaxEntries ) { + # Cache is not fully filled + $firstKey = 1; + } else { + # Cache overflowed + $firstKey = 1 + $entryToFill - $cacheMaxEntries; + } + + $lastKey = $entryToFill; + + for( $i=$firstKey; $i<=$lastKey; $i++ ) { + $expected["cache-key-$i"] = array( "prop-$i" => "value-$i" ); + } + return $expected; + } + + /** + * Highlight diff between assertEquals and assertNotSame + */ + function testPhpUnitArrayEquality() { + $one = array( 'A' => 1, 'B' => 2 ); + $two = array( 'B' => 2, 'A' => 1 ); + $this->assertEquals( $one, $two ); // == + $this->assertNotSame( $one, $two ); // === + } + + /** + * @dataProvider provideInvalidConstructorArg + * @expectedException MWException + */ + function testConstructorGivenInvalidValue( $maxSize ) { + $c = new ProcessCacheLRUTestable( $maxSize ); + } + + /** + * Value which are forbidden by the constructor + */ + function provideInvalidConstructorArg() { + return array( + array( null ), + array( array() ), + array( new stdClass() ), + array( 0 ), + array( '5' ), + array( -1 ), + ); + } + + function testAddAndGetAKey() { + $oneCache = new ProcessCacheLRUTestable( 1 ); + $this->assertCacheEmpty( $oneCache ); + + // First set just one value + $oneCache->set( 'cache-key', 'prop1', 'value1' ); + $this->assertEquals( 1, $oneCache->getEntriesCount() ); + $this->assertTrue( $oneCache->has( 'cache-key', 'prop1' ) ); + $this->assertEquals( 'value1', $oneCache->get( 'cache-key', 'prop1' ) ); + } + + function testDeleteOldKey() { + $oneCache = new ProcessCacheLRUTestable( 1 ); + $this->assertCacheEmpty( $oneCache ); + + $oneCache->set( 'cache-key', 'prop1', 'value1' ); + $oneCache->set( 'cache-key', 'prop1', 'value2' ); + $this->assertEquals( 'value2', $oneCache->get( 'cache-key', 'prop1' ) ); + } + + /** + * This test that we properly overflow when filling a cache with + * a sequence of always different cache-keys. Meant to verify we correclty + * delete the older key. + * + * @dataProvider provideCacheFilling + * @param $cacheMaxEntries Maximum entry the created cache will hold + * @param $entryToFill Number of entries to insert in the created cache. + */ + function testFillingCache( $cacheMaxEntries, $entryToFill, $msg = '' ) { + $cache = new ProcessCacheLRUTestable( $cacheMaxEntries ); + $this->fillCache( $cache, $entryToFill); + + $this->assertSame( + $this->getExpectedCache( $cacheMaxEntries, $entryToFill ), + $cache->getCache(), + "Filling a $cacheMaxEntries entries cache with $entryToFill entries" + ); + + } + + /** + * Provider for testFillingCache + */ + function provideCacheFilling() { + // ($cacheMaxEntries, $entryToFill, $msg='') + return array( + array( 1, 0 ), + array( 1, 1 ), + array( 1, 2 ), # overflow + array( 5, 33 ), # overflow + ); + + } + + /** + * Create a cache with only one remaining entry then update + * the first inserted entry. Should bump it to the top. + */ + function testReplaceExistingKeyShouldBumpEntryToTop() { + $maxEntries = 3; + + $cache = new ProcessCacheLRUTestable( $maxEntries ); + // Fill cache leaving just one remaining slot + $this->fillCache( $cache, $maxEntries - 1 ); + + // Set an existing cache key + $cache->set( "cache-key-1", "prop-1", "new-value-for-1" ); + + $this->assertSame( + array( + 'cache-key-2' => array( 'prop-2' => 'value-2' ), + 'cache-key-1' => array( 'prop-1' => 'new-value-for-1' ), + ), + $cache->getCache() + ); + } + + function testRecentlyAccessedKeyStickIn() { + $cache = new ProcessCacheLRUTestable( 2 ); + $cache->set( 'first' , 'prop1', 'value1' ); + $cache->set( 'second', 'prop2', 'value2' ); + + // Get first + $cache->get( 'first', 'prop1' ); + // Cache a third value, should invalidate the least used one + $cache->set( 'third', 'prop3', 'value3' ); + + $this->assertFalse( $cache->has( 'second', 'prop2' ) ); + } + + /** + * This first create a full cache then update the value for the 2nd + * filled entry. + * Given a cache having 1,2,3 as key, updating 2 should bump 2 to + * the top of the queue with the new value: 1,3,2* (* = updated). + */ + function testReplaceExistingKeyInAFullCacheShouldBumpToTop() { + $maxEntries = 3; + + $cache = new ProcessCacheLRUTestable( $maxEntries ); + $this->fillCache( $cache, $maxEntries ); + + // Set an existing cache key + $cache->set( "cache-key-2", "prop-2", "new-value-for-2" ); + $this->assertSame( + array( + 'cache-key-1' => array( 'prop-1' => 'value-1' ), + 'cache-key-3' => array( 'prop-3' => 'value-3' ), + 'cache-key-2' => array( 'prop-2' => 'new-value-for-2' ), + ), + $cache->getCache() + ); + $this->assertEquals( 'new-value-for-2', + $cache->get( 'cache-key-2', 'prop-2' ) + ); + } + + function testBumpExistingKeyToTop() { + $cache = new ProcessCacheLRUTestable( 3 ); + $this->fillCache( $cache, 3 ); + + // Set the very first cache key to a new value + $cache->set( "cache-key-1", "prop-1", "new value for 1" ); + $this->assertEquals( + array( + 'cache-key-2' => array( 'prop-2' => 'value-2' ), + 'cache-key-3' => array( 'prop-3' => 'value-3' ), + 'cache-key-1' => array( 'prop-1' => 'new value for 1' ), + ), + $cache->getCache() + ); + + } + +} + +/** + * Overrides some ProcessCacheLRU methods and properties accessibility. + */ +class ProcessCacheLRUTestable extends ProcessCacheLRU { + public $cache = array(); + + public function getCache() { + return $this->cache; + } + public function getEntriesCount() { + return count( $this->cache ); + } +} diff --git a/tests/phpunit/includes/db/DatabaseSQLTest.php b/tests/phpunit/includes/db/DatabaseSQLTest.php new file mode 100644 index 00000000..e37cd445 --- /dev/null +++ b/tests/phpunit/includes/db/DatabaseSQLTest.php @@ -0,0 +1,147 @@ +db->getType() !== 'mysql' ) { + $this->markTestSkipped( 'No mysql database' ); + } + } + + /** + * @dataProvider dataSelectSQLText + */ + function testSelectSQLText( $sql, $sqlText ) { + $this->assertEquals( trim( $this->db->selectSQLText( + isset( $sql['tables'] ) ? $sql['tables'] : array(), + isset( $sql['fields'] ) ? $sql['fields'] : array(), + isset( $sql['conds'] ) ? $sql['conds'] : array(), + __METHOD__, + isset( $sql['options'] ) ? $sql['options'] : array(), + isset( $sql['join_conds'] ) ? $sql['join_conds'] : array() + ) ), $sqlText ); + } + + function dataSelectSQLText() { + return array( + array( + array( + 'tables' => 'table', + 'fields' => array( 'field', 'alias' => 'field2' ), + 'conds' => array( 'alias' => 'text' ), + ), + "SELECT field,field2 AS alias " . + "FROM `unittest_table` " . + "WHERE alias = 'text'" + ), + array( + array( + 'tables' => 'table', + 'fields' => array( 'field', 'alias' => 'field2' ), + 'conds' => array( 'alias' => 'text' ), + 'options' => array( 'LIMIT' => 1, 'ORDER BY' => 'field' ), + ), + "SELECT field,field2 AS alias " . + "FROM `unittest_table` " . + "WHERE alias = 'text' " . + "ORDER BY field " . + "LIMIT 1" + ), + array( + array( + 'tables' => array( 'table', 't2' => 'table2' ), + 'fields' => array( 'tid', 'field', 'alias' => 'field2', 't2.id' ), + 'conds' => array( 'alias' => 'text' ), + 'options' => array( 'LIMIT' => 1, 'ORDER BY' => 'field' ), + 'join_conds' => array( 't2' => array( + 'LEFT JOIN', 'tid = t2.id' + )), + ), + "SELECT tid,field,field2 AS alias,t2.id " . + "FROM `unittest_table` LEFT JOIN `unittest_table2` `t2` ON ((tid = t2.id)) " . + "WHERE alias = 'text' " . + "ORDER BY field " . + "LIMIT 1" + ), + array( + array( + 'tables' => array( 'table', 't2' => 'table2' ), + 'fields' => array( 'tid', 'field', 'alias' => 'field2', 't2.id' ), + 'conds' => array( 'alias' => 'text' ), + 'options' => array( 'LIMIT' => 1, 'GROUP BY' => 'field', 'HAVING' => 'COUNT(*) > 1' ), + 'join_conds' => array( 't2' => array( + 'LEFT JOIN', 'tid = t2.id' + )), + ), + "SELECT tid,field,field2 AS alias,t2.id " . + "FROM `unittest_table` LEFT JOIN `unittest_table2` `t2` ON ((tid = t2.id)) " . + "WHERE alias = 'text' " . + "GROUP BY field HAVING COUNT(*) > 1 " . + "LIMIT 1" + ), + array( + array( + 'tables' => array( 'table', 't2' => 'table2' ), + 'fields' => array( 'tid', 'field', 'alias' => 'field2', 't2.id' ), + 'conds' => array( 'alias' => 'text' ), + 'options' => array( 'LIMIT' => 1, 'GROUP BY' => array( 'field', 'field2' ), 'HAVING' => array( 'COUNT(*) > 1', 'field' => 1 ) ), + 'join_conds' => array( 't2' => array( + 'LEFT JOIN', 'tid = t2.id' + )), + ), + "SELECT tid,field,field2 AS alias,t2.id " . + "FROM `unittest_table` LEFT JOIN `unittest_table2` `t2` ON ((tid = t2.id)) " . + "WHERE alias = 'text' " . + "GROUP BY field,field2 HAVING (COUNT(*) > 1) AND field = '1' " . + "LIMIT 1" + ), + ); + } + + /** + * @dataProvider dataConditional + */ + function testConditional( $sql, $sqlText ) { + $this->assertEquals( trim( $this->db->conditional( + $sql['conds'], + $sql['true'], + $sql['false'] + ) ), $sqlText ); + } + + function dataConditional() { + return array( + array( + array( + 'conds' => array( 'field' => 'text' ), + 'true' => 1, + 'false' => 'NULL', + ), + "(CASE WHEN field = 'text' THEN 1 ELSE NULL END)" + ), + array( + array( + 'conds' => array( 'field' => 'text', 'field2' => 'anothertext' ), + 'true' => 1, + 'false' => 'NULL', + ), + "(CASE WHEN field = 'text' AND field2 = 'anothertext' THEN 1 ELSE NULL END)" + ), + array( + array( + 'conds' => 'field=1', + 'true' => 1, + 'false' => 'NULL', + ), + "(CASE WHEN field=1 THEN 1 ELSE NULL END)" + ), + ); + } +} \ No newline at end of file diff --git a/tests/phpunit/includes/db/DatabaseSqliteTest.php b/tests/phpunit/includes/db/DatabaseSqliteTest.php index 067c731a..d226598b 100644 --- a/tests/phpunit/includes/db/DatabaseSqliteTest.php +++ b/tests/phpunit/includes/db/DatabaseSqliteTest.php @@ -250,6 +250,16 @@ class DatabaseSqliteTest extends MediaWikiTestCase { } } + public function testInsertIdType() { + $db = new DatabaseSqliteStandalone( ':memory:' ); + $this->assertInstanceOf( 'ResultWrapper', + $db->query( 'CREATE TABLE a ( a_1 )', __METHOD__ ), "Database creationg" ); + $this->assertTrue( $db->insert( 'a', array( 'a_1' => 10 ), __METHOD__ ), + "Insertion worked" ); + $this->assertEquals( "integer", gettype( $db->insertId() ), "Actual typecheck" ); + $this->assertTrue( $db->close(), "closing database" ); + } + private function prepareDB( $version ) { static $maint = null; if ( $maint === null ) { diff --git a/tests/phpunit/includes/db/ORMRowTest.php b/tests/phpunit/includes/db/ORMRowTest.php new file mode 100644 index 00000000..9dcaf2b3 --- /dev/null +++ b/tests/phpunit/includes/db/ORMRowTest.php @@ -0,0 +1,234 @@ + + */ +abstract class ORMRowTest extends \MediaWikiTestCase { + + /** + * @since 1.20 + * @return string + */ + protected abstract function getRowClass(); + + /** + * @since 1.20 + * @return IORMTable + */ + protected abstract function getTableInstance(); + + /** + * @since 1.20 + * @return array + */ + public abstract function constructorTestProvider(); + + /** + * @since 1.20 + * @param IORMRow $row + * @param array $data + */ + protected function verifyFields( IORMRow $row, array $data ) { + foreach ( array_keys( $data ) as $fieldName ) { + $this->assertEquals( $data[$fieldName], $row->getField( $fieldName ) ); + } + } + + /** + * @since 1.20 + * @param array $data + * @param boolean $loadDefaults + * @return IORMRow + */ + protected function getRowInstance( array $data, $loadDefaults ) { + $class = $this->getRowClass(); + return new $class( $this->getTableInstance(), $data, $loadDefaults ); + } + + /** + * @since 1.20 + * @return array + */ + protected function getMockValues() { + return array( + 'id' => 1, + 'str' => 'foobar4645645', + 'int' => 42, + 'float' => 4.2, + 'bool' => true, + 'array' => array( 42, 'foobar' ), + 'blob' => new stdClass() + ); + } + + /** + * @since 1.20 + * @return array + */ + protected function getMockFields() { + $mockValues = $this->getMockValues(); + $mockFields = array(); + + foreach ( $this->getTableInstance()->getFields() as $name => $type ) { + if ( $name !== 'id' ) { + $mockFields[$name] = $mockValues[$type]; + } + } + + return $mockFields; + } + + /** + * @since 1.20 + * @return array of IORMRow + */ + public function instanceProvider() { + $instances = array(); + + foreach ( $this->constructorTestProvider() as $arguments ) { + $instances[] = array( call_user_func_array( array( $this, 'getRowInstance' ), $arguments ) ); + } + + return $instances; + } + + /** + * @dataProvider constructorTestProvider + */ + public function testConstructor( array $data, $loadDefaults ) { + $this->verifyFields( $this->getRowInstance( $data, $loadDefaults ), $data ); + } + + /** + * @dataProvider constructorTestProvider + */ + public function testSave( array $data, $loadDefaults ) { + $item = $this->getRowInstance( $data, $loadDefaults ); + + $this->assertTrue( $item->save() ); + + $this->assertTrue( $item->hasIdField() ); + $this->assertTrue( is_integer( $item->getId() ) ); + + $id = $item->getId(); + + $this->assertTrue( $item->save() ); + + $this->assertEquals( $id, $item->getId() ); + + $this->verifyFields( $item, $data ); + } + + /** + * @dataProvider constructorTestProvider + */ + public function testRemove( array $data, $loadDefaults ) { + $item = $this->getRowInstance( $data, $loadDefaults ); + + $this->assertTrue( $item->save() ); + + $this->assertTrue( $item->remove() ); + + $this->assertFalse( $item->hasIdField() ); + + $this->assertTrue( $item->save() ); + + $this->verifyFields( $item, $data ); + + $this->assertTrue( $item->remove() ); + + $this->assertFalse( $item->hasIdField() ); + + $this->verifyFields( $item, $data ); + } + + /** + * @dataProvider instanceProvider + */ + public function testSetField( IORMRow $item ) { + foreach ( $this->getMockFields() as $name => $value ) { + $item->setField( $name, $value ); + $this->assertEquals( $value, $item->getField( $name ) ); + } + } + + /** + * @since 1.20 + * @param array $expected + * @param IORMRow $item + */ + protected function assertFieldValues( array $expected, IORMRow $item ) { + foreach ( $expected as $name => $type ) { + if ( $name !== 'id' ) { + $this->assertEquals( $expected[$name], $item->getField( $name ) ); + } + } + } + + /** + * @dataProvider instanceProvider + */ + public function testSetFields( IORMRow $item ) { + $originalValues = $item->getFields(); + + $item->setFields( array(), false ); + + foreach ( $item->getTable()->getFields() as $name => $type ) { + $originalHas = array_key_exists( $name, $originalValues ); + $newHas = $item->hasField( $name ); + + $this->assertEquals( $originalHas, $newHas ); + + if ( $originalHas && $newHas ) { + $this->assertEquals( $originalValues[$name], $item->getField( $name ) ); + } + } + + $mockFields = $this->getMockFields(); + + $item->setFields( $mockFields, false ); + + $this->assertFieldValues( $originalValues, $item ); + + $item->setFields( $mockFields, true ); + + $this->assertFieldValues( $mockFields, $item ); + } + + // TODO: test all of the methods! + +} \ No newline at end of file diff --git a/tests/phpunit/includes/db/TestORMRowTest.php b/tests/phpunit/includes/db/TestORMRowTest.php new file mode 100644 index 00000000..afd1cb80 --- /dev/null +++ b/tests/phpunit/includes/db/TestORMRowTest.php @@ -0,0 +1,174 @@ + + */ +require_once __DIR__ . "/ORMRowTest.php"; + +class TestORMRowTest extends ORMRowTest { + + /** + * @since 1.20 + * @return string + */ + protected function getRowClass() { + return 'TestORMRow'; + } + + /** + * @since 1.20 + * @return IORMTable + */ + protected function getTableInstance() { + return TestORMTable::singleton(); + } + + public function setUp() { + parent::setUp(); + + $dbw = wfGetDB( DB_MASTER ); + + $isSqlite = $GLOBALS['wgDBtype'] === 'sqlite'; + + $idField = $isSqlite ? 'INTEGER' : 'INT unsigned'; + $primaryKey = $isSqlite ? 'PRIMARY KEY AUTOINCREMENT' : 'auto_increment PRIMARY KEY'; + + $dbw->query( + 'CREATE TABLE IF NOT EXISTS ' . $dbw->tableName( 'orm_test' ) . '( + test_id ' . $idField . ' NOT NULL ' . $primaryKey . ', + test_name VARCHAR(255) NOT NULL, + test_age TINYINT unsigned NOT NULL, + test_height FLOAT NOT NULL, + test_awesome TINYINT unsigned NOT NULL, + test_stuff BLOB NOT NULL, + test_moarstuff BLOB NOT NULL, + test_time varbinary(14) NOT NULL + );' + ); + } + + public function constructorTestProvider() { + return array( + array( + array( + 'name' => 'Foobar', + 'age' => 42, + 'height' => 9000.1, + 'awesome' => true, + 'stuff' => array( 13, 11, 7, 5, 3, 2 ), + 'moarstuff' => (object)array( 'foo' => 'bar', 'bar' => array( 4, 2 ), 'baz' => true ) + ), + true + ), + ); + } + +} + +class TestORMRow extends ORMRow {} + +class TestORMTable extends ORMTable { + + /** + * Returns the name of the database table objects of this type are stored in. + * + * @since 1.20 + * + * @return string + */ + public function getName() { + return 'orm_test'; + } + + /** + * Returns the name of a IORMRow implementing class that + * represents single rows in this table. + * + * @since 1.20 + * + * @return string + */ + public function getRowClass() { + return 'TestORMRow'; + } + + /** + * Returns an array with the fields and their types this object contains. + * This corresponds directly to the fields in the database, without prefix. + * + * field name => type + * + * Allowed types: + * * id + * * str + * * int + * * float + * * bool + * * array + * * blob + * + * @since 1.20 + * + * @return array + */ + public function getFields() { + return array( + 'id' => 'id', + 'name' => 'str', + 'age' => 'int', + 'height' => 'float', + 'awesome' => 'bool', + 'stuff' => 'array', + 'moarstuff' => 'blob', + 'time' => 'int', // TS_MW + ); + } + + /** + * Gets the db field prefix. + * + * @since 1.20 + * + * @return string + */ + protected function getFieldPrefix() { + return 'test_'; + } + + +} diff --git a/tests/phpunit/includes/debug/MWDebugTest.php b/tests/phpunit/includes/debug/MWDebugTest.php index 5a4e66d4..246b2918 100644 --- a/tests/phpunit/includes/debug/MWDebugTest.php +++ b/tests/phpunit/includes/debug/MWDebugTest.php @@ -12,6 +12,11 @@ class MWDebugTest extends MediaWikiTestCase { } /** Clear log before each test */ MWDebug::clearLog(); + wfSuppressWarnings(); + } + + function tearDown() { + wfRestoreWarnings(); } function testAddLog() { @@ -30,7 +35,7 @@ class MWDebugTest extends MediaWikiTestCase { $this->assertEquals( array( array( 'msg' => 'Warning message', 'type' => 'warn', - 'caller' => 'MWDebug::warning', + 'caller' => 'MWDebugTest::testAddWarning', ) ), MWDebug::getLog() ); diff --git a/tests/phpunit/includes/filerepo/FileBackendTest.php b/tests/phpunit/includes/filerepo/FileBackendTest.php index da44797a..a2dc5c6c 100644 --- a/tests/phpunit/includes/filerepo/FileBackendTest.php +++ b/tests/phpunit/includes/filerepo/FileBackendTest.php @@ -3,11 +3,11 @@ /** * @group FileRepo * @group FileBackend + * @group medium */ class FileBackendTest extends MediaWikiTestCase { private $backend, $multiBackend; private $filesToPrune = array(); - private $dirsToPrune = array(); private static $backendToUse; function setUp() { @@ -23,10 +23,14 @@ class FileBackendTest extends MediaWikiTestCase { foreach ( $wgFileBackends as $conf ) { if ( $conf['name'] == $name ) { $useConfig = $conf; + break; } } $useConfig['name'] = 'localtesting'; // swap name - $class = $conf['class']; + $useConfig['shardViaHashLevels'] = array( // test sharding + 'unittest-cont1' => array( 'levels' => 1, 'base' => 16, 'repeat' => 1 ) + ); + $class = $useConfig['class']; self::$backendToUse = new $class( $useConfig ); $this->singleBackend = self::$backendToUse; } @@ -34,6 +38,7 @@ class FileBackendTest extends MediaWikiTestCase { $this->singleBackend = new FSFileBackend( array( 'name' => 'localtesting', 'lockManager' => 'fsLockManager', + #'parallelize' => 'implicit', 'containerPaths' => array( 'unittest-cont1' => "{$tmpPrefix}-localtesting-cont1", 'unittest-cont2' => "{$tmpPrefix}-localtesting-cont2" ) @@ -42,6 +47,7 @@ class FileBackendTest extends MediaWikiTestCase { $this->multiBackend = new FileBackendMultiWrite( array( 'name' => 'localtesting', 'lockManager' => 'fsLockManager', + 'parallelize' => 'implicit', 'backends' => array( array( 'name' => 'localmutlitesting1', @@ -204,7 +210,7 @@ class FileBackendTest extends MediaWikiTestCase { $this->tearDownFiles(); } - function doTestStore( $op ) { + private function doTestStore( $op ) { $backendName = $this->backendClass(); $source = $op['src']; @@ -219,9 +225,9 @@ class FileBackendTest extends MediaWikiTestCase { $status = $this->backend->doOperation( $op ); - $this->assertEquals( array(), $status->errors, + $this->assertGoodStatus( $status, "Store from $source to $dest succeeded without warnings ($backendName)." ); - $this->assertEquals( array(), $status->errors, + $this->assertEquals( true, $status->isOK(), "Store from $source to $dest succeeded ($backendName)." ); $this->assertEquals( array( 0 => true ), $status->success, "Store from $source to $dest has proper 'success' field in Status ($backendName)." ); @@ -238,13 +244,15 @@ class FileBackendTest extends MediaWikiTestCase { $props2 = $this->backend->getFileProps( array( 'src' => $dest ) ); $this->assertEquals( $props1, $props2, "Source and destination have the same props ($backendName)." ); + + $this->assertBackendPathsConsistent( array( $dest ) ); } public function provider_testStore() { $cases = array(); $tmpName = TempFSFile::factory( "unittests_", 'txt' )->getPath(); - $toPath = $this->baseStorePath() . '/unittest-cont1/fun/obj1.txt'; + $toPath = $this->baseStorePath() . '/unittest-cont1/e/fun/obj1.txt'; $op = array( 'op' => 'store', 'src' => $tmpName, 'dst' => $toPath ); $cases[] = array( $op, // operation @@ -286,7 +294,7 @@ class FileBackendTest extends MediaWikiTestCase { $this->tearDownFiles(); } - function doTestCopy( $op ) { + private function doTestCopy( $op ) { $backendName = $this->backendClass(); $source = $op['src']; @@ -296,7 +304,7 @@ class FileBackendTest extends MediaWikiTestCase { $status = $this->backend->doOperation( array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) ); - $this->assertEquals( array(), $status->errors, + $this->assertGoodStatus( $status, "Creation of file at $source succeeded ($backendName)." ); if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) { @@ -305,7 +313,7 @@ class FileBackendTest extends MediaWikiTestCase { $status = $this->backend->doOperation( $op ); - $this->assertEquals( array(), $status->errors, + $this->assertGoodStatus( $status, "Copy from $source to $dest succeeded without warnings ($backendName)." ); $this->assertEquals( true, $status->isOK(), "Copy from $source to $dest succeeded ($backendName)." ); @@ -325,13 +333,15 @@ class FileBackendTest extends MediaWikiTestCase { $props2 = $this->backend->getFileProps( array( 'src' => $dest ) ); $this->assertEquals( $props1, $props2, "Source and destination have the same props ($backendName)." ); + + $this->assertBackendPathsConsistent( array( $source, $dest ) ); } public function provider_testCopy() { $cases = array(); - $source = $this->baseStorePath() . '/unittest-cont1/file.txt'; - $dest = $this->baseStorePath() . '/unittest-cont2/fileMoved.txt'; + $source = $this->baseStorePath() . '/unittest-cont1/e/file.txt'; + $dest = $this->baseStorePath() . '/unittest-cont2/a/fileMoved.txt'; $op = array( 'op' => 'copy', 'src' => $source, 'dst' => $dest ); $cases[] = array( @@ -384,7 +394,7 @@ class FileBackendTest extends MediaWikiTestCase { $status = $this->backend->doOperation( array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) ); - $this->assertEquals( array(), $status->errors, + $this->assertGoodStatus( $status, "Creation of file at $source succeeded ($backendName)." ); if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) { @@ -392,7 +402,7 @@ class FileBackendTest extends MediaWikiTestCase { } $status = $this->backend->doOperation( $op ); - $this->assertEquals( array(), $status->errors, + $this->assertGoodStatus( $status, "Move from $source to $dest succeeded without warnings ($backendName)." ); $this->assertEquals( true, $status->isOK(), "Move from $source to $dest succeeded ($backendName)." ); @@ -414,13 +424,15 @@ class FileBackendTest extends MediaWikiTestCase { "Source file does not exist accourding to props ($backendName)." ); $this->assertEquals( true, $props2['fileExists'], "Destination file exists accourding to props ($backendName)." ); + + $this->assertBackendPathsConsistent( array( $source, $dest ) ); } public function provider_testMove() { $cases = array(); - $source = $this->baseStorePath() . '/unittest-cont1/file.txt'; - $dest = $this->baseStorePath() . '/unittest-cont2/fileMoved.txt'; + $source = $this->baseStorePath() . '/unittest-cont1/e/file.txt'; + $dest = $this->baseStorePath() . '/unittest-cont2/a/fileMoved.txt'; $op = array( 'op' => 'move', 'src' => $source, 'dst' => $dest ); $cases[] = array( @@ -472,13 +484,13 @@ class FileBackendTest extends MediaWikiTestCase { if ( $withSource ) { $status = $this->backend->doOperation( array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) ); - $this->assertEquals( array(), $status->errors, + $this->assertGoodStatus( $status, "Creation of file at $source succeeded ($backendName)." ); } $status = $this->backend->doOperation( $op ); if ( $okStatus ) { - $this->assertEquals( array(), $status->errors, + $this->assertGoodStatus( $status, "Deletion of file at $source succeeded without warnings ($backendName)." ); $this->assertEquals( true, $status->isOK(), "Deletion of file at $source succeeded ($backendName)." ); @@ -499,12 +511,14 @@ class FileBackendTest extends MediaWikiTestCase { $props1 = $this->backend->getFileProps( array( 'src' => $source ) ); $this->assertFalse( $props1['fileExists'], "Source file $source does not exist according to props ($backendName)." ); + + $this->assertBackendPathsConsistent( array( $source ) ); } public function provider_testDelete() { $cases = array(); - $source = $this->baseStorePath() . '/unittest-cont1/myfacefile.txt'; + $source = $this->baseStorePath() . '/unittest-cont1/e/myfacefile.txt'; $op = array( 'op' => 'delete', 'src' => $source ); $cases[] = array( @@ -554,13 +568,13 @@ class FileBackendTest extends MediaWikiTestCase { if ( $alreadyExists ) { $status = $this->backend->doOperation( array( 'op' => 'create', 'content' => $oldText, 'dst' => $dest ) ); - $this->assertEquals( array(), $status->errors, + $this->assertGoodStatus( $status, "Creation of file at $dest succeeded ($backendName)." ); } $status = $this->backend->doOperation( $op ); if ( $okStatus ) { - $this->assertEquals( array(), $status->errors, + $this->assertGoodStatus( $status, "Creation of file at $dest succeeded without warnings ($backendName)." ); $this->assertEquals( true, $status->isOK(), "Creation of file at $dest succeeded ($backendName)." ); @@ -590,6 +604,8 @@ class FileBackendTest extends MediaWikiTestCase { $this->backend->getFileSize( array( 'src' => $dest ) ), "Destination file $dest has original size according to props ($backendName)." ); } + + $this->assertBackendPathsConsistent( array( $dest ) ); } /** @@ -598,7 +614,7 @@ class FileBackendTest extends MediaWikiTestCase { public function provider_testCreate() { $cases = array(); - $dest = $this->baseStorePath() . '/unittest-cont2/myspacefile.txt'; + $dest = $this->baseStorePath() . '/unittest-cont2/a/myspacefile.txt'; $op = array( 'op' => 'create', 'content' => 'test test testing', 'dst' => $dest ); $cases[] = array( @@ -649,6 +665,54 @@ class FileBackendTest extends MediaWikiTestCase { return $cases; } + public function testDoQuickOperations() { + $this->backend = $this->singleBackend; + $this->doTestDoQuickOperations(); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->doTestDoQuickOperations(); + $this->tearDownFiles(); + } + + private function doTestDoQuickOperations() { + $backendName = $this->backendClass(); + + $base = $this->baseStorePath(); + $files = array( + "$base/unittest-cont1/e/fileA.a", + "$base/unittest-cont1/e/fileB.a", + "$base/unittest-cont1/e/fileC.a" + ); + $ops = array(); + $purgeOps = array(); + foreach ( $files as $path ) { + $status = $this->prepare( array( 'dir' => dirname( $path ) ) ); + $this->assertGoodStatus( $status, + "Preparing $path succeeded without warnings ($backendName)." ); + $ops[] = array( 'op' => 'create', 'dst' => $path, 'content' => mt_rand(0,50000) ); + $purgeOps[] = array( 'op' => 'delete', 'src' => $path ); + } + $purgeOps[] = array( 'op' => 'null' ); + $status = $this->backend->doQuickOperations( $ops ); + $this->assertGoodStatus( $status, + "Creation of source files succeeded ($backendName)." ); + + foreach ( $files as $file ) { + $this->assertTrue( $this->backend->fileExists( array( 'src' => $file ) ), + "File $file exists." ); + } + + $status = $this->backend->doQuickOperations( $purgeOps ); + $this->assertGoodStatus( $status, + "Quick deletion of source files succeeded ($backendName)." ); + + foreach ( $files as $file ) { + $this->assertFalse( $this->backend->fileExists( array( 'src' => $file ) ), + "File $file purged." ); + } + } + /** * @dataProvider provider_testConcatenate */ @@ -667,7 +731,7 @@ class FileBackendTest extends MediaWikiTestCase { $this->tearDownFiles(); } - public function doTestConcatenate( $params, $srcs, $srcsContent, $alreadyExists, $okStatus ) { + private function doTestConcatenate( $params, $srcs, $srcsContent, $alreadyExists, $okStatus ) { $backendName = $this->backendClass(); $expContent = ''; @@ -684,7 +748,7 @@ class FileBackendTest extends MediaWikiTestCase { } $status = $this->backend->doOperations( $ops ); - $this->assertEquals( array(), $status->errors, + $this->assertGoodStatus( $status, "Creation of source files succeeded ($backendName)." ); $dest = $params['dst']; @@ -701,7 +765,7 @@ class FileBackendTest extends MediaWikiTestCase { // Combine the files into one $status = $this->backend->concatenate( $params ); if ( $okStatus ) { - $this->assertEquals( array(), $status->errors, + $this->assertGoodStatus( $status, "Creation of concat file at $dest succeeded without warnings ($backendName)." ); $this->assertEquals( true, $status->isOK(), "Creation of concat file at $dest succeeded ($backendName)." ); @@ -736,16 +800,16 @@ class FileBackendTest extends MediaWikiTestCase { $rand = mt_rand( 0, 2000000000 ) . time(); $dest = wfTempDir() . "/randomfile!$rand.txt"; $srcs = array( - $this->baseStorePath() . '/unittest-cont1/file1.txt', - $this->baseStorePath() . '/unittest-cont1/file2.txt', - $this->baseStorePath() . '/unittest-cont1/file3.txt', - $this->baseStorePath() . '/unittest-cont1/file4.txt', - $this->baseStorePath() . '/unittest-cont1/file5.txt', - $this->baseStorePath() . '/unittest-cont1/file6.txt', - $this->baseStorePath() . '/unittest-cont1/file7.txt', - $this->baseStorePath() . '/unittest-cont1/file8.txt', - $this->baseStorePath() . '/unittest-cont1/file9.txt', - $this->baseStorePath() . '/unittest-cont1/file10.txt' + $this->baseStorePath() . '/unittest-cont1/e/file1.txt', + $this->baseStorePath() . '/unittest-cont1/e/file2.txt', + $this->baseStorePath() . '/unittest-cont1/e/file3.txt', + $this->baseStorePath() . '/unittest-cont1/e/file4.txt', + $this->baseStorePath() . '/unittest-cont1/e/file5.txt', + $this->baseStorePath() . '/unittest-cont1/e/file6.txt', + $this->baseStorePath() . '/unittest-cont1/e/file7.txt', + $this->baseStorePath() . '/unittest-cont1/e/file8.txt', + $this->baseStorePath() . '/unittest-cont1/e/file9.txt', + $this->baseStorePath() . '/unittest-cont1/e/file10.txt' ); $content = array( 'egfage', @@ -800,8 +864,8 @@ class FileBackendTest extends MediaWikiTestCase { if ( $alreadyExists ) { $this->prepare( array( 'dir' => dirname( $path ) ) ); - $status = $this->backend->create( array( 'dst' => $path, 'content' => $content ) ); - $this->assertEquals( array(), $status->errors, + $status = $this->create( array( 'dst' => $path, 'content' => $content ) ); + $this->assertGoodStatus( $status, "Creation of file at $path succeeded ($backendName)." ); $size = $this->backend->getFileSize( array( 'src' => $path ) ); @@ -810,20 +874,34 @@ class FileBackendTest extends MediaWikiTestCase { $this->assertEquals( strlen( $content ), $size, "Correct file size of '$path'" ); - $this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 5, + $this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 10, "Correct file timestamp of '$path'" ); $size = $stat['size']; $time = $stat['mtime']; $this->assertEquals( strlen( $content ), $size, "Correct file size of '$path'" ); - $this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 5, + $this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 10, "Correct file timestamp of '$path'" ); + + $this->backend->clearCache( array( $path ) ); + + $size = $this->backend->getFileSize( array( 'src' => $path ) ); + + $this->assertEquals( strlen( $content ), $size, + "Correct file size of '$path'" ); + + $this->backend->preloadCache( array( $path ) ); + + $size = $this->backend->getFileSize( array( 'src' => $path ) ); + + $this->assertEquals( strlen( $content ), $size, + "Correct file size of '$path'" ); } else { $size = $this->backend->getFileSize( array( 'src' => $path ) ); $time = $this->backend->getFileTimestamp( array( 'src' => $path ) ); $stat = $this->backend->getFileStat( array( 'src' => $path ) ); - + $this->assertFalse( $size, "Correct file size of '$path'" ); $this->assertFalse( $time, "Correct file timestamp of '$path'" ); $this->assertFalse( $stat, "Correct file stat of '$path'" ); @@ -834,9 +912,9 @@ class FileBackendTest extends MediaWikiTestCase { $cases = array(); $base = $this->baseStorePath(); - $cases[] = array( "$base/unittest-cont1/b/z/some_file.txt", "some file contents", true ); - $cases[] = array( "$base/unittest-cont1/b/some-other_file.txt", "", true ); - $cases[] = array( "$base/unittest-cont1/b/some-diff_file.txt", null, false ); + $cases[] = array( "$base/unittest-cont1/e/b/z/some_file.txt", "some file contents", true ); + $cases[] = array( "$base/unittest-cont1/e/b/some-other_file.txt", "", true ); + $cases[] = array( "$base/unittest-cont1/e/b/some-diff_file.txt", null, false ); return $cases; } @@ -856,14 +934,14 @@ class FileBackendTest extends MediaWikiTestCase { $this->tearDownFiles(); } - public function doTestGetFileContents( $source, $content ) { + private function doTestGetFileContents( $source, $content ) { $backendName = $this->backendClass(); $this->prepare( array( 'dir' => dirname( $source ) ) ); $status = $this->backend->doOperation( array( 'op' => 'create', 'content' => $content, 'dst' => $source ) ); - $this->assertEquals( array(), $status->errors, + $this->assertGoodStatus( $status, "Creation of file at $source succeeded ($backendName)." ); $this->assertEquals( true, $status->isOK(), "Creation of file at $source succeeded with OK status ($backendName)." ); @@ -880,8 +958,8 @@ class FileBackendTest extends MediaWikiTestCase { $cases = array(); $base = $this->baseStorePath(); - $cases[] = array( "$base/unittest-cont1/b/z/some_file.txt", "some file contents" ); - $cases[] = array( "$base/unittest-cont1/b/some-other_file.txt", "more file contents" ); + $cases[] = array( "$base/unittest-cont1/e/b/z/some_file.txt", "some file contents" ); + $cases[] = array( "$base/unittest-cont1/e/b/some-other_file.txt", "more file contents" ); return $cases; } @@ -901,14 +979,14 @@ class FileBackendTest extends MediaWikiTestCase { $this->tearDownFiles(); } - public function doTestGetLocalCopy( $source, $content ) { + private function doTestGetLocalCopy( $source, $content ) { $backendName = $this->backendClass(); $this->prepare( array( 'dir' => dirname( $source ) ) ); $status = $this->backend->doOperation( array( 'op' => 'create', 'content' => $content, 'dst' => $source ) ); - $this->assertEquals( array(), $status->errors, + $this->assertGoodStatus( $status, "Creation of file at $source succeeded ($backendName)." ); $tmpFile = $this->backend->getLocalCopy( array( 'src' => $source ) ); @@ -923,8 +1001,8 @@ class FileBackendTest extends MediaWikiTestCase { $cases = array(); $base = $this->baseStorePath(); - $cases[] = array( "$base/unittest-cont1/a/z/some_file.txt", "some file contents" ); - $cases[] = array( "$base/unittest-cont1/a/some-other_file.txt", "more file contents" ); + $cases[] = array( "$base/unittest-cont1/e/a/z/some_file.txt", "some file contents" ); + $cases[] = array( "$base/unittest-cont1/e/a/some-other_file.txt", "more file contents" ); return $cases; } @@ -949,9 +1027,8 @@ class FileBackendTest extends MediaWikiTestCase { $this->prepare( array( 'dir' => dirname( $source ) ) ); - $status = $this->backend->doOperation( - array( 'op' => 'create', 'content' => $content, 'dst' => $source ) ); - $this->assertEquals( array(), $status->errors, + $status = $this->create( array( 'content' => $content, 'dst' => $source ) ); + $this->assertGoodStatus( $status, "Creation of file at $source succeeded ($backendName)." ); $tmpFile = $this->backend->getLocalReference( array( 'src' => $source ) ); @@ -966,8 +1043,8 @@ class FileBackendTest extends MediaWikiTestCase { $cases = array(); $base = $this->baseStorePath(); - $cases[] = array( "$base/unittest-cont1/a/z/some_file.txt", "some file contents" ); - $cases[] = array( "$base/unittest-cont1/a/some-other_file.txt", "more file contents" ); + $cases[] = array( "$base/unittest-cont1/e/a/z/some_file.txt", "some file contents" ); + $cases[] = array( "$base/unittest-cont1/e/a/some-other_file.txt", "more file contents" ); return $cases; } @@ -988,19 +1065,19 @@ class FileBackendTest extends MediaWikiTestCase { function provider_testPrepareAndClean() { $base = $this->baseStorePath(); return array( - array( "$base/unittest-cont1/a/z/some_file1.txt", true ), + array( "$base/unittest-cont1/e/a/z/some_file1.txt", true ), array( "$base/unittest-cont2/a/z/some_file2.txt", true ), # Specific to FS backend with no basePath field set #array( "$base/unittest-cont3/a/z/some_file3.txt", false ), ); } - function doTestPrepareAndClean( $path, $isOK ) { + private function doTestPrepareAndClean( $path, $isOK ) { $backendName = $this->backendClass(); $status = $this->prepare( array( 'dir' => dirname( $path ) ) ); if ( $isOK ) { - $this->assertEquals( array(), $status->errors, + $this->assertGoodStatus( $status, "Preparing dir $path succeeded without warnings ($backendName)." ); $this->assertEquals( true, $status->isOK(), "Preparing dir $path succeeded ($backendName)." ); @@ -1011,7 +1088,7 @@ class FileBackendTest extends MediaWikiTestCase { $status = $this->backend->clean( array( 'dir' => dirname( $path ) ) ); if ( $isOK ) { - $this->assertEquals( array(), $status->errors, + $this->assertGoodStatus( $status, "Cleaning dir $path succeeded without warnings ($backendName)." ); $this->assertEquals( true, $status->isOK(), "Cleaning dir $path succeeded ($backendName)." ); @@ -1021,6 +1098,58 @@ class FileBackendTest extends MediaWikiTestCase { } } + public function testRecursiveClean() { + $this->backend = $this->singleBackend; + $this->doTestRecursiveClean(); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->doTestRecursiveClean(); + $this->tearDownFiles(); + } + + private function doTestRecursiveClean() { + $backendName = $this->backendClass(); + + $base = $this->baseStorePath(); + $dirs = array( + "$base/unittest-cont1/e/a", + "$base/unittest-cont1/e/a/b", + "$base/unittest-cont1/e/a/b/c", + "$base/unittest-cont1/e/a/b/c/d0", + "$base/unittest-cont1/e/a/b/c/d1", + "$base/unittest-cont1/e/a/b/c/d2", + "$base/unittest-cont1/e/a/b/c/d0/1", + "$base/unittest-cont1/e/a/b/c/d0/2", + "$base/unittest-cont1/e/a/b/c/d1/3", + "$base/unittest-cont1/e/a/b/c/d1/4", + "$base/unittest-cont1/e/a/b/c/d2/5", + "$base/unittest-cont1/e/a/b/c/d2/6" + ); + foreach ( $dirs as $dir ) { + $status = $this->prepare( array( 'dir' => $dir ) ); + $this->assertGoodStatus( $status, + "Preparing dir $dir succeeded without warnings ($backendName)." ); + } + + if ( $this->backend instanceof FSFileBackend ) { + foreach ( $dirs as $dir ) { + $this->assertEquals( true, $this->backend->directoryExists( array( 'dir' => $dir ) ), + "Dir $dir exists ($backendName)." ); + } + } + + $status = $this->backend->clean( + array( 'dir' => "$base/unittest-cont1", 'recursive' => 1 ) ); + $this->assertGoodStatus( $status, + "Recursive cleaning of dir $dir succeeded without warnings ($backendName)." ); + + foreach ( $dirs as $dir ) { + $this->assertEquals( false, $this->backend->directoryExists( array( 'dir' => $dir ) ), + "Dir $dir no longer exists ($backendName)." ); + } + } + // @TODO: testSecure public function testDoOperations() { @@ -1033,39 +1162,127 @@ class FileBackendTest extends MediaWikiTestCase { $this->tearDownFiles(); $this->doTestDoOperations(); $this->tearDownFiles(); + } + + private function doTestDoOperations() { + $base = $this->baseStorePath(); + + $fileA = "$base/unittest-cont1/e/a/b/fileA.txt"; + $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq'; + $fileB = "$base/unittest-cont1/e/a/b/fileB.txt"; + $fileBContents = 'g-jmq3gpqgt3qtg q3GT '; + $fileC = "$base/unittest-cont1/e/a/b/fileC.txt"; + $fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag'; + $fileD = "$base/unittest-cont1/e/a/b/fileD.txt"; + + $this->prepare( array( 'dir' => dirname( $fileA ) ) ); + $this->create( array( 'dst' => $fileA, 'content' => $fileAContents ) ); + $this->prepare( array( 'dir' => dirname( $fileB ) ) ); + $this->create( array( 'dst' => $fileB, 'content' => $fileBContents ) ); + $this->prepare( array( 'dir' => dirname( $fileC ) ) ); + $this->create( array( 'dst' => $fileC, 'content' => $fileCContents ) ); + $this->prepare( array( 'dir' => dirname( $fileD ) ) ); + + $status = $this->backend->doOperations( array( + array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC, 'overwrite' => 1 ), + // Now: A:, B:, C:, D: (file:) + array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileA, 'overwriteSame' => 1 ), + // Now: A:, B:, C:, D: + array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileD, 'overwrite' => 1 ), + // Now: A:, B:, C:, D: + array( 'op' => 'move', 'src' => $fileB, 'dst' => $fileC ), + // Now: A:, B:, C:, D: + array( 'op' => 'move', 'src' => $fileD, 'dst' => $fileA, 'overwriteSame' => 1 ), + // Now: A:, B:, C:, D: + array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileA, 'overwrite' => 1 ), + // Now: A:, B:, C:, D: + array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC ), + // Now: A:, B:, C:, D: + array( 'op' => 'move', 'src' => $fileA, 'dst' => $fileC, 'overwriteSame' => 1 ), + // Now: A:, B:, C:, D: + array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwrite' => 1 ), + // Does nothing + array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ), + // Does nothing + array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwrite' => 1 ), + // Does nothing + array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ), + // Does nothing + array( 'op' => 'null' ), + // Does nothing + ) ); + $this->assertGoodStatus( $status, "Operation batch succeeded" ); + $this->assertEquals( true, $status->isOK(), "Operation batch succeeded" ); + $this->assertEquals( 13, count( $status->success ), + "Operation batch has correct success array" ); + + $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileA ) ), + "File does not exist at $fileA" ); + $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileB ) ), + "File does not exist at $fileB" ); + $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileD ) ), + "File does not exist at $fileD" ); + + $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $fileC ) ), + "File exists at $fileC" ); + $this->assertEquals( $fileBContents, + $this->backend->getFileContents( array( 'src' => $fileC ) ), + "Correct file contents of $fileC" ); + $this->assertEquals( strlen( $fileBContents ), + $this->backend->getFileSize( array( 'src' => $fileC ) ), + "Correct file size of $fileC" ); + $this->assertEquals( wfBaseConvert( sha1( $fileBContents ), 16, 36, 31 ), + $this->backend->getFileSha1Base36( array( 'src' => $fileC ) ), + "Correct file SHA-1 of $fileC" ); + } + + public function testDoOperationsPipeline() { $this->backend = $this->singleBackend; $this->tearDownFiles(); - $this->doTestDoOperationsFailing(); + $this->doTestDoOperationsPipeline(); $this->tearDownFiles(); $this->backend = $this->multiBackend; $this->tearDownFiles(); - $this->doTestDoOperationsFailing(); + $this->doTestDoOperationsPipeline(); $this->tearDownFiles(); - - // @TODO: test some cases where the ops should fail } - function doTestDoOperations() { + // concurrency orientated + private function doTestDoOperationsPipeline() { $base = $this->baseStorePath(); - $fileA = "$base/unittest-cont1/a/b/fileA.txt"; $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq'; - $fileB = "$base/unittest-cont1/a/b/fileB.txt"; $fileBContents = 'g-jmq3gpqgt3qtg q3GT '; - $fileC = "$base/unittest-cont1/a/b/fileC.txt"; $fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag'; - $fileD = "$base/unittest-cont1/a/b/fileD.txt"; + + $tmpNameA = TempFSFile::factory( "unittests_", 'txt' )->getPath(); + file_put_contents( $tmpNameA, $fileAContents ); + $tmpNameB = TempFSFile::factory( "unittests_", 'txt' )->getPath(); + file_put_contents( $tmpNameB, $fileBContents ); + $tmpNameC = TempFSFile::factory( "unittests_", 'txt' )->getPath(); + file_put_contents( $tmpNameC, $fileCContents ); + + $this->filesToPrune[] = $tmpNameA; # avoid file leaking + $this->filesToPrune[] = $tmpNameB; # avoid file leaking + $this->filesToPrune[] = $tmpNameC; # avoid file leaking + + $fileA = "$base/unittest-cont1/e/a/b/fileA.txt"; + $fileB = "$base/unittest-cont1/e/a/b/fileB.txt"; + $fileC = "$base/unittest-cont1/e/a/b/fileC.txt"; + $fileD = "$base/unittest-cont1/e/a/b/fileD.txt"; $this->prepare( array( 'dir' => dirname( $fileA ) ) ); - $this->backend->create( array( 'dst' => $fileA, 'content' => $fileAContents ) ); + $this->create( array( 'dst' => $fileA, 'content' => $fileAContents ) ); $this->prepare( array( 'dir' => dirname( $fileB ) ) ); - $this->backend->create( array( 'dst' => $fileB, 'content' => $fileBContents ) ); $this->prepare( array( 'dir' => dirname( $fileC ) ) ); - $this->backend->create( array( 'dst' => $fileC, 'content' => $fileCContents ) ); + $this->prepare( array( 'dir' => dirname( $fileD ) ) ); $status = $this->backend->doOperations( array( + array( 'op' => 'store', 'src' => $tmpNameA, 'dst' => $fileA, 'overwriteSame' => 1 ), + array( 'op' => 'store', 'src' => $tmpNameB, 'dst' => $fileB, 'overwrite' => 1 ), + array( 'op' => 'store', 'src' => $tmpNameC, 'dst' => $fileC, 'overwrite' => 1 ), array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC, 'overwrite' => 1 ), // Now: A:, B:, C:, D: (file:) array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileA, 'overwriteSame' => 1 ), @@ -1094,9 +1311,9 @@ class FileBackendTest extends MediaWikiTestCase { // Does nothing ) ); - $this->assertEquals( array(), $status->errors, "Operation batch succeeded" ); + $this->assertGoodStatus( $status, "Operation batch succeeded" ); $this->assertEquals( true, $status->isOK(), "Operation batch succeeded" ); - $this->assertEquals( 13, count( $status->success ), + $this->assertEquals( 16, count( $status->success ), "Operation batch has correct success array" ); $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileA ) ), @@ -1119,7 +1336,19 @@ class FileBackendTest extends MediaWikiTestCase { "Correct file SHA-1 of $fileC" ); } - function doTestDoOperationsFailing() { + public function testDoOperationsFailing() { + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestDoOperationsFailing(); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestDoOperationsFailing(); + $this->tearDownFiles(); + } + + private function doTestDoOperationsFailing() { $base = $this->baseStorePath(); $fileA = "$base/unittest-cont2/a/b/fileA.txt"; @@ -1131,11 +1360,11 @@ class FileBackendTest extends MediaWikiTestCase { $fileD = "$base/unittest-cont2/a/b/fileD.txt"; $this->prepare( array( 'dir' => dirname( $fileA ) ) ); - $this->backend->create( array( 'dst' => $fileA, 'content' => $fileAContents ) ); + $this->create( array( 'dst' => $fileA, 'content' => $fileAContents ) ); $this->prepare( array( 'dir' => dirname( $fileB ) ) ); - $this->backend->create( array( 'dst' => $fileB, 'content' => $fileBContents ) ); + $this->create( array( 'dst' => $fileB, 'content' => $fileBContents ) ); $this->prepare( array( 'dir' => dirname( $fileC ) ) ); - $this->backend->create( array( 'dst' => $fileC, 'content' => $fileCContents ) ); + $this->create( array( 'dst' => $fileC, 'content' => $fileCContents ) ); $status = $this->backend->doOperations( array( array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC, 'overwrite' => 1 ), @@ -1195,23 +1424,26 @@ class FileBackendTest extends MediaWikiTestCase { private function doTestGetFileList() { $backendName = $this->backendClass(); - $base = $this->baseStorePath(); + + // Should have no errors + $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont-notexists" ) ); + $files = array( - "$base/unittest-cont1/test1.txt", - "$base/unittest-cont1/test2.txt", - "$base/unittest-cont1/test3.txt", - "$base/unittest-cont1/subdir1/test1.txt", - "$base/unittest-cont1/subdir1/test2.txt", - "$base/unittest-cont1/subdir2/test3.txt", - "$base/unittest-cont1/subdir2/test4.txt", - "$base/unittest-cont1/subdir2/subdir/test1.txt", - "$base/unittest-cont1/subdir2/subdir/test2.txt", - "$base/unittest-cont1/subdir2/subdir/test3.txt", - "$base/unittest-cont1/subdir2/subdir/test4.txt", - "$base/unittest-cont1/subdir2/subdir/test5.txt", - "$base/unittest-cont1/subdir2/subdir/sub/test0.txt", - "$base/unittest-cont1/subdir2/subdir/sub/120-px-file.txt", + "$base/unittest-cont1/e/test1.txt", + "$base/unittest-cont1/e/test2.txt", + "$base/unittest-cont1/e/test3.txt", + "$base/unittest-cont1/e/subdir1/test1.txt", + "$base/unittest-cont1/e/subdir1/test2.txt", + "$base/unittest-cont1/e/subdir2/test3.txt", + "$base/unittest-cont1/e/subdir2/test4.txt", + "$base/unittest-cont1/e/subdir2/subdir/test1.txt", + "$base/unittest-cont1/e/subdir2/subdir/test2.txt", + "$base/unittest-cont1/e/subdir2/subdir/test3.txt", + "$base/unittest-cont1/e/subdir2/subdir/test4.txt", + "$base/unittest-cont1/e/subdir2/subdir/test5.txt", + "$base/unittest-cont1/e/subdir2/subdir/sub/test0.txt", + "$base/unittest-cont1/e/subdir2/subdir/sub/120-px-file.txt", ); // Add the files @@ -1220,28 +1452,28 @@ class FileBackendTest extends MediaWikiTestCase { $this->prepare( array( 'dir' => dirname( $file ) ) ); $ops[] = array( 'op' => 'create', 'content' => 'xxy', 'dst' => $file ); } - $status = $this->backend->doOperations( $ops ); - $this->assertEquals( array(), $status->errors, + $status = $this->backend->doQuickOperations( $ops ); + $this->assertGoodStatus( $status, "Creation of files succeeded ($backendName)." ); $this->assertEquals( true, $status->isOK(), "Creation of files succeeded with OK status ($backendName)." ); // Expected listing $expected = array( - "test1.txt", - "test2.txt", - "test3.txt", - "subdir1/test1.txt", - "subdir1/test2.txt", - "subdir2/test3.txt", - "subdir2/test4.txt", - "subdir2/subdir/test1.txt", - "subdir2/subdir/test2.txt", - "subdir2/subdir/test3.txt", - "subdir2/subdir/test4.txt", - "subdir2/subdir/test5.txt", - "subdir2/subdir/sub/test0.txt", - "subdir2/subdir/sub/120-px-file.txt", + "e/test1.txt", + "e/test2.txt", + "e/test3.txt", + "e/subdir1/test1.txt", + "e/subdir1/test2.txt", + "e/subdir2/test3.txt", + "e/subdir2/test4.txt", + "e/subdir2/subdir/test1.txt", + "e/subdir2/subdir/test2.txt", + "e/subdir2/subdir/test3.txt", + "e/subdir2/subdir/test4.txt", + "e/subdir2/subdir/test5.txt", + "e/subdir2/subdir/sub/test0.txt", + "e/subdir2/subdir/sub/120-px-file.txt", ); sort( $expected ); @@ -1279,7 +1511,7 @@ class FileBackendTest extends MediaWikiTestCase { // Actual listing (no trailing slash) $list = array(); - $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/subdir2/subdir" ) ); + $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/e/subdir2/subdir" ) ); foreach ( $iter as $file ) { $list[] = $file; } @@ -1289,7 +1521,7 @@ class FileBackendTest extends MediaWikiTestCase { // Actual listing (with trailing slash) $list = array(); - $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/subdir2/subdir/" ) ); + $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/e/subdir2/subdir/" ) ); foreach ( $iter as $file ) { $list[] = $file; } @@ -1306,6 +1538,26 @@ class FileBackendTest extends MediaWikiTestCase { $this->assertEquals( $expected, $list, "Correct file listing ($backendName), second iteration." ); + // Expected listing (top files only) + $expected = array( + "test1.txt", + "test2.txt", + "test3.txt", + "test4.txt", + "test5.txt" + ); + sort( $expected ); + + // Actual listing (top files only) + $list = array(); + $iter = $this->backend->getTopFileList( array( 'dir' => "$base/unittest-cont1/e/subdir2/subdir" ) ); + foreach ( $iter as $file ) { + $list[] = $file; + } + sort( $list ); + + $this->assertEquals( $expected, $list, "Correct top file listing ($backendName)." ); + foreach ( $files as $file ) { // clean up $this->backend->doOperation( array( 'op' => 'delete', 'src' => $file ) ); } @@ -1314,12 +1566,268 @@ class FileBackendTest extends MediaWikiTestCase { foreach ( $iter as $iter ) {} // no errors } + public function testGetDirectoryList() { + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestGetDirectoryList(); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestGetDirectoryList(); + $this->tearDownFiles(); + } + + private function doTestGetDirectoryList() { + $backendName = $this->backendClass(); + + $base = $this->baseStorePath(); + $files = array( + "$base/unittest-cont1/e/test1.txt", + "$base/unittest-cont1/e/test2.txt", + "$base/unittest-cont1/e/test3.txt", + "$base/unittest-cont1/e/subdir1/test1.txt", + "$base/unittest-cont1/e/subdir1/test2.txt", + "$base/unittest-cont1/e/subdir2/test3.txt", + "$base/unittest-cont1/e/subdir2/test4.txt", + "$base/unittest-cont1/e/subdir2/subdir/test1.txt", + "$base/unittest-cont1/e/subdir3/subdir/test2.txt", + "$base/unittest-cont1/e/subdir4/subdir/test3.txt", + "$base/unittest-cont1/e/subdir4/subdir/test4.txt", + "$base/unittest-cont1/e/subdir4/subdir/test5.txt", + "$base/unittest-cont1/e/subdir4/subdir/sub/test0.txt", + "$base/unittest-cont1/e/subdir4/subdir/sub/120-px-file.txt", + ); + + // Add the files + $ops = array(); + foreach ( $files as $file ) { + $this->prepare( array( 'dir' => dirname( $file ) ) ); + $ops[] = array( 'op' => 'create', 'content' => 'xxy', 'dst' => $file ); + } + $status = $this->backend->doQuickOperations( $ops ); + $this->assertGoodStatus( $status, + "Creation of files succeeded ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Creation of files succeeded with OK status ($backendName)." ); + + $this->assertEquals( true, + $this->backend->directoryExists( array( 'dir' => "$base/unittest-cont1/e/subdir1" ) ), + "Directory exists in ($backendName)." ); + $this->assertEquals( true, + $this->backend->directoryExists( array( 'dir' => "$base/unittest-cont1/e/subdir2/subdir" ) ), + "Directory exists in ($backendName)." ); + $this->assertEquals( false, + $this->backend->directoryExists( array( 'dir' => "$base/unittest-cont1/e/subdir2/test1.txt" ) ), + "Directory does not exists in ($backendName)." ); + + // Expected listing + $expected = array( + "e", + ); + sort( $expected ); + + // Actual listing (no trailing slash) + $list = array(); + $iter = $this->backend->getTopDirectoryList( array( 'dir' => "$base/unittest-cont1" ) ); + foreach ( $iter as $file ) { + $list[] = $file; + } + sort( $list ); + + $this->assertEquals( $expected, $list, "Correct top dir listing ($backendName)." ); + + // Expected listing + $expected = array( + "subdir1", + "subdir2", + "subdir3", + "subdir4", + ); + sort( $expected ); + + // Actual listing (no trailing slash) + $list = array(); + $iter = $this->backend->getTopDirectoryList( array( 'dir' => "$base/unittest-cont1/e" ) ); + foreach ( $iter as $file ) { + $list[] = $file; + } + sort( $list ); + + $this->assertEquals( $expected, $list, "Correct top dir listing ($backendName)." ); + + // Actual listing (with trailing slash) + $list = array(); + $iter = $this->backend->getTopDirectoryList( array( 'dir' => "$base/unittest-cont1/e/" ) ); + foreach ( $iter as $file ) { + $list[] = $file; + } + sort( $list ); + + $this->assertEquals( $expected, $list, "Correct top dir listing ($backendName)." ); + + // Expected listing + $expected = array( + "subdir", + ); + sort( $expected ); + + // Actual listing (no trailing slash) + $list = array(); + $iter = $this->backend->getTopDirectoryList( array( 'dir' => "$base/unittest-cont1/e/subdir2" ) ); + foreach ( $iter as $file ) { + $list[] = $file; + } + sort( $list ); + + $this->assertEquals( $expected, $list, "Correct top dir listing ($backendName)." ); + + // Actual listing (with trailing slash) + $list = array(); + $iter = $this->backend->getTopDirectoryList( array( 'dir' => "$base/unittest-cont1/e/subdir2/" ) ); + foreach ( $iter as $file ) { + $list[] = $file; + } + sort( $list ); + + $this->assertEquals( $expected, $list, "Correct top dir listing ($backendName)." ); + + // Actual listing (using iterator second time) + $list = array(); + foreach ( $iter as $file ) { + $list[] = $file; + } + sort( $list ); + + $this->assertEquals( $expected, $list, "Correct top dir listing ($backendName), second iteration." ); + + // Expected listing (recursive) + $expected = array( + "e", + "e/subdir1", + "e/subdir2", + "e/subdir3", + "e/subdir4", + "e/subdir2/subdir", + "e/subdir3/subdir", + "e/subdir4/subdir", + "e/subdir4/subdir/sub", + ); + sort( $expected ); + + // Actual listing (recursive) + $list = array(); + $iter = $this->backend->getDirectoryList( array( 'dir' => "$base/unittest-cont1/" ) ); + foreach ( $iter as $file ) { + $list[] = $file; + } + sort( $list ); + + $this->assertEquals( $expected, $list, "Correct dir listing ($backendName)." ); + + // Expected listing (recursive) + $expected = array( + "subdir", + "subdir/sub", + ); + sort( $expected ); + + // Actual listing (recursive) + $list = array(); + $iter = $this->backend->getDirectoryList( array( 'dir' => "$base/unittest-cont1/e/subdir4" ) ); + foreach ( $iter as $file ) { + $list[] = $file; + } + sort( $list ); + + $this->assertEquals( $expected, $list, "Correct dir listing ($backendName)." ); + + // Actual listing (recursive, second time) + $list = array(); + foreach ( $iter as $file ) { + $list[] = $file; + } + sort( $list ); + + $this->assertEquals( $expected, $list, "Correct dir listing ($backendName)." ); + + foreach ( $files as $file ) { // clean up + $this->backend->doOperation( array( 'op' => 'delete', 'src' => $file ) ); + } + + $iter = $this->backend->getDirectoryList( array( 'dir' => "$base/unittest-cont1/not/exists" ) ); + foreach ( $iter as $iter ) {} // no errors + } + + public function testLockCalls() { + $this->backend = $this->singleBackend; + $this->doTestLockCalls(); + } + + private function doTestLockCalls() { + $backendName = $this->backendClass(); + + for ( $i=0; $i<50; $i++ ) { + $paths = array( + "test1.txt", + "test2.txt", + "test3.txt", + "subdir1", + "subdir1", // duplicate + "subdir1/test1.txt", + "subdir1/test2.txt", + "subdir2", + "subdir2", // duplicate + "subdir2/test3.txt", + "subdir2/test4.txt", + "subdir2/subdir", + "subdir2/subdir/test1.txt", + "subdir2/subdir/test2.txt", + "subdir2/subdir/test3.txt", + "subdir2/subdir/test4.txt", + "subdir2/subdir/test5.txt", + "subdir2/subdir/sub", + "subdir2/subdir/sub/test0.txt", + "subdir2/subdir/sub/120-px-file.txt", + ); + + $status = $this->backend->lockFiles( $paths, LockManager::LOCK_EX ); + $this->assertEquals( array(), $status->errors, + "Locking of files succeeded ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Locking of files succeeded with OK status ($backendName)." ); + + $status = $this->backend->lockFiles( $paths, LockManager::LOCK_SH ); + $this->assertEquals( array(), $status->errors, + "Locking of files succeeded ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Locking of files succeeded with OK status ($backendName)." ); + + $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_SH ); + $this->assertEquals( array(), $status->errors, + "Locking of files succeeded ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Locking of files succeeded with OK status ($backendName)." ); + + $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_EX ); + $this->assertEquals( array(), $status->errors, + "Locking of files succeeded ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Locking of files succeeded with OK status ($backendName)." ); + } + } + // test helper wrapper for backend prepare() function private function prepare( array $params ) { - $this->dirsToPrune[] = $params['dir']; return $this->backend->prepare( $params ); } + // test helper wrapper for backend prepare() function + private function create( array $params ) { + $params['op'] = 'create'; + return $this->backend->doQuickOperations( array( $params ) ); + } + function tearDownFiles() { foreach ( $this->filesToPrune as $file ) { @unlink( $file ); @@ -1328,10 +1836,7 @@ class FileBackendTest extends MediaWikiTestCase { foreach ( $containers as $container ) { $this->deleteFiles( $container ); } - foreach ( $this->dirsToPrune as $dir ) { - $this->recursiveClean( $dir ); - } - $this->filesToPrune = $this->dirsToPrune = array(); + $this->filesToPrune = array(); } private function deleteFiles( $container ) { @@ -1339,17 +1844,22 @@ class FileBackendTest extends MediaWikiTestCase { $iter = $this->backend->getFileList( array( 'dir' => "$base/$container" ) ); if ( $iter ) { foreach ( $iter as $file ) { - $this->backend->delete( array( 'src' => "$base/$container/$file" ), array( 'force' => 1 ) ); + $this->backend->delete( array( 'src' => "$base/$container/$file" ), + array( 'force' => 1, 'nonLocking' => 1 ) ); } } + $this->backend->clean( array( 'dir' => "$base/$container", 'recursive' => 1 ) ); } - private function recursiveClean( $dir ) { - do { - if ( !$this->backend->clean( array( 'dir' => $dir ) )->isOK() ) { - break; - } - } while ( $dir = FileBackend::parentStoragePath( $dir ) ); + function assertBackendPathsConsistent( array $paths ) { + if ( $this->backend instanceof FileBackendMultiWrite ) { + $status = $this->backend->consistencyCheck( $paths ); + $this->assertGoodStatus( $status, "Files synced: " . implode( ',', $paths ) ); + } + } + + function assertGoodStatus( $status, $msg ) { + $this->assertEquals( print_r( array(), 1 ), print_r( $status->errors, 1 ), $msg ); } function tearDown() { diff --git a/tests/phpunit/includes/filerepo/FileRepoTest.php b/tests/phpunit/includes/filerepo/FileRepoTest.php index 0f023138..8f92c123 100644 --- a/tests/phpunit/includes/filerepo/FileRepoTest.php +++ b/tests/phpunit/includes/filerepo/FileRepoTest.php @@ -34,8 +34,12 @@ class FileRepoTest extends MediaWikiTestCase { function testFileRepoConstructionWithRequiredOptions() { $f = new FileRepo( array( 'name' => 'FileRepoTestRepository', - 'backend' => 'local-backend', - )); + 'backend' => new FSFileBackend( array( + 'name' => 'local-testing', + 'lockManager' => 'nullLockManager', + 'containerPaths' => array() + ) ) + ) ); $this->assertInstanceOf( 'FileRepo', $f ); } } diff --git a/tests/phpunit/includes/filerepo/StoreBatchTest.php b/tests/phpunit/includes/filerepo/StoreBatchTest.php index 6abceeb3..3ab56af8 100644 --- a/tests/phpunit/includes/filerepo/StoreBatchTest.php +++ b/tests/phpunit/includes/filerepo/StoreBatchTest.php @@ -1,6 +1,7 @@ assertEquals( $transformedA, $cssB, 'Test A-B transformation' ); + + $transformedB = CSSJanus::transform( $cssB ); + $this->assertEquals( $transformedB, $cssA, 'Test B-A transformation' ); + + // If no B version is provided, it means + // the output should equal the input. + } else { + $transformedA = CSSJanus::transform( $cssA ); + $this->assertEquals( $transformedA, $cssA, 'Nothing was flipped' ); + } + } + + /** + * @dataProvider provideTransformAdvancedCases + */ + function testTransformAdvanced( $code, $expectedOutput, $options = array() ) { + $swapLtrRtlInURL = isset( $options['swapLtrRtlInURL'] ) ? $options['swapLtrRtlInURL'] : false; + $swapLeftRightInURL = isset( $options['swapLeftRightInURL'] ) ? $options['swapLeftRightInURL'] : false; + + $flipped = CSSJanus::transform( $code, $swapLtrRtlInURL, $swapLeftRightInURL ); + + $this->assertEquals( $expectedOutput, $flipped, + 'Test flipping, options: url-ltr-rtl=' . ($swapLtrRtlInURL ? 'true' : 'false') + . ' url-left-right=' . ($swapLeftRightInURL ? 'true' : 'false') + ); + } + /** + * @dataProvider provideTransformBrokenCases + * @group Broken + */ + function testTransformBroken( $code, $expectedOutput ) { + $flipped = CSSJanus::transform( $code ); + + $this->assertEquals( $expectedOutput, $flipped, 'Test flipping' ); + } + + /** + * These transform cases are tested *in both directions* + * No need to declare a principle twice in both directions here. + */ + function provideTransformCases() { + return array( + // Property keys + array( + '.foo { left: 0; }', + '.foo { right: 0; }' + ), + // Guard against partial keys + // (CSS currently doesn't have flippable properties + // that contain the direction as part of the key without + // dash separation) + array( + '.foo { alright: 0; }' + ), + array( + '.foo { balleft: 0; }' + ), + + // Dashed property keys + array( + '.foo { padding-left: 0; }', + '.foo { padding-right: 0; }' + ), + array( + '.foo { margin-left: 0; }', + '.foo { margin-right: 0; }' + ), + array( + '.foo { border-left: 0; }', + '.foo { border-right: 0; }' + ), + + // Double-dashed property keys + array( + '.foo { border-left-color: red; }', + '.foo { border-right-color: red; }' + ), + array( + // Includes unknown properties? + '.foo { x-left-y: 0; }', + '.foo { x-right-y: 0; }' + ), + + // Multi-value properties + array( + '.foo { padding: 0; }' + ), + array( + '.foo { padding: 0 1px; }' + ), + array( + '.foo { padding: 0 1px 2px; }' + ), + array( + '.foo { padding: 0 1px 2px 3px; }', + '.foo { padding: 0 3px 2px 1px; }' + ), + + // Shorthand / Four notation + array( + '.foo { padding: .25em 15px 0pt 0ex; }', + '.foo { padding: .25em 0ex 0pt 15px; }' + ), + array( + '.foo { margin: 1px -4px 3px 2px; }', + '.foo { margin: 1px 2px 3px -4px; }' + ), + array( + '.foo { padding: 0 15px .25em 0; }', + '.foo { padding: 0 0 .25em 15px; }' + ), + array( + '.foo { padding: 1px 4.1grad 3px 2%; }', + '.foo { padding: 1px 2% 3px 4.1grad; }' + ), + array( + '.foo { padding: 1px 2px 3px auto; }', + '.foo { padding: 1px auto 3px 2px; }' + ), + array( + '.foo { padding: 1px inherit 3px auto; }', + '.foo { padding: 1px auto 3px inherit; }' + ), + array( + '.foo { border-radius: .25em 15px 0pt 0ex; }', + '.foo { border-radius: .25em 0ex 0pt 15px; }' + ), + array( + '.foo { x-unknown: a b c d; }' + ), + array( + '.foo barpx 0 2% { opacity: 0; }' + ), + array( + '#settings td p strong' + ), + array( + # Not sure how 4+ values should behave, + # testing to make sure changes are detected + '.foo { x-unknown: 1 2 3 4 5; }', + '.foo { x-unknown: 1 4 3 2 5; }', + ), + array( + '.foo { x-unknown: 1 2 3 4 5 6; }', + '.foo { x-unknown: 1 4 3 2 5 6; }', + ), + + // Shorthand / Three notation + array( + '.foo { margin: 1em 0 .25em; }' + ), + array( + '.foo { margin:-1.5em 0 -.75em; }' + ), + + // Shorthand / Two notation + array( + '.foo { padding: 1px 2px; }' + ), + + // Shorthand / One notation + array( + '.foo { padding: 1px; }' + ), + + // Direction + // Note: This differs from the Python implementation, + // see also CSSJanus::fixDirection for more info. + array( + '.foo { direction: ltr; }', + '.foo { direction: rtl; }' + ), + array( + '.foo { direction: rtl; }', + '.foo { direction: ltr; }' + ), + array( + 'input { direction: ltr; }', + 'input { direction: rtl; }' + ), + array( + 'input { direction: rtl; }', + 'input { direction: ltr; }' + ), + array( + 'body { direction: ltr; }', + 'body { direction: rtl; }' + ), + array( + '.foo, body, input { direction: ltr; }', + '.foo, body, input { direction: rtl; }' + ), + array( + 'body { padding: 10px; direction: ltr; }', + 'body { padding: 10px; direction: rtl; }' + ), + array( + 'body { direction: ltr } .myClass { direction: ltr }', + 'body { direction: rtl } .myClass { direction: rtl }' + ), + + // Left/right values + array( + '.foo { float: left; }', + '.foo { float: right; }' + ), + array( + '.foo { text-align: left; }', + '.foo { text-align: right; }' + ), + array( + '.foo { -x-unknown: left; }', + '.foo { -x-unknown: right; }' + ), + // Guard against selectors that look flippable + array( + '.column-left { width: 0; }' + ), + array( + 'a.left { width: 0; }' + ), + array( + 'a.leftification { width: 0; }' + ), + array( + 'a.ltr { width: 0; }' + ), + array( + #
+ '.a-ltr.png { width: 0; }' + ), + array( + # + 'foo-ltr[attr="x"] { width: 0; }' + ), + array( + 'div.left > span.right+span.left { width: 0; }' + ), + array( + '.thisclass .left .myclass { width: 0; }' + ), + array( + '.thisclass .left .myclass #myid { width: 0; }' + ), + + // Cursor values (east/west) + array( + '.foo { cursor: e-resize; }', + '.foo { cursor: w-resize; }' + ), + array( + '.foo { cursor: se-resize; }', + '.foo { cursor: sw-resize; }' + ), + array( + '.foo { cursor: ne-resize; }', + '.foo { cursor: nw-resize; }' + ), + + // Background + array( + '.foo { background-position: top left; }', + '.foo { background-position: top right; }' + ), + array( + '.foo { background: url(/foo/bar.png) top left; }', + '.foo { background: url(/foo/bar.png) top right; }' + ), + array( + '.foo { background: url(/foo/bar.png) top left no-repeat; }', + '.foo { background: url(/foo/bar.png) top right no-repeat; }' + ), + array( + '.foo { background: url(/foo/bar.png) no-repeat top left; }', + '.foo { background: url(/foo/bar.png) no-repeat top right; }' + ), + array( + '.foo { background: #fff url(/foo/bar.png) no-repeat top left; }', + '.foo { background: #fff url(/foo/bar.png) no-repeat top right; }' + ), + array( + '.foo { background-position: 100% 40%; }', + '.foo { background-position: 0% 40%; }' + ), + array( + '.foo { background-position: 23% 0; }', + '.foo { background-position: 77% 0; }' + ), + array( + '.foo { background-position: 23% auto; }', + '.foo { background-position: 77% auto; }' + ), + array( + '.foo { background-position-x: 23%; }', + '.foo { background-position-x: 77%; }' + ), + array( + '.foo { background-position-y: 23%; }', + '.foo { background-position-y: 23%; }' + ), + array( + '.foo { background:url(../foo.png) no-repeat 75% 50%; }', + '.foo { background:url(../foo.png) no-repeat 25% 50%; }' + ), + array( + '.foo { background: 10% 20% } .bar { background: 40% 30% }', + '.foo { background: 90% 20% } .bar { background: 60% 30% }' + ), + + // Multiple rules + array( + 'body { direction: rtl; float: right; } .foo { direction: ltr; float: right; }', + 'body { direction: ltr; float: left; } .foo { direction: rtl; float: left; }', + ), + + // Duplicate properties + array( + '.foo { float: left; float: right; float: left; }', + '.foo { float: right; float: left; float: right; }', + ), + + // Preserve comments + array( + '/* left /* right */left: 10px', + '/* left /* right */right: 10px' + ), + array( + '/*left*//*left*/left: 10px', + '/*left*//*left*/right: 10px' + ), + array( + '/* Going right is cool */ .foo { width: 0 }', + ), + array( + "/* padding-right 1 2 3 4 */\n#test { width: 0}\n/*right*/" + ), + array( + "/** Two line comment\n * left\n \*/\n#test {width: 0}" + ), + + // @noflip annotation + array( + // before selector (single) + '/* @noflip */ div { float: left; }' + ), + array( + // before selector (multiple) + '/* @noflip */ div, .notme { float: left; }' + ), + array( + // inside selector + 'div, /* @noflip */ .foo { float: left; }' + ), + array( + // after selector + 'div, .notme /* @noflip */ { float: left; }' + ), + array( + // before multiple rules + '/* @noflip */ div { float: left; } .foo { float: left; }', + '/* @noflip */ div { float: left; } .foo { float: right; }' + ), + array( + // after multiple rules + '.foo { float: left; } /* @noflip */ div { float: left; }', + '.foo { float: right; } /* @noflip */ div { float: left; }' + ), + array( + // before multiple properties + 'div { /* @noflip */ float: left; text-align: left; }', + 'div { /* @noflip */ float: left; text-align: right; }' + ), + array( + // after multiple properties + 'div { float: left; /* @noflip */ text-align: left; }', + 'div { float: right; /* @noflip */ text-align: left; }' + ), + + // Guard against css3 stuff + array( + 'background-image: -moz-linear-gradient(#326cc1, #234e8c);' + ), + array( + 'background-image: -webkit-gradient(linear, 100% 0%, 0% 0%, from(#666666), to(#ffffff));' + ), + + // CSS syntax / white-space variations + // spaces, no spaces, tabs, new lines, omitting semi-colons + array( + ".foo { left: 0; }", + ".foo { right: 0; }" + ), + array( + ".foo{ left: 0; }", + ".foo{ right: 0; }" + ), + array( + ".foo{ left: 0 }", + ".foo{ right: 0 }" + ), + array( + ".foo{left:0 }", + ".foo{right:0 }" + ), + array( + ".foo{left:0}", + ".foo{right:0}" + ), + array( + ".foo { left : 0 ; }", + ".foo { right : 0 ; }" + ), + array( + ".foo\n { left : 0 ; }", + ".foo\n { right : 0 ; }" + ), + array( + ".foo\n { \nleft : 0 ; }", + ".foo\n { \nright : 0 ; }" + ), + array( + ".foo\n { \n left : 0 ; }", + ".foo\n { \n right : 0 ; }" + ), + array( + ".foo\n { \n left\n : 0; }", + ".foo\n { \n right\n : 0; }" + ), + array( + ".foo \n { \n left\n : 0; }", + ".foo \n { \n right\n : 0; }" + ), + array( + ".foo\n{\nleft\n:\n0;}", + ".foo\n{\nright\n:\n0;}" + ), + array( + ".foo\n.bar {\n\tleft: 0;\n}", + ".foo\n.bar {\n\tright: 0;\n}" + ), + array( + ".foo\t{\tleft\t:\t0;}", + ".foo\t{\tright\t:\t0;}" + ), + ); + } + + /** + * These cases are tested in one way only (format: actual, expected, msg). + * If both ways can be tested, either put both versions in here or move + * it to provideTransformCases(). + */ + function provideTransformAdvancedCases() { + $bgPairs = array( + # [ - _ . ] <-> [ left right ltr rtl ] + 'foo.jpg' => 'foo.jpg', + 'left.jpg' => 'right.jpg', + 'ltr.jpg' => 'rtl.jpg', + + 'foo-left.png' => 'foo-right.png', + 'foo_left.png' => 'foo_right.png', + 'foo.left.png' => 'foo.right.png', + + 'foo-ltr.png' => 'foo-rtl.png', + 'foo_ltr.png' => 'foo_rtl.png', + 'foo.ltr.png' => 'foo.rtl.png', + + 'left-foo.png' => 'right-foo.png', + 'left_foo.png' => 'right_foo.png', + 'left.foo.png' => 'right.foo.png', + + 'ltr-foo.png' => 'rtl-foo.png', + 'ltr_foo.png' => 'rtl_foo.png', + 'ltr.foo.png' => 'rtl.foo.png', + + 'foo-ltr-left.gif' => 'foo-rtl-right.gif', + 'foo_ltr_left.gif' => 'foo_rtl_right.gif', + 'foo.ltr.left.gif' => 'foo.rtl.right.gif', + 'foo-ltr_left.gif' => 'foo-rtl_right.gif', + 'foo_ltr.left.gif' => 'foo_rtl.right.gif', + ); + $provider = array(); + foreach ( $bgPairs as $left => $right ) { + # By default '-rtl' and '-left' etc. are not touched, + # Only when the appropiate parameter is set. + $provider[] = array( + ".foo { background: url(images/$left); }", + ".foo { background: url(images/$left); }" + ); + $provider[] = array( + ".foo { background: url(images/$right); }", + ".foo { background: url(images/$right); }" + ); + $provider[] = array( + ".foo { background: url(images/$left); }", + ".foo { background: url(images/$right); }", + array( + 'swapLtrRtlInURL' => true, + 'swapLeftRightInURL' => true, + ) + ); + $provider[] = array( + ".foo { background: url(images/$right); }", + ".foo { background: url(images/$left); }", + array( + 'swapLtrRtlInURL' => true, + 'swapLeftRightInURL' => true, + ) + ); + } + + return $provider; + } + + /** + * Cases that are currently failing, but + * should be looked at in the future as enhancements and/or bug fix + */ + function provideTransformBrokenCases() { + return array( + // Guard against partial keys + array( + '.foo { leftxx: 0; }', + '.foo { leftxx: 0; }' + ), + array( + '.foo { rightxx: 0; }', + '.foo { rightxx: 0; }' + ), + + // Guard against selectors that look flippable + array( + # + 'foo-left-x[attr="x"] { width: 0; }', + 'foo-left-x[attr="x"] { width: 0; }' + ), + array( + #
+ '.foo[data-left="x"] { width: 0; }', + '.foo[data-left="x"] { width: 0; }' + ), + ); + } +} diff --git a/tests/phpunit/includes/libs/CSSMinTest.php b/tests/phpunit/includes/libs/CSSMinTest.php new file mode 100644 index 00000000..a3827756 --- /dev/null +++ b/tests/phpunit/includes/libs/CSSMinTest.php @@ -0,0 +1,142 @@ +oldServer = $wgServer; + $this->oldCanServer = $wgCanonicalServer; + $wgServer = $wgCanonicalServer = 'http://wiki.example.org'; + } + + function tearDown() { + // Restore $wgServer and $wgCanonicalServer + global $wgServer, $wgCanonicalServer; + $wgServer = $this->oldServer; + $wgCanonicalServer = $this->oldCanServer; + + parent::tearDown(); + } + + /** + * @dataProvider provideMinifyCases + */ + function testMinify( $code, $expectedOutput ) { + $minified = CSSMin::minify( $code ); + + $this->assertEquals( $expectedOutput, $minified, 'Minified output should be in the form expected.' ); + } + + function provideMinifyCases() { + return array( + // Whitespace + array( "\r\t\f \v\n\r", "" ), + array( "foo, bar {\n\tprop: value;\n}", "foo,bar{prop:value}" ), + + // Loose comments + array( "/* foo */", "" ), + array( "/*******\n foo\n *******/", "" ), + array( "/*!\n foo\n */", "" ), + + // Inline comments in various different places + array( "/* comment */foo, bar {\n\tprop: value;\n}", "foo,bar{prop:value}" ), + array( "foo/* comment */, bar {\n\tprop: value;\n}", "foo,bar{prop:value}" ), + array( "foo,/* comment */ bar {\n\tprop: value;\n}", "foo,bar{prop:value}" ), + array( "foo, bar/* comment */ {\n\tprop: value;\n}", "foo,bar{prop:value}" ), + array( "foo, bar {\n\t/* comment */prop: value;\n}", "foo,bar{prop:value}" ), + array( "foo, bar {\n\tprop: /* comment */value;\n}", "foo,bar{prop:value}" ), + array( "foo, bar {\n\tprop: value /* comment */;\n}", "foo,bar{prop:value }" ), + array( "foo, bar {\n\tprop: value; /* comment */\n}", "foo,bar{prop:value; }" ), + + // Keep track of things that aren't as minified as much as they + // could be (bug 35493) + array( 'foo { prop: value ;}', 'foo{prop:value }' ), + array( 'foo { prop : value; }', 'foo{prop :value}' ), + array( 'foo { prop: value ; }', 'foo{prop:value }' ), + array( 'foo { font-family: "foo" , "bar"; }', 'foo{font-family:"foo" ,"bar"}' ), + array( "foo { src:\n\turl('foo') ,\n\turl('bar') ; }", "foo{src:url('foo') ,url('bar') }" ), + + // Interesting cases with string values + // - Double quotes, single quotes + array( 'foo { content: ""; }', 'foo{content:""}' ), + array( "foo { content: ''; }", "foo{content:''}" ), + array( 'foo { content: "\'"; }', 'foo{content:"\'"}' ), + array( "foo { content: '\"'; }", "foo{content:'\"'}" ), + // - Whitespace in string values + array( 'foo { content: " "; }', 'foo{content:" "}' ), + ); + } + + /** + * @dataProvider provideRemapCases + */ + function testRemap( $message, $params, $expectedOutput ) { + $remapped = call_user_func_array( 'CSSMin::remap', $params ); + + $messageAdd = " Case: $message"; + $this->assertEquals( $expectedOutput, $remapped, 'CSSMin::remap should return the expected url form.' . $messageAdd ); + } + + function provideRemapCases() { + // Parameter signature: + // CSSMin::remap( $code, $local, $remote, $embedData = true ) + return array( + array( + 'Simple case', + array( 'foo { prop: url(bar.png); }', false, 'http://example.org', false ), + 'foo { prop: url(http://example.org/bar.png); }', + ), + array( + 'Without trailing slash', + array( 'foo { prop: url(../bar.png); }', false, 'http://example.org/quux', false ), + 'foo { prop: url(http://example.org/quux/../bar.png); }', + ), + array( + 'With trailing slash on remote (bug 27052)', + array( 'foo { prop: url(../bar.png); }', false, 'http://example.org/quux/', false ), + 'foo { prop: url(http://example.org/quux/../bar.png); }', + ), + array( + 'Guard against stripping double slashes from query', + array( 'foo { prop: url(bar.png?corge=//grault); }', false, 'http://example.org/quux/', false ), + 'foo { prop: url(http://example.org/quux/bar.png?corge=//grault); }', + ), + array( + 'Expand absolute paths', + array( 'foo { prop: url(/w/skin/images/bar.png); }', false, 'http://example.org/quux', false ), + 'foo { prop: url(http://wiki.example.org/w/skin/images/bar.png); }', + ), + ); + } + + /** + * Seperated because they are currently broken (bug 35492) + * + * @group Broken + * @dataProvider provideStringCases + */ + function testMinifyWithCSSStringValues( $code, $expectedOutput ) { + $this->testMinifyOutput( $code, $expectedOutput ); + } + + function provideStringCases() { + return array( + // String values should be respected + // - More than one space in a string value + array( 'foo { content: " "; }', 'foo{content:" "}' ), + // - Using a tab in a string value (turns into a space) + array( "foo { content: '\t'; }", "foo{content:'\t'}" ), + // - Using css-like syntax in string values + array( 'foo::after { content: "{;}"; position: absolute; }', 'foo::after{content:"{;}";position:absolute}' ), + ); + } +} diff --git a/tests/phpunit/includes/libs/GenericArrayObjectTest.php b/tests/phpunit/includes/libs/GenericArrayObjectTest.php new file mode 100644 index 00000000..70fce111 --- /dev/null +++ b/tests/phpunit/includes/libs/GenericArrayObjectTest.php @@ -0,0 +1,245 @@ + + */ +abstract class GenericArrayObjectTest extends MediaWikiTestCase { + + /** + * Returns objects that can serve as elements in the concrete GenericArrayObject deriving class being tested. + * + * @since 1.20 + * + * @return array + */ + public abstract function elementInstancesProvider(); + + /** + * Returns the name of the concrete class being tested. + * + * @since 1.20 + * + * @return string + */ + public abstract function getInstanceClass(); + + /** + * Provides instances of the concrete class being tested. + * + * @since 1.20 + * + * @return array + */ + public function instanceProvider() { + $instances = array(); + + foreach ( $this->elementInstancesProvider() as $elementInstances ) { + $instances[] = $this->getNew( $elementInstances ); + } + + return $this->arrayWrap( $instances ); + } + + /** + * @since 1.20 + * + * @param array $elements + * + * @return GenericArrayObject + */ + protected function getNew( array $elements = array() ) { + $class = $this->getInstanceClass(); + return new $class( $elements ); + } + + /** + * @dataProvider elementInstancesProvider + * + * @since 1.20 + * + * @param array $elements + */ + public function testConstructor( array $elements ) { + $arrayObject = $this->getNew( $elements ); + + $this->assertEquals( count( $elements ), $arrayObject->count() ); + } + + /** + * @dataProvider elementInstancesProvider + * + * @since 1.20 + * + * @param array $elements + */ + public function testIsEmpty( array $elements ) { + $arrayObject = $this->getNew( $elements ); + + $this->assertEquals( $elements === array(), $arrayObject->isEmpty() ); + } + + /** + * @dataProvider instanceProvider + * + * @since 1.20 + * + * @param GenericArrayObject $list + */ + public function testUnset( GenericArrayObject $list ) { + if ( !$list->isEmpty() ) { + $offset = $list->getIterator()->key(); + $count = $list->count(); + $list->offsetUnset( $offset ); + $this->assertEquals( $count - 1, $list->count() ); + } + + if ( !$list->isEmpty() ) { + $offset = $list->getIterator()->key(); + $count = $list->count(); + unset( $list[$offset] ); + $this->assertEquals( $count - 1, $list->count() ); + } + + $exception = null; + try { $list->offsetUnset( 'sdfsedtgsrdysftu' ); } catch ( \Exception $exception ){} + $this->assertInstanceOf( '\Exception', $exception ); + } + + /** + * @dataProvider elementInstancesProvider + * + * @since 1.20 + * + * @param array $elements + */ + public function testAppend( array $elements ) { + $list = $this->getNew(); + + $listSize = count( $elements ); + + foreach ( $elements as $element ) { + $list->append( $element ); + } + + $this->assertEquals( $listSize, $list->count() ); + + $list = $this->getNew(); + + foreach ( $elements as $element ) { + $list[] = $element; + } + + $this->assertEquals( $listSize, $list->count() ); + + $this->checkTypeChecks( function( GenericArrayObject $list, $element ) { + $list->append( $element ); + } ); + } + + /** + * @since 1.20 + * + * @param callback $function + */ + protected function checkTypeChecks( $function ) { + $excption = null; + $list = $this->getNew(); + + $elementClass = $list->getObjectType(); + + foreach ( array( 42, 'foo', array(), new \stdClass(), 4.2 ) as $element ) { + $validValid = $element instanceof $elementClass; + + try{ + call_user_func( $function, $list, $element ); + $valid = true; + } + catch ( InvalidArgumentException $exception ) { + $valid = false; + } + + $this->assertEquals( + $validValid, + $valid, + 'Object of invalid type got successfully added to a GenericArrayObject' + ); + } + } + + /** + * @dataProvider elementInstancesProvider + * + * @since 1.20 + * + * @param array $elements + */ + public function testOffsetSet( array $elements ) { + if ( $elements === array() ) { + $this->assertTrue( true ); + return; + } + + $list = $this->getNew(); + + $element = reset( $elements ); + $list->offsetSet( 42, $element ); + $this->assertEquals( $element, $list->offsetGet( 42 ) ); + + $list = $this->getNew(); + + $element = reset( $elements ); + $list['oHai'] = $element; + $this->assertEquals( $element, $list['oHai'] ); + + $list = $this->getNew(); + + $element = reset( $elements ); + $list->offsetSet( 9001, $element ); + $this->assertEquals( $element, $list[9001] ); + + $list = $this->getNew(); + + $element = reset( $elements ); + $list->offsetSet( null, $element ); + $this->assertEquals( $element, $list[0] ); + + $list = $this->getNew(); + $offset = 0; + + foreach ( $elements as $element ) { + $list->offsetSet( null, $element ); + $this->assertEquals( $element, $list[$offset++] ); + } + + $this->assertEquals( count( $elements ), $list->count() ); + + $this->checkTypeChecks( function( GenericArrayObject $list, $element ) { + $list->offsetSet( mt_rand(), $element ); + } ); + } + +} diff --git a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php index d2bfeedf..f121b018 100644 --- a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php +++ b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php @@ -4,9 +4,18 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { function provideCases() { return array( - // Basic tokens + + // Basic whitespace and comments that should be stripped entirely array( "\r\t\f \v\n\r", "" ), array( "/* Foo *\n*bar\n*/", "" ), + + /** + * Slashes used inside block comments (bug 26931). + * At some point there was a bug that caused this comment to be ended at '* /', + * causing /M... to be left as the beginning of a regex. + */ + array( "/**\n * Foo\n * {\n * 'bar' : {\n * //Multiple rules with configurable operators\n * 'baz' : false\n * }\n */", ""), + /** * ' Foo \' bar \ * baz \' quox ' . @@ -15,11 +24,13 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { array( "\" Foo \\\" bar \\\n baz \\\" quox \" .length", "\" Foo \\\" bar \\\n baz \\\" quox \".length" ), array( "// Foo b/ar baz", "" ), array( "/ Foo \\/ bar [ / \\] / ] baz / .length", "/ Foo \\/ bar [ / \\] / ] baz /.length" ), + // HTML comments array( " bar", "" ), array( "--> Foo", "" ), array( "x --> y", "x-->y" ), + // Semicolon insertion array( "(function(){return\nx;})", "(function(){return\nx;})" ), array( "throw\nx;", "throw\nx;" ), @@ -35,12 +46,19 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { array( "5.\nx;", "5.\nx;" ), array( "0xFF.\nx;", "0xFF.x;" ), array( "5.3.\nx;", "5.3.x;" ), + + // Semicolon insertion between an expression having an inline + // comment after it, and a statement on the next line (bug 27046). + array( "var a = this //foo bar \n for ( b = 0; c < d; b++ ) {}", "var a=this\nfor(b=0;cassertEquals( $expectedOutput, $minified, "Minified output should be in the form expected." ); } - /** - * @dataProvider provideBug32548 - */ - function testBug32548Exponent($num) { - // Long line breaking was being incorrectly done between the base and - // exponent part of a number, causing a syntax error. The line should - // instead break at the start of the number. - $prefix = 'var longVarName' . str_repeat('_', 973) . '='; - $suffix = ',shortVarName=0;'; - - $input = $prefix . $num . $suffix; - $expected = $prefix . "\n" . $num . $suffix; - - $minified = JavaScriptMinifier::minify( $input ); - - $this->assertEquals( $expected, $minified, "Line breaks must not occur in middle of exponent"); - } - function provideBug32548() { return array( array( @@ -145,4 +149,22 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { ), ); } + + /** + * @dataProvider provideBug32548 + */ + function testBug32548Exponent( $num ) { + // Long line breaking was being incorrectly done between the base and + // exponent part of a number, causing a syntax error. The line should + // instead break at the start of the number. + $prefix = 'var longVarName' . str_repeat( '_', 973 ) . '='; + $suffix = ',shortVarName=0;'; + + $input = $prefix . $num . $suffix; + $expected = $prefix . "\n" . $num . $suffix; + + $minified = JavaScriptMinifier::minify( $input ); + + $this->assertEquals( $expected, $minified, "Line breaks must not occur in middle of exponent"); + } } diff --git a/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php b/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php index f4f52dd8..88f87ef9 100644 --- a/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php +++ b/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php @@ -2,7 +2,7 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { public function setUp() { - $this->filePath = dirname( __FILE__ ) . '/../../data/media/'; + $this->filePath = __DIR__ . '/../../data/media/'; } /** @@ -73,7 +73,8 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { $this->assertEquals( '2020:07:14 01:36:05', $meta['DateTimeDigitized'] ); $this->assertEquals( '1997:03:02 00:01:02', $meta['DateTimeOriginal'] ); } - /* File has an invalid time (+ one valid but really weird time) + /** + * File has an invalid time (+ one valid but really weird time) * that shouldn't be included */ public function testIPTCDatesInvalid() { diff --git a/tests/phpunit/includes/media/ExifRotationTest.php b/tests/phpunit/includes/media/ExifRotationTest.php index 25149a05..6af52dd1 100644 --- a/tests/phpunit/includes/media/ExifRotationTest.php +++ b/tests/phpunit/includes/media/ExifRotationTest.php @@ -5,16 +5,12 @@ */ class ExifRotationTest extends MediaWikiTestCase { - /** track directories creations. Content will be deleted. */ - private $createdDirs = array(); - function setUp() { parent::setUp(); $this->handler = new BitmapHandler(); - $filePath = dirname( __FILE__ ) . '/../../data/media'; + $filePath = __DIR__ . '/../../data/media'; - $tmpDir = wfTempDir() . '/exif-test-' . time() . '-' . mt_rand(); - $this->createdDirs[] = $tmpDir; + $tmpDir = $this->getNewTempDirectory(); $this->repo = new FSRepo( array( 'name' => 'temp', @@ -42,17 +38,7 @@ class ExifRotationTest extends MediaWikiTestCase { $wgShowEXIF = $this->show; $wgEnableAutoRotation = $this->oldAuto; - $this->tearDownFiles(); - } - - private function tearDownFiles() { - foreach( $this->createdDirs as $dir ) { - wfRecursiveRemoveDir( $dir ); - } - } - - function __destruct() { - $this->tearDownFiles(); + parent::tearDown(); } /** diff --git a/tests/phpunit/includes/media/ExifTest.php b/tests/phpunit/includes/media/ExifTest.php index b39c15e4..045777d7 100644 --- a/tests/phpunit/includes/media/ExifTest.php +++ b/tests/phpunit/includes/media/ExifTest.php @@ -2,22 +2,22 @@ class ExifTest extends MediaWikiTestCase { public function setUp() { - $this->mediaPath = dirname( __FILE__ ) . '/../../data/media/'; + $this->mediaPath = __DIR__ . '/../../data/media/'; if ( !wfDl( 'exif' ) ) { $this->markTestSkipped( "This test needs the exif extension." ); } - global $wgShowEXIF; - $this->showExif = $wgShowEXIF; - $wgShowEXIF = true; + global $wgShowEXIF; + $this->showExif = $wgShowEXIF; + $wgShowEXIF = true; } - public function tearDown() { - global $wgShowEXIF; - $wgShowEXIF = $this->showExif; - } - public function testGPSExtraction() { + public function tearDown() { + global $wgShowEXIF; + $wgShowEXIF = $this->showExif; + } + public function testGPSExtraction() { $filename = $this->mediaPath . 'exif-gps.jpg'; $seg = JpegMetadataExtractor::segmentSplitter( $filename ); $exif = new Exif( $filename, $seg['byteOrder'] ); @@ -25,14 +25,14 @@ class ExifTest extends MediaWikiTestCase { $expected = array( 'GPSLatitude' => 88.5180555556, 'GPSLongitude' => -21.12357, - 'GPSAltitude' => -200, + 'GPSAltitude' => -3.141592653, 'GPSDOP' => '5/1', 'GPSVersionID' => '2.2.0.0', ); $this->assertEquals( $expected, $data, '', 0.0000000001 ); } - public function testUnicodeUserComment() { + public function testUnicodeUserComment() { $filename = $this->mediaPath . 'exif-user-comment.jpg'; $seg = JpegMetadataExtractor::segmentSplitter( $filename ); $exif = new Exif( $filename, $seg['byteOrder'] ); diff --git a/tests/phpunit/includes/media/FormatMetadataTest.php b/tests/phpunit/includes/media/FormatMetadataTest.php index 8a632f52..6ade6702 100644 --- a/tests/phpunit/includes/media/FormatMetadataTest.php +++ b/tests/phpunit/includes/media/FormatMetadataTest.php @@ -4,7 +4,7 @@ class FormatMetadataTest extends MediaWikiTestCase { if ( !wfDl( 'exif' ) ) { $this->markTestSkipped( "This test needs the exif extension." ); } - $filePath = dirname( __FILE__ ) . '/../../data/media'; + $filePath = __DIR__ . '/../../data/media'; $this->backend = new FSFileBackend( array( 'name' => 'localtesting', 'lockManager' => 'nullLockManager', diff --git a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php index 47fc368b..650fdd5c 100644 --- a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php +++ b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php @@ -2,7 +2,7 @@ class GIFMetadataExtractorTest extends MediaWikiTestCase { public function setUp() { - $this->mediaPath = dirname( __FILE__ ) . '/../../data/media/'; + $this->mediaPath = __DIR__ . '/../../data/media/'; } /** * Put in a file, and see if the metadata coming out is as expected. diff --git a/tests/phpunit/includes/media/GIFTest.php b/tests/phpunit/includes/media/GIFTest.php index 36658358..5dcbeee0 100644 --- a/tests/phpunit/includes/media/GIFTest.php +++ b/tests/phpunit/includes/media/GIFTest.php @@ -2,7 +2,7 @@ class GIFHandlerTest extends MediaWikiTestCase { public function setUp() { - $this->filePath = dirname( __FILE__ ) . '/../../data/media'; + $this->filePath = __DIR__ . '/../../data/media'; $this->backend = new FSFileBackend( array( 'name' => 'localtesting', 'lockManager' => 'nullLockManager', diff --git a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php index f48382a4..41d81190 100644 --- a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php +++ b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php @@ -9,7 +9,7 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase { public function setUp() { - $this->filePath = dirname( __FILE__ ) . '/../../data/media/'; + $this->filePath = __DIR__ . '/../../data/media/'; } /** diff --git a/tests/phpunit/includes/media/JpegTest.php b/tests/phpunit/includes/media/JpegTest.php index ddabf5b8..ea007f90 100644 --- a/tests/phpunit/includes/media/JpegTest.php +++ b/tests/phpunit/includes/media/JpegTest.php @@ -2,7 +2,7 @@ class JpegTest extends MediaWikiTestCase { public function setUp() { - $this->filePath = dirname( __FILE__ ) . '/../../data/media/'; + $this->filePath = __DIR__ . '/../../data/media/'; if ( !wfDl( 'exif' ) ) { $this->markTestSkipped( "This test needs the exif extension." ); } diff --git a/tests/phpunit/includes/media/PNGMetadataExtractorTest.php b/tests/phpunit/includes/media/PNGMetadataExtractorTest.php index 9f702c50..1b1b2ec3 100644 --- a/tests/phpunit/includes/media/PNGMetadataExtractorTest.php +++ b/tests/phpunit/includes/media/PNGMetadataExtractorTest.php @@ -2,7 +2,7 @@ class PNGMetadataExtractorTest extends MediaWikiTestCase { function setUp() { - $this->filePath = dirname( __FILE__ ) . '/../../data/media/'; + $this->filePath = __DIR__ . '/../../data/media/'; } /** * Tests zTXt tag (compressed textual metadata) diff --git a/tests/phpunit/includes/media/PNGTest.php b/tests/phpunit/includes/media/PNGTest.php index b6f911fd..fe73c9c7 100644 --- a/tests/phpunit/includes/media/PNGTest.php +++ b/tests/phpunit/includes/media/PNGTest.php @@ -2,7 +2,7 @@ class PNGHandlerTest extends MediaWikiTestCase { public function setUp() { - $this->filePath = dirname( __FILE__ ) . '/../../data/media'; + $this->filePath = __DIR__ . '/../../data/media'; $this->backend = new FSFileBackend( array( 'name' => 'localtesting', 'lockManager' => 'nullLockManager', diff --git a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php index 526beae8..2116554e 100644 --- a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php +++ b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php @@ -39,27 +39,33 @@ class SVGMetadataExtractorTest extends MediaWikiTestCase { } function providerSvgFiles() { - $base = dirname( __FILE__ ) . '/../../data/media'; + $base = __DIR__ . '/../../data/media'; return array( array( "$base/Wikimedia-logo.svg", array( 'width' => 1024, - 'height' => 1024 + 'height' => 1024, + 'originalWidth' => '1024', + 'originalHeight' => '1024', ) ), array( "$base/QA_icon.svg", array( 'width' => 60, - 'height' => 60 + 'height' => 60, + 'originalWidth' => '60', + 'originalHeight' => '60', ) ), array( "$base/Gtk-media-play-ltr.svg", array( 'width' => 60, - 'height' => 60 + 'height' => 60, + 'originalWidth' => '60.0000000', + 'originalHeight' => '60.0000000', ) ), array( @@ -67,14 +73,16 @@ class SVGMetadataExtractorTest extends MediaWikiTestCase { // This file triggered bug 31719, needs entity expansion in the xmlns checks array( 'width' => 385, - 'height' => 385 + 'height' => 385, + 'originalWidth' => '385', + 'originalHeight' => '385.0004883', ) ) ); } function providerSvgFilesWithXMLMetadata() { - $base = dirname( __FILE__ ) . '/../../data/media'; + $base = __DIR__ . '/../../data/media'; $metadata = ' @@ -89,7 +97,9 @@ class SVGMetadataExtractorTest extends MediaWikiTestCase { array( 'height' => 593, 'metadata' => $metadata, - 'width' => 959 + 'width' => 959, + 'originalWidth' => '958.69', + 'originalHeight' => '592.78998', ) ), ); diff --git a/tests/phpunit/includes/media/TiffTest.php b/tests/phpunit/includes/media/TiffTest.php index d4cf503b..4c79f66c 100644 --- a/tests/phpunit/includes/media/TiffTest.php +++ b/tests/phpunit/includes/media/TiffTest.php @@ -5,7 +5,7 @@ class TiffTest extends MediaWikiTestCase { global $wgShowEXIF; $this->showExif = $wgShowEXIF; $wgShowEXIF = true; - $this->filePath = dirname( __FILE__ ) . '/../../data/media/'; + $this->filePath = __DIR__ . '/../../data/media/'; $this->handler = new TiffHandler; } diff --git a/tests/phpunit/includes/media/XMPTest.php b/tests/phpunit/includes/media/XMPTest.php index c952b23c..8198d3b0 100644 --- a/tests/phpunit/includes/media/XMPTest.php +++ b/tests/phpunit/includes/media/XMPTest.php @@ -22,11 +22,11 @@ class XMPTest extends MediaWikiTestCase { } $reader = new XMPReader; $reader->parse( $xmp ); - $this->assertEquals( $expected, $reader->getResults(), $info ); + $this->assertEquals( $expected, $reader->getResults(), $info, 0.0000000001 ); } public function dataXMPParse() { - $xmpPath = dirname( __FILE__ ) . '/../../data/xmp/' ; + $xmpPath = __DIR__ . '/../../data/xmp/' ; $data = array(); // $xmpFiles format: array of arrays with first arg file base name, @@ -52,6 +52,7 @@ class XMPTest extends MediaWikiTestCase { array( 'utf32BE', 'UTF-32BE encoding' ), array( 'utf32LE', 'UTF-32LE encoding' ), array( 'xmpExt', 'Extended XMP missing second part' ), + array( 'gps', 'Handling of exif GPS parameters in XMP' ), ); foreach( $xmpFiles as $file ) { $xmp = file_get_contents( $xmpPath . $file[0] . '.xmp' ); @@ -72,7 +73,7 @@ class XMPTest extends MediaWikiTestCase { * world example file to double check the support for this is right. */ function testExtendedXMP() { - $xmpPath = dirname( __FILE__ ) . '/../../data/xmp/'; + $xmpPath = __DIR__ . '/../../data/xmp/'; $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' ); $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' ); @@ -102,7 +103,7 @@ class XMPTest extends MediaWikiTestCase { * and thus should only return the StandardXMP, not the ExtendedXMP. */ function testExtendedXMPWithWrongGUID() { - $xmpPath = dirname( __FILE__ ) . '/../../data/xmp/'; + $xmpPath = __DIR__ . '/../../data/xmp/'; $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' ); $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' ); @@ -130,7 +131,7 @@ class XMPTest extends MediaWikiTestCase { * which should cause it to ignore the ExtendedXMP packet. */ function testExtendedXMPMissingPacket() { - $xmpPath = dirname( __FILE__ ) . '/../../data/xmp/'; + $xmpPath = __DIR__ . '/../../data/xmp/'; $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' ); $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' ); diff --git a/tests/phpunit/includes/mobile/DeviceDetectionTest.php b/tests/phpunit/includes/mobile/DeviceDetectionTest.php new file mode 100644 index 00000000..0e156532 --- /dev/null +++ b/tests/phpunit/includes/mobile/DeviceDetectionTest.php @@ -0,0 +1,40 @@ +assertEquals( $format, $detector->detectFormatName( $userAgent ) ); + } + + public function provideTestFormatName() { + return array( + array( 'android', 'Mozilla/5.0 (Linux; U; Android 2.1; en-us; Nexus One Build/ERD62) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17' ), + array( 'iphone2', 'Mozilla/5.0 (ipod: U;CPU iPhone OS 2_2 like Mac OS X: es_es) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.0 Mobile/3B48b Safari/419.3' ), + array( 'iphone', 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/3B48b Safari/419.3' ), + array( 'nokia', 'Mozilla/5.0 (SymbianOS/9.1; U; [en]; SymbianOS/91 Series60/3.0) AppleWebKit/413 (KHTML, like Gecko) Safari/413' ), + array( 'palm_pre', 'Mozilla/5.0 (webOS/1.0; U; en-US) AppleWebKit/525.27.1 (KHTML, like Gecko) Version/1.0 Safari/525.27.1 Pre/1.0' ), + array( 'wii', 'Opera/9.00 (Nintendo Wii; U; ; 1309-9; en)' ), + array( 'operamini', 'Opera/9.50 (J2ME/MIDP; Opera Mini/4.0.10031/298; U; en)' ), + array( 'operamobile', 'Opera/9.51 Beta (Microsoft Windows; PPC; Opera Mobi/1718; U; en)' ), + array( 'kindle', 'Mozilla/4.0 (compatible; Linux 2.6.10) NetFront/3.3 Kindle/1.0 (screen 600x800)' ), + array( 'kindle2', 'Mozilla/4.0 (compatible; Linux 2.6.22) NetFront/3.4 Kindle/2.0 (screen 824x1200; rotate)' ), + array( 'capable', 'Mozilla/5.0 (X11; Linux i686; rv:2.0.1) Gecko/20100101 Firefox/4.0.1' ), + array( 'netfront', 'Mozilla/4.08 (Windows; Mobile Content Viewer/1.0) NetFront/3.2' ), + array( 'wap2', 'SonyEricssonK608i/R2L/SN356841000828910 Browser/SEMC-Browser/4.2 Profile/MIDP-2.0 Configuration/CLDC-1.1' ), + array( 'wap2', 'NokiaN73-2/3.0-630.0.2 Series60/3.0 Profile/MIDP-2.0 Configuration/CLDC-1.1' ), + array( 'psp', 'Mozilla/4.0 (PSP (PlayStation Portable); 2.00)' ), + array( 'ps3', 'Mozilla/5.0 (PLAYSTATION 3; 1.00)' ), + array( 'ie', 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)' ), + array( 'ie', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0)' ), + array( 'blackberry', 'BlackBerry9300/5.0.0.716 Profile/MIDP-2.1 Configuration/CLDC-1.1 VendorID/133' ), + array( 'blackberry-lt5', 'BlackBerry7250/4.0.0 Profile/MIDP-2.0 Configuration/CLDC-1.1' ), + ); + } +} diff --git a/tests/phpunit/includes/parser/MediaWikiParserTest.php b/tests/phpunit/includes/parser/MediaWikiParserTest.php index 816c017a..6a6fded1 100644 --- a/tests/phpunit/includes/parser/MediaWikiParserTest.php +++ b/tests/phpunit/includes/parser/MediaWikiParserTest.php @@ -1,5 +1,5 @@ db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) { $image->recordUpload2( '', // archive name - 'Upload of some lame file', + 'Upload of some lame file', 'Some lame file', array( 'size' => 12345, @@ -197,7 +197,7 @@ class NewParserTest extends MediaWikiTestCase { 'mime' => 'image/jpeg', 'metadata' => serialize( array() ), 'sha1' => wfBaseConvert( '', 16, 36, 31 ), - 'fileExists' => true ), + 'fileExists' => true ), $this->db->timestamp( '20010115123500' ), $user ); } @@ -207,8 +207,8 @@ class NewParserTest extends MediaWikiTestCase { if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) { $image->recordUpload2( '', // archive name - 'zomgnotcensored', - 'Borderline image', + 'zomgnotcensored', + 'Borderline image', array( 'size' => 12345, 'width' => 320, @@ -218,7 +218,7 @@ class NewParserTest extends MediaWikiTestCase { 'mime' => 'image/jpeg', 'metadata' => serialize( array() ), 'sha1' => wfBaseConvert( '', 16, 36, 31 ), - 'fileExists' => true ), + 'fileExists' => true ), $this->db->timestamp( '20010115123500' ), $user ); } @@ -326,7 +326,6 @@ class NewParserTest extends MediaWikiTestCase { 'wgExternalLinkTarget' => false, 'wgAlwaysUseTidy' => false, 'wgHtml5' => true, - 'wgCleanupPresentationalAttributes' => true, 'wgWellFormedXml' => true, 'wgAllowMicrodataAttributes' => true, 'wgAdaptiveMessageCache' => true, @@ -345,6 +344,9 @@ class NewParserTest extends MediaWikiTestCase { $this->savedGlobals = array(); + /** @since 1.20 */ + wfRunHooks( 'ParserTestGlobals', array( &$settings ) ); + foreach ( $settings as $var => $val ) { if ( array_key_exists( $var, $GLOBALS ) ) { $this->savedGlobals[$var] = $GLOBALS[$var]; @@ -380,7 +382,7 @@ class NewParserTest extends MediaWikiTestCase { # The entries saved into RepoGroup cache with previous globals will be wrong. RepoGroup::destroySingleton(); FileBackendGroup::destroySingleton(); - MessageCache::singleton()->destroyInstance(); + MessageCache::destroyInstance(); return $context; } @@ -596,7 +598,7 @@ class NewParserTest extends MediaWikiTestCase { * Run a fuzz test series * Draw input from a set of test files * - * @todo @fixme Needs some work to not eat memory until the world explodes + * @todo fixme Needs some work to not eat memory until the world explodes * * @group ParserFuzz */ diff --git a/tests/phpunit/includes/parser/ParserMethodsTest.php b/tests/phpunit/includes/parser/ParserMethodsTest.php new file mode 100644 index 00000000..dea406c3 --- /dev/null +++ b/tests/phpunit/includes/parser/ParserMethodsTest.php @@ -0,0 +1,33 @@ +~~~', + 'hello \'\'this\'\' is ~~~', + ), + ); + } + + /** + * @dataProvider dataPreSaveTransform + */ + public function testPreSaveTransform( $text, $expected ) { + global $wgParser; + + $title = Title::newFromText( str_replace( '::', '__', __METHOD__ ) ); + $user = new User(); + $user->setName( "127.0.0.1" ); + $popts = ParserOptions::newFromUser( $user ); + $text = $wgParser->preSaveTransform( $text, $title, $user, $popts ); + + $this->assertEquals( $expected, $text ); + } + + // TODO: Add tests for cleanSig() / cleanSigInSig(), getSection(), replaceSection(), getPreloadText() +} + diff --git a/tests/phpunit/includes/parser/PreprocessorTest.php b/tests/phpunit/includes/parser/PreprocessorTest.php index 9d3499a0..fee56748 100644 --- a/tests/phpunit/includes/parser/PreprocessorTest.php +++ b/tests/phpunit/includes/parser/PreprocessorTest.php @@ -103,7 +103,7 @@ class PreprocessorTest extends MediaWikiTestCase { array( "{{foo|bar=|}", "{{foo|bar=|}"), array( "{{Foo|} Bar=", "{{Foo|} Bar="), array( "{{Foo|} Bar=}}", ""), - /* array( file_get_contents( dirname( __FILE__ ) . '/QuoteQuran.txt' ), file_get_contents( dirname( __FILE__ ) . '/QuoteQuranExpanded.txt' ) ), */ + /* array( file_get_contents( __DIR__ . '/QuoteQuran.txt' ), file_get_contents( __DIR__ . '/QuoteQuranExpanded.txt' ) ), */ ); } @@ -165,7 +165,7 @@ class PreprocessorTest extends MediaWikiTestCase { * @dataProvider provideFiles */ function testPreprocessorOutputFiles( $filename ) { - $folder = dirname( __FILE__ ) . "/../../../parser/preprocess"; + $folder = __DIR__ . "/../../../parser/preprocess"; $wikiText = file_get_contents( "$folder/$filename.txt" ); $output = $this->preprocessToXml( $wikiText ); diff --git a/tests/phpunit/includes/specials/SpecialSearchTest.php b/tests/phpunit/includes/specials/SpecialSearchTest.php index ea9d5533..20e42a68 100644 --- a/tests/phpunit/includes/specials/SpecialSearchTest.php +++ b/tests/phpunit/includes/specials/SpecialSearchTest.php @@ -87,6 +87,14 @@ class SpecialSearchTest extends MediaWikiTestCase { 'advanced', array( 2, 14 ), 'Bug 33583: search with no option should honor User search preferences' ), + array( + $EMPTY_REQUEST, array_fill_keys( array_map( function( $ns ) { + return "searchNs$ns"; + }, $defaultNS ), 0 ) + array( 'searchNs2' => 1, 'searchNs14' => 1 ), + 'advanced', array( 2, 14 ), + 'Bug 33583: search with no option should honor User search preferences' + . 'and have all other namespace disabled' + ), ); } diff --git a/tests/phpunit/includes/upload/UploadFromUrlTest.php b/tests/phpunit/includes/upload/UploadFromUrlTest.php index d56cce31..f66c387b 100644 --- a/tests/phpunit/includes/upload/UploadFromUrlTest.php +++ b/tests/phpunit/includes/upload/UploadFromUrlTest.php @@ -20,7 +20,7 @@ class UploadFromUrlTest extends ApiTestCase { } } - protected function doApiRequest( $params, $unused = null, $appendModule = false, $user = null ) { + protected function doApiRequest( Array $params, Array $unused = null, $appendModule = false, User $user = null ) { $sessionId = session_id(); session_write_close(); @@ -228,11 +228,11 @@ class UploadFromUrlTest extends ApiTestCase { $talk = $this->user->user->getTalkPage(); if ( $talk->exists() ) { - $a = new Article( $talk ); - $a->doDeleteArticle( '' ); + $page = WikiPage::factory( $talk ); + $page->doDeleteArticle( '' ); } - $this->assertFalse( (bool)$talk->getArticleId( Title::GAID_FOR_UPDATE ), 'User talk does not exist' ); + $this->assertFalse( (bool)$talk->getArticleID( Title::GAID_FOR_UPDATE ), 'User talk does not exist' ); $data = $this->doApiRequest( array( 'action' => 'upload', @@ -249,7 +249,7 @@ class UploadFromUrlTest extends ApiTestCase { $job->run(); $this->assertTrue( wfLocalFile( 'UploadFromUrlTest.png' )->exists() ); - $this->assertTrue( (bool)$talk->getArticleId( Title::GAID_FOR_UPDATE ), 'User talk exists' ); + $this->assertTrue( (bool)$talk->getArticleID( Title::GAID_FOR_UPDATE ), 'User talk exists' ); $this->deleteFile( 'UploadFromUrlTest.png' ); @@ -341,8 +341,8 @@ class UploadFromUrlTest extends ApiTestCase { $file = wfFindFile( $name, array( 'ignoreRedirect' => true ) ); $empty = ""; FileDeleteForm::doDelete( $t, $file, $empty, "none", true ); - $a = new Article ( $t ); - $a->doDeleteArticle( "testing" ); + $page = WikiPage::factory( $t ); + $page->doDeleteArticle( "testing" ); } $t = Title::newFromText( $name, NS_FILE ); diff --git a/tests/phpunit/includes/upload/UploadStashTest.php b/tests/phpunit/includes/upload/UploadStashTest.php index c9dbb138..66fafaaf 100644 --- a/tests/phpunit/includes/upload/UploadStashTest.php +++ b/tests/phpunit/includes/upload/UploadStashTest.php @@ -12,17 +12,17 @@ class UploadStashTest extends MediaWikiTestCase { parent::setUp(); // Setup a file for bug 29408 - $this->bug29408File = dirname( __FILE__ ) . '/bug29408'; + $this->bug29408File = __DIR__ . '/bug29408'; file_put_contents( $this->bug29408File, "\x00" ); self::$users = array( - 'sysop' => new ApiTestUser( + 'sysop' => new TestUser( 'Uploadstashtestsysop', 'Upload Stash Test Sysop', 'upload_stash_test_sysop@example.com', array( 'sysop' ) ), - 'uploader' => new ApiTestUser( + 'uploader' => new TestUser( 'Uploadstashtestuser', 'Upload Stash Test User', 'upload_stash_test_user@example.com', diff --git a/tests/phpunit/includes/upload/UploadTest.php b/tests/phpunit/includes/upload/UploadTest.php index 4293d23b..6948f5b1 100644 --- a/tests/phpunit/includes/upload/UploadTest.php +++ b/tests/phpunit/includes/upload/UploadTest.php @@ -12,7 +12,9 @@ class UploadTest extends MediaWikiTestCase { $this->upload = new UploadTestHandler; $this->hooks = $wgHooks; - $wgHooks['InterwikiLoadPrefix'][] = 'MediaWikiTestCase::disableInterwikis'; + $wgHooks['InterwikiLoadPrefix'][] = function( $prefix, &$data ) { + return false; + }; } function tearDown() { -- cgit v1.2.2