diff options
Diffstat (limited to 'tests/phpunit')
176 files changed, 8717 insertions, 1602 deletions
diff --git a/tests/phpunit/LessFileCompilationTest.php b/tests/phpunit/LessFileCompilationTest.php index df4690a4..eec02edc 100644 --- a/tests/phpunit/LessFileCompilationTest.php +++ b/tests/phpunit/LessFileCompilationTest.php @@ -45,11 +45,7 @@ class LessFileCompilationTest extends ResourceLoaderTestCase { $method->setAccessible( true ); $compiler = $method->invoke( $this->module, $rlContext ); - $this->assertNotNull( $compiler->compileFile( $this->file ) ); - } - - public function getName( $withDataSet = true ) { - return $this->toString(); + $this->assertNotNull( $compiler->parseFile( $this->file )->getCss() ); } public function toString() { diff --git a/tests/phpunit/Makefile b/tests/phpunit/Makefile index a33b86a3..e1537bf5 100644 --- a/tests/phpunit/Makefile +++ b/tests/phpunit/Makefile @@ -73,7 +73,6 @@ help: # # Targets: # phpunit (default) Run all the tests with phpunit - # install Install PHPUnit from phpunit.de # tap Run the tests individually through Test::Harness's prove(1) # help You're looking at it! # coverage Run the tests and generates an HTML code coverage report diff --git a/tests/phpunit/MediaWikiTestCase.php b/tests/phpunit/MediaWikiTestCase.php index 72cac051..7dc7027a 100644 --- a/tests/phpunit/MediaWikiTestCase.php +++ b/tests/phpunit/MediaWikiTestCase.php @@ -204,13 +204,11 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { while ( $this->db->trxLevel() > 0 ) { $this->db->rollback(); } - - // don't ignore DB errors - $this->db->ignoreErrors( false ); } DeferredUpdates::clearPendingUpdates(); + ob_start( 'MediaWikiTestCase::wfResetOutputBuffersBarrier' ); } protected function addTmpFiles( $files ) { @@ -218,6 +216,11 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { } protected function tearDown() { + $status = ob_get_status(); + if ( isset( $status['name'] ) && $status['name'] === 'MediaWikiTestCase::wfResetOutputBuffersBarrier' ) { + ob_end_flush(); + } + $this->called['tearDown'] = true; // Cleaning up temporary files foreach ( $this->tmpFiles as $fileName ) { @@ -233,9 +236,6 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { while ( $this->db->trxLevel() > 0 ) { $this->db->rollback(); } - - // don't ignore DB errors - $this->db->ignoreErrors( false ); } // Restore mw globals @@ -716,9 +716,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { * @param string $function */ public function hideDeprecated( $function ) { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); wfDeprecated( $function ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); } /** @@ -1002,9 +1002,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { # This check may also protect against code injection in # case of broken installations. - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( !$haveDiff3 ) { $this->markTestSkipped( "Skip test, since diff3 is not configured" ); @@ -1117,7 +1117,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { // of tidy. In that case however, we can not reliably detect whether a failing validation // is due to malformed HTML, or caused by tidy not being installed as a command line tool. // That would cause all HTML assertions to fail on a system that has no tidy installed. - if ( !$GLOBALS['wgTidyInternal'] ) { + if ( !$GLOBALS['wgTidyInternal'] || !MWTidy::isEnabled() ) { $this->markTestSkipped( 'Tidy extension not installed' ); } @@ -1180,4 +1180,12 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { self::assertFalse( self::tagMatch( $matcher, $actual, $isHtml ), $message ); } + + /** + * Used as a marker to prevent wfResetOutputBuffers from breaking PHPUnit. + * @return string + */ + public static function wfResetOutputBuffersBarrier( $buffer ) { + return $buffer; + } } diff --git a/tests/phpunit/README b/tests/phpunit/README index 0a32ba17..f555812d 100644 --- a/tests/phpunit/README +++ b/tests/phpunit/README @@ -14,16 +14,13 @@ TO RETAIN YOUR DATA. == Installation == -If PHPUnit is not installed, follow the installation instructions in the -PHPUnit Manual at: - - http://www.phpunit.de/manual/current/en/installation.html +If you used composer to install MediaWiki's dependencies PHPUnit will already be available, unless +you explicitly specified the --no-dev flag during the install. In this case just run "composer update". -- or - - -On Unix-like operating systems, run: +Otherwise follow the installation instructions in the +PHPUnit Manual at: - make install + https://phpunit.de/manual/current/en/installation.html == Running tests == @@ -47,7 +44,7 @@ On Windows-family operating systems, run the 'run-tests.bat' batch file. === Writing tests === -A guide to writing unit tests for MediaWiki can be found at: +A guide to writing PHP unit tests for MediaWiki can be found at: - http://mediawiki.org/wiki/Unit_Testing + https://www.mediawiki.org/wiki/Manual:PHP_unit_testing diff --git a/tests/phpunit/ResourceLoaderTestCase.php b/tests/phpunit/ResourceLoaderTestCase.php index deecb31e..325b20ee 100644 --- a/tests/phpunit/ResourceLoaderTestCase.php +++ b/tests/phpunit/ResourceLoaderTestCase.php @@ -25,27 +25,35 @@ abstract class ResourceLoaderTestCase extends MediaWikiTestCase { return $ctx; } - protected function setUp() { - parent::setUp(); - - ResourceLoader::clearCache(); - - $this->setMwGlobals( array( + public static function getSettings() { + return array( // For ResourceLoader::inDebugMode since it doesn't have context - 'wgResourceLoaderDebug' => true, + 'ResourceLoaderDebug' => true, // Avoid influence from wgInvalidateCacheOnLocalSettingsChange - 'wgCacheEpoch' => '20140101000000', + 'CacheEpoch' => '20140101000000', // For ResourceLoader::__construct() - 'wgResourceLoaderSources' => array(), + 'ResourceLoaderSources' => array(), // For wfScript() - 'wgScriptPath' => '/w', - 'wgScriptExtension' => '.php', - 'wgScript' => '/w/index.php', - 'wgLoadScript' => '/w/load.php', - ) ); + 'ScriptPath' => '/w', + 'ScriptExtension' => '.php', + 'Script' => '/w/index.php', + 'LoadScript' => '/w/load.php', + ); + } + + protected function setUp() { + parent::setUp(); + + ResourceLoader::clearCache(); + + $globals = array(); + foreach ( self::getSettings() as $key => $value ) { + $globals['wg' . $key] = $value; + } + $this->setMwGlobals( $globals ); } } @@ -68,14 +76,14 @@ class ResourceLoaderTestModule extends ResourceLoaderModule { } public function getScript( ResourceLoaderContext $context ) { - return $this->script; + return $this->validateScriptFile( 'input', $this->script ); } public function getStyles( ResourceLoaderContext $context ) { return array( '' => $this->styles ); } - public function getDependencies() { + public function getDependencies( ResourceLoaderContext $context = null ) { return $this->dependencies; } @@ -94,6 +102,10 @@ class ResourceLoaderTestModule extends ResourceLoaderModule { public function isRaw() { return $this->isRaw; } + + public function enableModuleContentVersion() { + return true; + } } class ResourceLoaderFileModuleTestModule extends ResourceLoaderFileModule { diff --git a/tests/phpunit/data/css/comments.css b/tests/phpunit/data/css/comments.css new file mode 100644 index 00000000..744a14c7 --- /dev/null +++ b/tests/phpunit/data/css/comments.css @@ -0,0 +1,7 @@ +/* url expressions in comments should be ignored */ + +.selector { /*@noflip*/ background-image: /*@embed*/ url(not-commented.gif); } + +/* +.selector { background-image: url(commented-out.gif); } +*/ diff --git a/tests/phpunit/data/helpers/WellProtectedClass.php b/tests/phpunit/data/helpers/WellProtectedClass.php index 99c7f642..a45cfbbf 100644 --- a/tests/phpunit/data/helpers/WellProtectedClass.php +++ b/tests/phpunit/data/helpers/WellProtectedClass.php @@ -1,20 +1,47 @@ <?php -class WellProtectedClass { +class WellProtectedParentClass { + private $privateParentProperty; + + public function __construct() { + $this->privateParentProperty = 9000; + } + + private function incrementPrivateParentPropertyValue() { + $this->privateParentProperty++; + } + + public function getPrivateParentProperty() { + return $this->privateParentProperty; + } +} + +class WellProtectedClass extends WellProtectedParentClass { protected $property; + private $privateProperty; public function __construct() { + parent::__construct(); $this->property = 1; + $this->privateProperty = 42; } protected function incrementPropertyValue() { $this->property++; } + private function incrementPrivatePropertyValue() { + $this->privateProperty++; + } + public function getProperty() { return $this->property; } + public function getPrivateProperty() { + return $this->privateProperty; + } + protected function whatSecondArg( $a, $b = false ) { return $b; } diff --git a/tests/phpunit/data/import/ImportLinkCacheIntegrationTest.xml b/tests/phpunit/data/import/ImportLinkCacheIntegrationTest.xml new file mode 100644 index 00000000..8949f406 --- /dev/null +++ b/tests/phpunit/data/import/ImportLinkCacheIntegrationTest.xml @@ -0,0 +1,43 @@ +<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.6/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.6/ http://www.mediawiki.org/xml/export-0.6.xsd" version="0.6" xml:lang="en-gb"> + <siteinfo> + <sitename>MW-19</sitename> + <base>http://localhost:8080/w/index.php/Main_Page</base> + <generator>MediaWiki 1.19.7</generator> + <case>first-letter</case> + </siteinfo> + <page> + <title>Lorem ipsum</title> + <ns>0</ns> + <id>493</id> + <sha1>94lztkh4kgb0mvjr87iyjfq4iv7ltlh</sha1> + <revision> + <id>1358</id> + <timestamp>2014-04-04T22:55:04Z</timestamp> + <contributor> + <username>Tester</username> + <id>1</id> + </contributor> + <text xml:space="preserve" bytes="979">[[Has text::Lorem ipsum dolor sit amet consectetuer Maecenas adipiscing Pellentesque id sem]]. [[Has page::Elit Aliquam urna interdum]] morbi faucibus id tellus ipsum semper wisi. [[Has page::Platea enim hendrerit]] pellentesque consectetuer scelerisque Sed est felis felis quis. Auctor Proin In dolor id et ipsum vel at vitae ut. Praesent elit convallis Praesent aliquet pellentesque vel dolor pellentesque lacinia vitae. At tortor lacus Sed In interdum pulvinar et. + +[[Has number::1001]] [[Has quantity::10.25 km²]] [[Has date::1 Jan 2014]] [[Has Url::http://loremipsum.org/]] [[Has annotation uri::http://loremipsum.org/foaf.rdf]] [[Has email::Lorem@ipsum.org]] [[Has temperature::100 °C]] [[Has boolean::true]] + +[[Category:Lorem ipsum]]</text> + </revision> + </page> + <page> + <title>Category:Lorem ipsum</title> + <ns>14</ns> + <id>496</id> + <sha1>sir97j6uzt9ev2uyhaz1aj4i3spogih</sha1> + <revision> + <id>1355</id> + <timestamp>2014-04-04T22:29:18Z</timestamp> + <contributor> + <username>Tester</username> + <id>1</id> + </contributor> + <text xml:space="preserve" bytes="17">[[Category:Main]]</text> + </revision> + </page> +</mediawiki> + diff --git a/tests/phpunit/data/less/common/test.common.mixins.less b/tests/phpunit/data/less/common/test.common.mixins.less index 2fbe9b79..40647291 100644 --- a/tests/phpunit/data/less/common/test.common.mixins.less +++ b/tests/phpunit/data/less/common/test.common.mixins.less @@ -1,5 +1,4 @@ .test-mixin (@value) { color: @value; border: @foo solid @Foo; - line-height: test-sum(@bar, 10, 20); } diff --git a/tests/phpunit/data/less/module/styles.css b/tests/phpunit/data/less/module/styles.css index b78780a9..bac695b9 100644 --- a/tests/phpunit/data/less/module/styles.css +++ b/tests/phpunit/data/less/module/styles.css @@ -1,6 +1,5 @@ /* @noflip */ .unit-tests { - color: green; + color: #008000; border: 2px solid #eeeeee; - line-height: 35; } diff --git a/tests/phpunit/data/media/2_webp_a.webp b/tests/phpunit/data/media/2_webp_a.webp Binary files differnew file mode 100644 index 00000000..8764f066 --- /dev/null +++ b/tests/phpunit/data/media/2_webp_a.webp diff --git a/tests/phpunit/data/media/2_webp_ll.webp b/tests/phpunit/data/media/2_webp_ll.webp Binary files differnew file mode 100644 index 00000000..5794bbf2 --- /dev/null +++ b/tests/phpunit/data/media/2_webp_ll.webp diff --git a/tests/phpunit/data/media/srgb.jpg b/tests/phpunit/data/media/srgb.jpg Binary files differnew file mode 100644 index 00000000..b965dc4f --- /dev/null +++ b/tests/phpunit/data/media/srgb.jpg diff --git a/tests/phpunit/data/media/tinyrgb.icc b/tests/phpunit/data/media/tinyrgb.icc Binary files differnew file mode 100644 index 00000000..eab973f5 --- /dev/null +++ b/tests/phpunit/data/media/tinyrgb.icc diff --git a/tests/phpunit/data/media/tinyrgb.jpg b/tests/phpunit/data/media/tinyrgb.jpg Binary files differnew file mode 100644 index 00000000..12a8e09f --- /dev/null +++ b/tests/phpunit/data/media/tinyrgb.jpg diff --git a/tests/phpunit/data/media/webp_animated.webp b/tests/phpunit/data/media/webp_animated.webp Binary files differnew file mode 100644 index 00000000..25c6a4dd --- /dev/null +++ b/tests/phpunit/data/media/webp_animated.webp diff --git a/tests/phpunit/data/templates/bad_partial.mustache b/tests/phpunit/data/templates/bad_partial.mustache new file mode 100644 index 00000000..d2767f0f --- /dev/null +++ b/tests/phpunit/data/templates/bad_partial.mustache @@ -0,0 +1 @@ +Partial {{>nonexistenttemplate}} in here diff --git a/tests/phpunit/data/templates/has_partial.mustache b/tests/phpunit/data/templates/has_partial.mustache new file mode 100644 index 00000000..504387a4 --- /dev/null +++ b/tests/phpunit/data/templates/has_partial.mustache @@ -0,0 +1 @@ +Partial {{>foobar_args}} in here diff --git a/tests/phpunit/includes/BlockTest.php b/tests/phpunit/includes/BlockTest.php index 19741621..7b0de866 100644 --- a/tests/phpunit/includes/BlockTest.php +++ b/tests/phpunit/includes/BlockTest.php @@ -38,9 +38,13 @@ class BlockTest extends MediaWikiLangTestCase { $oldBlock->delete(); } - $this->block = new Block( 'UTBlockee', $user->getID(), 0, - 'Parce que', 0, false, time() + 100500 + $blockOptions = array( + 'address' => 'UTBlockee', + 'user' => $user->getID(), + 'reason' => 'Parce que', + 'expiry' => time() + 100500, ); + $this->block = new Block( $blockOptions ); $this->madeAt = wfTimestamp( TS_MW ); $this->block->insert(); @@ -151,22 +155,19 @@ class BlockTest extends MediaWikiLangTestCase { ); // 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' + $blockOptions = array( + 'address' => $username, + 'user' => 14146, + 'reason' => 'crosswiki block...', + 'timestamp' => wfTimestampNow(), + 'expiry' => $this->db->getInfinity(), + 'createAccount' => true, + 'enableAutoblock' => true, + 'hideName' => true, + 'blockEmail' => true, + 'byText' => 'MetaWikiUser', ); + $block = new Block( $blockOptions ); $block->insert(); // Reload block from DB @@ -208,22 +209,19 @@ class BlockTest extends MediaWikiLangTestCase { $this->db->update( 'user', array( 'user_id' => 14146 ), array( 'user_id' => $user->getId() ) ); // 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' + $blockOptions = array( + 'address' => 'UserOnForeignWiki', + 'user' => 14146, + 'reason' => 'crosswiki block...', + 'timestamp' => wfTimestampNow(), + 'expiry' => $this->db->getInfinity(), + 'createAccount' => true, + 'enableAutoblock' => true, + 'hideName' => true, + 'blockEmail' => true, + 'byText' => 'MetaWikiUser', ); + $block = new Block( $blockOptions ); $res = $block->insert( $this->db ); $this->assertTrue( (bool)$res['id'], 'Block succeeded' ); @@ -367,4 +365,56 @@ class BlockTest extends MediaWikiLangTestCase { $block = Block::chooseBlock( $xffblocks, $list ); $this->assertEquals( $exResult, $block->mReason, 'Correct block type for XFF header ' . $xff ); } + + public function testDeprecatedConstructor() { + $this->hideDeprecated( 'Block::__construct with multiple arguments' ); + $username = 'UnthinkablySecretRandomUsername'; + $reason = 'being irrational'; + + # Set up the target + $u = User::newFromName( $username ); + if ( $u->getID() == 0 ) { + $u->setPassword( 'TotallyObvious' ); + $u->addToDatabase(); + } + unset( $u ); + + # Make sure the user isn't blocked + $this->assertNull( + Block::newFromTarget( $username ), + "$username should not be blocked" + ); + + # Perform the block + $block = new Block( + /* address */ $username, + /* user */ 0, + /* by */ 0, + /* reason */ $reason, + /* timestamp */ 0, + /* auto */ false, + /* expiry */ 0 + ); + $block->insert(); + + # Check target + $this->assertEquals( + $block->getTarget()->getName(), + $username, + "Target should be set properly" + ); + + # Check supplied parameter + $this->assertEquals( + $block->mReason, + $reason, + "Reason should be non-default" + ); + + # Check default parameter + $this->assertFalse( + (bool)$block->prevents( 'createaccount' ), + "Account creation should not be blocked by default" + ); + } } diff --git a/tests/phpunit/includes/ConsecutiveParametersMatcher.php b/tests/phpunit/includes/ConsecutiveParametersMatcher.php new file mode 100644 index 00000000..adf74bb4 --- /dev/null +++ b/tests/phpunit/includes/ConsecutiveParametersMatcher.php @@ -0,0 +1,123 @@ +<?php +/* + * This file is part of the PHPUnit_MockObject package. + * + * (c) Sebastian Bergmann <sebastian@phpunit.de> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Invocation matcher which looks for sets of specific parameters in the invocations. + * + * Checks the parameters of the incoming invocations, the parameter list is + * checked against the defined constraints in $parameters. If the constraint + * is met it will return true in matches(). + * + * It takes a list of match groups and and increases a call index after each invocation. + * So the first invocation uses the first group of constraints, the second the next and so on. + */ +class PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters extends PHPUnit_Framework_MockObject_Matcher_StatelessInvocation +{ + /** + * @var array + */ + private $_parameterGroups = array(); + + /** + * @var array + */ + private $_invocations = array(); + + /** + * @param array $parameterGroups + */ + public function __construct(array $parameterGroups) + { + foreach ($parameterGroups as $index => $parameters) { + foreach ($parameters as $parameter) { + if (!($parameter instanceof \PHPUnit_Framework_Constraint)) { + $parameter = new \PHPUnit_Framework_Constraint_IsEqual($parameter); + } + $this->_parameterGroups[$index][] = $parameter; + } + } + } + + /** + * @return string + */ + public function toString() + { + $text = 'with consecutive parameters'; + + return $text; + } + + /** + * @param PHPUnit_Framework_MockObject_Invocation $invocation + * @return bool + */ + public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) + { + $this->_invocations[] = $invocation; + $callIndex = count($this->_invocations) - 1; + $this->verifyInvocation($invocation, $callIndex); + + return false; + } + + public function verify() + { + foreach ($this->_invocations as $callIndex => $invocation) { + $this->verifyInvocation($invocation, $callIndex); + } + } + + /** + * Verify a single invocation + * + * @param PHPUnit_Framework_MockObject_Invocation $invocation + * @param int $callIndex + * @throws PHPUnit_Framework_ExpectationFailedException + */ + private function verifyInvocation(PHPUnit_Framework_MockObject_Invocation $invocation, $callIndex) + { + + if (isset($this->_parameterGroups[$callIndex])) { + $parameters = $this->_parameterGroups[$callIndex]; + } else { + // no parameter assertion for this call index + return; + } + + if ($invocation === null) { + throw new PHPUnit_Framework_ExpectationFailedException( + 'Mocked method does not exist.' + ); + } + + if (count($invocation->parameters) < count($parameters)) { + throw new PHPUnit_Framework_ExpectationFailedException( + sprintf( + 'Parameter count for invocation %s is too low.', + $invocation->toString() + ) + ); + } + + foreach ($parameters as $i => $parameter) { + $parameter->evaluate( + $invocation->parameters[$i], + sprintf( + 'Parameter %s for invocation #%d %s does not match expected ' . + 'value.', + $i, + $callIndex, + $invocation->toString() + ) + ); + } + } +} diff --git a/tests/phpunit/includes/EditPageTest.php b/tests/phpunit/includes/EditPageTest.php index 15778e40..27959b1d 100644 --- a/tests/phpunit/includes/EditPageTest.php +++ b/tests/phpunit/includes/EditPageTest.php @@ -11,6 +11,28 @@ */ class EditPageTest extends MediaWikiLangTestCase { + protected function setUp() { + global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang; + + parent::setUp(); + + $this->setMwGlobals( array( + 'wgExtraNamespaces' => $wgExtraNamespaces, + 'wgNamespaceContentModels' => $wgNamespaceContentModels, + 'wgContentHandlers' => $wgContentHandlers, + 'wgContLang' => $wgContLang, + ) ); + + $wgExtraNamespaces[12312] = 'Dummy'; + $wgExtraNamespaces[12313] = 'Dummy_talk'; + + $wgNamespaceContentModels[12312] = "testing"; + $wgContentHandlers["testing"] = 'DummyContentHandlerForTesting'; + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + } + /** * @dataProvider provideExtractSectionTitle * @covers EditPage::extractSectionTitle @@ -499,4 +521,37 @@ hello $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Berta', $bertasEdit, $expectedCode, $expectedText, $message ); } + + /** + * @depends testAutoMerge + */ + public function testCheckDirectEditingDisallowed_forNonTextContent() { + $title = Title::newFromText( 'Dummy:NonTextPageForEditPage' ); + $page = WikiPage::factory( $title ); + + $article = new Article( $title ); + $article->getContext()->setTitle( $title ); + $ep = new EditPage( $article ); + $ep->setContextTitle( $title ); + + $user = $GLOBALS['wgUser']; + + $edit = array( + 'wpTextbox1' => serialize( 'non-text content' ), + 'wpEditToken' => $user->getEditToken(), + 'wpEdittime' => '', + 'wpStarttime' => wfTimestampNow() + ); + + $req = new FauxRequest( $edit, true ); + $ep->importFormData( $req ); + + $this->setExpectedException( + 'MWException', + 'This content model is not supported: testing' + ); + + $ep->internalAttemptSave( $result, false ); + } + } diff --git a/tests/phpunit/includes/ExtraParserTest.php b/tests/phpunit/includes/ExtraParserTest.php index 4a4130e0..7b60fb3f 100644 --- a/tests/phpunit/includes/ExtraParserTest.php +++ b/tests/phpunit/includes/ExtraParserTest.php @@ -2,6 +2,8 @@ /** * Parser-related tests that don't suit for parserTests.txt + * + * @group Database */ class ExtraParserTest extends MediaWikiTestCase { @@ -20,7 +22,6 @@ class ExtraParserTest extends MediaWikiTestCase { 'wgContLang' => $contLang, 'wgLang' => Language::factory( 'en' ), 'wgMemc' => new EmptyBagOStuff, - 'wgAlwaysUseTidy' => false, 'wgCleanSignatures' => true, ) ); diff --git a/tests/phpunit/includes/FauxRequestTest.php b/tests/phpunit/includes/FauxRequestTest.php index 745a5b42..07214b21 100644 --- a/tests/phpunit/includes/FauxRequestTest.php +++ b/tests/phpunit/includes/FauxRequestTest.php @@ -6,13 +6,46 @@ class FauxRequestTest extends MediaWikiTestCase { * @covers FauxRequest::getHeader */ public function testGetSetHeader() { - $value = 'test/test'; + $value = 'text/plain, text/html'; $request = new FauxRequest(); - $request->setHeader( 'Content-Type', $value ); + $request->setHeader( 'Accept', $value ); - $this->assertEquals( $request->getHeader( 'Content-Type' ), $value ); - $this->assertEquals( $request->getHeader( 'CONTENT-TYPE' ), $value ); - $this->assertEquals( $request->getHeader( 'content-type' ), $value ); + $this->assertEquals( $request->getHeader( 'Nonexistent' ), false ); + $this->assertEquals( $request->getHeader( 'Accept' ), $value ); + $this->assertEquals( $request->getHeader( 'ACCEPT' ), $value ); + $this->assertEquals( $request->getHeader( 'accept' ), $value ); + $this->assertEquals( + $request->getHeader( 'Accept', WebRequest::GETHEADER_LIST ), + array( 'text/plain', 'text/html' ) + ); + } + + /** + * @covers FauxRequest::getAllHeaders + */ + public function testGetAllHeaders() { + $_SERVER['HTTP_TEST'] = 'Example'; + + $request = new FauxRequest(); + + $this->assertEquals( + array(), + $request->getAllHeaders() + ); + } + + /** + * @covers FauxRequest::getHeader + */ + public function testGetHeader() { + $_SERVER['HTTP_TEST'] = 'Example'; + + $request = new FauxRequest(); + + $this->assertEquals( + false, + $request->getHeader( 'test' ) + ); } } diff --git a/tests/phpunit/includes/FauxResponseTest.php b/tests/phpunit/includes/FauxResponseTest.php index 4a974ba2..39a0effa 100644 --- a/tests/phpunit/includes/FauxResponseTest.php +++ b/tests/phpunit/includes/FauxResponseTest.php @@ -108,6 +108,13 @@ class FauxResponseTest extends MediaWikiTestCase { 'Third parameter overrides the HTTP/... header' ); + $this->response->statusHeader( 210 ); + $this->assertEquals( + 210, + $this->response->getStatusCode(), + 'Handle statusHeader method' + ); + $this->response->header( 'Location: http://localhost/', false, 206 ); $this->assertEquals( 206, diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php index 1e30273e..e89e36f6 100644 --- a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php +++ b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php @@ -666,9 +666,9 @@ class GlobalTest extends MediaWikiTestCase { public function testWfMkdirParents() { // Should not return true if file exists instead of directory $fname = $this->getNewTempFile(); - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $ok = wfMkdirParents( $fname ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); $this->assertFalse( $ok ); } @@ -687,6 +687,105 @@ class GlobalTest extends MediaWikiTestCase { $this->assertEquals( $expected, $actual, $description ); } + public function wfWikiID() { + $this->setMwGlobals( array( + 'wgDBname' => 'example', + 'wgDBprefix' => '', + ) ); + $this->assertEquals( + wfWikiID(), + 'example' + ); + + $this->setMwGlobals( array( + 'wgDBname' => 'example', + 'wgDBprefix' => 'mw_', + ) ); + $this->assertEquals( + wfWikiID(), + 'example-mw_' + ); + } + + public function testWfMemcKey() { + // Just assert the exact output so we can catch unintentional changes to key + // construction, which would effectively invalidate all existing cache. + + $this->setMwGlobals( array( + 'wgCachePrefix' => false, + 'wgDBname' => 'example', + 'wgDBprefix' => '', + ) ); + $this->assertEquals( + wfMemcKey( 'foo', '123', 'bar' ), + 'example:foo:123:bar' + ); + + $this->setMwGlobals( array( + 'wgCachePrefix' => false, + 'wgDBname' => 'example', + 'wgDBprefix' => 'mw_', + ) ); + $this->assertEquals( + wfMemcKey( 'foo', '123', 'bar' ), + 'example-mw_:foo:123:bar' + ); + + $this->setMwGlobals( array( + 'wgCachePrefix' => 'custom', + 'wgDBname' => 'example', + 'wgDBprefix' => 'mw_', + ) ); + $this->assertEquals( + wfMemcKey( 'foo', '123', 'bar' ), + 'custom:foo:123:bar' + ); + } + + public function testWfForeignMemcKey() { + $this->setMwGlobals( array( + 'wgCachePrefix' => false, + 'wgDBname' => 'example', + 'wgDBprefix' => '', + ) ); + $local = wfMemcKey( 'foo', 'bar' ); + + $this->setMwGlobals( array( + 'wgDBname' => 'other', + 'wgDBprefix' => 'mw_', + ) ); + $this->assertEquals( + wfForeignMemcKey( 'example', '', 'foo', 'bar' ), + $local, + 'Match output of wfMemcKey from local wiki' + ); + } + + public function testWfGlobalCacheKey() { + $this->setMwGlobals( array( + 'wgCachePrefix' => 'ignored', + 'wgDBname' => 'example', + 'wgDBprefix' => '' + ) ); + $one = wfGlobalCacheKey( 'some', 'thing' ); + $this->assertEquals( + $one, + 'global:some:thing' + ); + + $this->setMwGlobals( array( + 'wgDBname' => 'other', + 'wgDBprefix' => 'mw_' + ) ); + $two = wfGlobalCacheKey( 'some', 'thing' ); + + $this->assertEquals( + $one, + $two, + 'Not fragmented by wiki id' + ); + } + public static function provideWfShellWikiCmdList() { global $wgPhpCli; diff --git a/tests/phpunit/includes/GlobalFunctions/wfArrayPlus2dTest.php b/tests/phpunit/includes/GlobalFunctions/wfArrayPlus2dTest.php new file mode 100644 index 00000000..88875bb0 --- /dev/null +++ b/tests/phpunit/includes/GlobalFunctions/wfArrayPlus2dTest.php @@ -0,0 +1,94 @@ +<?php +/** + * @group GlobalFunctions + * @covers ::wfArrayPlus2d + */ +class WfArrayPlus2dTest extends MediaWikiTestCase { + /** + * @dataProvider provideArrays + */ + public function testWfArrayPlus2d( $baseArray, $newValues, $expected, $testName ) { + $this->assertEquals( + $expected, + wfArrayPlus2d( $baseArray, $newValues ), + $testName + ); + } + + /** + * Provider for testing wfArrayPlus2d + * + * @return array + */ + public static function provideArrays() { + return array( + // target array, new values array, expected result + array( + array( 0 => '1dArray' ), + array( 1 => '1dArray' ), + array( 0 => '1dArray', 1 => '1dArray' ), + "Test simple union of two arrays with different keys", + ), + array( + array( + 0 => array( 0 => '2dArray' ), + ), + array( + 0 => array( 1 => '2dArray' ), + ), + array( + 0 => array( 0 => '2dArray', 1 => '2dArray' ), + ), + "Test union of 2d arrays with different keys in the value array", + ), + array( + array( + 0 => array( 0 => '2dArray' ), + ), + array( + 0 => array( 0 => '1dArray' ), + ), + array( + 0 => array( 0 => '2dArray' ), + ), + "Test union of 2d arrays with same keys in the value array", + ), + array( + array( + 0 => array( 0 => array( 0 => '3dArray' ) ), + ), + array( + 0 => array( 0 => array( 1 => '2dArray' ) ), + ), + array( + 0 => array( 0 => array( 0 => '3dArray' ) ), + ), + "Test union of 3d array with different keys", + ), + array( + array( + 0 => array( 0 => array( 0 => '3dArray' ) ), + ), + array( + 0 => array( 1 => array( 0 => '2dArray' ) ), + ), + array( + 0 => array( 0 => array( 0 => '3dArray' ), 1 => array( 0 => '2dArray' ) ), + ), + "Test union of 3d array with different keys in the value array", + ), + array( + array( + 0 => array( 0 => array( 0 => '3dArray' ) ), + ), + array( + 0 => array( 0 => array( 0 => '2dArray' ) ), + ), + array( + 0 => array( 0 => array( 0 => '3dArray' ) ), + ), + "Test union of 3d array with same keys in the value array", + ), + ); + } +} diff --git a/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php index bea496c4..4ce51c6a 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php @@ -21,6 +21,7 @@ class WfTimestampTest extends MediaWikiTestCase { array( -30281104, TS_MW, '19690115123456', 'Negative TS_UNIX to TS_MW' ), array( $t, TS_UNIX, 979562096, 'TS_UNIX to TS_UNIX' ), array( $t, TS_DB, '2001-01-15 12:34:56', 'TS_UNIX to TS_DB' ), + array( $t + .01, TS_MW, '20010115123456', 'TS_UNIX float to TS_MW' ), array( $t, TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_ISO_8601_BASIC to TS_DB' ), diff --git a/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php b/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php index d11668b7..d4df7b00 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php @@ -112,6 +112,8 @@ class WfUrlencodeTest extends MediaWikiTestCase { ### Other tests // slash remain unchanged. %2F seems to break things array( '/', '/' ), + // T105265 + array( '~', '~' ), // Other 'funnies' chars array( '[]', '%5B%5D' ), diff --git a/tests/phpunit/includes/ImportLinkCacheIntegrationTest.php b/tests/phpunit/includes/ImportLinkCacheIntegrationTest.php new file mode 100644 index 00000000..1433b898 --- /dev/null +++ b/tests/phpunit/includes/ImportLinkCacheIntegrationTest.php @@ -0,0 +1,112 @@ +<?php +/** + * Integration test that checks import success and + * LinkCache integration. + * + * @group medium + * @group Database + * + * @author mwjames + */ +class ImportLinkCacheIntegrationTest extends MediaWikiTestCase { + + private $importStreamSource; + + protected function setUp() { + parent::setUp(); + + $file = dirname( __DIR__ ) . '/data/import/ImportLinkCacheIntegrationTest.xml'; + + $this->importStreamSource = ImportStreamSource::newFromFile( $file ); + + if ( !$this->importStreamSource->isGood() ) { + throw new Exception( "Import source for {$file} failed" ); + } + } + + public function testImportForImportSource() { + + $this->doImport( $this->importStreamSource ); + + // Imported title + $loremIpsum = Title::newFromText( 'Lorem ipsum' ); + + $this->assertSame( + $loremIpsum->getArticleID(), + $loremIpsum->getArticleID( Title::GAID_FOR_UPDATE ) + ); + + $categoryLoremIpsum = Title::newFromText( 'Category:Lorem ipsum' ); + + $this->assertSame( + $categoryLoremIpsum->getArticleID(), + $categoryLoremIpsum->getArticleID( Title::GAID_FOR_UPDATE ) + ); + + $page = new WikiPage( $loremIpsum ); + $page->doDeleteArticle( 'import test: delete page' ); + + $page = new WikiPage( $categoryLoremIpsum ); + $page->doDeleteArticle( 'import test: delete page' ); + } + + /** + * @depends testImportForImportSource + */ + public function testReImportForImportSource() { + + $this->doImport( $this->importStreamSource ); + + // ReImported title + $loremIpsum = Title::newFromText( 'Lorem ipsum' ); + + $this->assertSame( + $loremIpsum->getArticleID(), + $loremIpsum->getArticleID( Title::GAID_FOR_UPDATE ) + ); + + $categoryLoremIpsum = Title::newFromText( 'Category:Lorem ipsum' ); + + $this->assertSame( + $categoryLoremIpsum->getArticleID(), + $categoryLoremIpsum->getArticleID( Title::GAID_FOR_UPDATE ) + ); + } + + private function doImport( $importStreamSource ) { + + $importer = new WikiImporter( + $importStreamSource->value, + ConfigFactory::getDefaultInstance()->makeConfig( 'main' ) + ); + $importer->setDebug( true ); + + $reporter = new ImportReporter( + $importer, + false, + '', + false + ); + + $reporter->setContext( new RequestContext() ); + $reporter->open(); + $exception = false; + + try { + $importer->doImport(); + } catch ( Exception $e ) { + $exception = $e; + } + + $result = $reporter->close(); + + $this->assertFalse( + $exception + ); + + $this->assertTrue( + $result->isGood() + ); + } + +} diff --git a/tests/phpunit/includes/LinkFilterTest.php b/tests/phpunit/includes/LinkFilterTest.php index f2c9cb43..68081059 100644 --- a/tests/phpunit/includes/LinkFilterTest.php +++ b/tests/phpunit/includes/LinkFilterTest.php @@ -76,7 +76,7 @@ class LinkFilterTest extends MediaWikiLangTestCase { array( 'https://', '*.com', 'https://name:pass@secure.com/index.html' ), array( 'http://', 'name:pass@test.com', 'http://test.com' ), array( 'http://', 'test.com', 'http://name:pass@test.com' ), - array( 'http://', '*.test.com', 'http://a.b.c.test.com/dir/dir/file?a=6'), + array( 'http://', '*.test.com', 'http://a.b.c.test.com/dir/dir/file?a=6' ), array( null, 'http://*.test.com', 'http://www.test.com' ), array( 'mailto:', 'name@mail.test123.com', 'mailto:name@mail.test123.com' ), array( '', @@ -122,8 +122,8 @@ class LinkFilterTest extends MediaWikiLangTestCase { array( '', 'git://github.com/prwef/abc-def.git', 'git://github.com/prwef/abc-def.git' ), array( 'git://', 'github.com/', 'git://github.com/prwef/abc-def.git' ), array( 'git://', '*.github.com/', 'git://a.b.c.d.e.f.github.com/prwef/abc-def.git' ), - array( '', 'gopher://*.test.com/', 'gopher://gopher.test.com/0/v2/vstat'), - array( 'telnet://', '*.test.com', 'telnet://shell.test.com/~home/'), + array( '', 'gopher://*.test.com/', 'gopher://gopher.test.com/0/v2/vstat' ), + array( 'telnet://', '*.test.com', 'telnet://shell.test.com/~home/' ), // // The following only work in PHP >= 5.3.7, due to a bug in parse_url which eats @@ -243,10 +243,10 @@ class LinkFilterTest extends MediaWikiLangTestCase { array( 'http://*.test.*' ), array( 'http://*test.com' ), array( 'https://*' ), - array( '*://test.com'), + array( '*://test.com' ), array( 'mailto:name:pass@t*est.com' ), - array( 'http://*:888/'), - array( '*http://'), + array( 'http://*:888/' ), + array( '*http://' ), array( 'test.com/*/index' ), array( 'test.com/dir/index?arg=*' ), ); diff --git a/tests/phpunit/includes/LinkerTest.php b/tests/phpunit/includes/LinkerTest.php index 823c9330..a3efbb8d 100644 --- a/tests/phpunit/includes/LinkerTest.php +++ b/tests/phpunit/includes/LinkerTest.php @@ -93,12 +93,26 @@ class LinkerTest extends MediaWikiLangTestCase { * @covers Linker::formatAutocomments * @covers Linker::formatLinksInComment */ - public function testFormatComment( $expected, $comment, $title = false, $local = false ) { + public function testFormatComment( $expected, $comment, $title = false, $local = false, $wikiId = null ) { + $conf = new SiteConfiguration(); + $conf->settings = array( + 'wgServer' => array( + 'enwiki' => '//en.example.org', + 'dewiki' => '//de.example.org', + ), + 'wgArticlePath' => array( + 'enwiki' => '/w/$1', + 'dewiki' => '/w/$1', + ), + ); + $conf->suffixes = array( 'wiki' ); + $this->setMwGlobals( array( 'wgScript' => '/wiki/index.php', 'wgArticlePath' => '/wiki/$1', 'wgWellFormedXml' => true, 'wgCapitalLinks' => true, + 'wgConf' => $conf, ) ); if ( $title === false ) { @@ -108,11 +122,13 @@ class LinkerTest extends MediaWikiLangTestCase { $this->assertEquals( $expected, - Linker::formatComment( $comment, $title, $local ) + Linker::formatComment( $comment, $title, $local, $wikiId ) ); } - public static function provideCasesForFormatComment() { + public function provideCasesForFormatComment() { + $wikiId = 'enwiki'; // $wgConf has a fake entry for this + return array( // Linker::formatComment array( @@ -127,6 +143,10 @@ class LinkerTest extends MediaWikiLangTestCase { "'''not bolded'''", "'''not bolded'''", ), + array( + "try <script>evil</scipt> things", + "try <script>evil</scipt> things", + ), // Linker::formatAutocomments array( '<a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→</a><span dir="auto"><span class="autocomment">autocomment</span></span>', @@ -157,6 +177,14 @@ class LinkerTest extends MediaWikiLangTestCase { "/* autocomment containing /* */ T70361" ), array( + '<a href="/wiki/Special:BlankPage#autocomment_containing_.22quotes.22" title="Special:BlankPage">→</a><span dir="auto"><span class="autocomment">autocomment containing "quotes"</span></span>', + "/* autocomment containing \"quotes\" */" + ), + array( + '<a href="/wiki/Special:BlankPage#autocomment_containing_.3Cscript.3Etags.3C.2Fscript.3E" title="Special:BlankPage">→</a><span dir="auto"><span class="autocomment">autocomment containing <script>tags</script></span></span>', + "/* autocomment containing <script>tags</script> */" + ), + array( '<a href="#autocomment">→</a><span dir="auto"><span class="autocomment">autocomment</span></span>', "/* autocomment */", false, true @@ -166,6 +194,16 @@ class LinkerTest extends MediaWikiLangTestCase { "/* autocomment */", null ), + array( + '<a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→</a><span dir="auto"><span class="autocomment">autocomment</span></span>', + "/* autocomment */", + false, false + ), + array( + '<a class="external" rel="nofollow" href="//en.example.org/w/Special:BlankPage#autocomment">→</a><span dir="auto"><span class="autocomment">autocomment</span></span>', + "/* autocomment */", + false, false, $wikiId + ), // Linker::formatLinksInComment array( 'abc <a href="/wiki/index.php?title=Link&action=edit&redlink=1" class="new" title="Link (page does not exist)">link</a> def', @@ -191,6 +229,28 @@ class LinkerTest extends MediaWikiLangTestCase { 'abc <a href="/wiki/index.php?title=/subpage&action=edit&redlink=1" class="new" title="/subpage (page does not exist)">/subpage</a> def', "abc [[/subpage]] def", ), + array( + 'abc <a href="/wiki/index.php?title=%22evil!%22&action=edit&redlink=1" class="new" title=""evil!" (page does not exist)">"evil!"</a> def', + "abc [[\"evil!\"]] def", + ), + array( + 'abc [[<script>very evil</script>]] def', + "abc [[<script>very evil</script>]] def", + ), + array( + 'abc [[|]] def', + "abc [[|]] def", + ), + array( + 'abc <a href="/wiki/index.php?title=Link&action=edit&redlink=1" class="new" title="Link (page does not exist)">link</a> def', + "abc [[link]] def", + false, false + ), + array( + 'abc <a class="external" rel="nofollow" href="//en.example.org/w/Link">link</a> def', + "abc [[link]] def", + false, false, $wikiId + ) ); } diff --git a/tests/phpunit/includes/MediaWikiTest.php b/tests/phpunit/includes/MediaWikiTest.php new file mode 100644 index 00000000..e1962436 --- /dev/null +++ b/tests/phpunit/includes/MediaWikiTest.php @@ -0,0 +1,157 @@ +<?php + +class MediaWikiTest extends MediaWikiTestCase { + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( array( + 'wgServer' => 'http://example.org', + 'wgScriptPath' => '/w', + 'wgScript' => '/w/index.php', + 'wgArticlePath' => '/wiki/$1', + 'wgActionPaths' => array(), + ) ); + } + + public static function provideTryNormaliseRedirect() { + return array( + array( + // View: Canonical + 'url' => 'http://example.org/wiki/Foo_Bar', + 'query' => array(), + 'title' => 'Foo_Bar', + 'redirect' => false, + ), + array( + // View: Escaped title + 'url' => 'http://example.org/wiki/Foo%20Bar', + 'query' => array(), + 'title' => 'Foo_Bar', + 'redirect' => 'http://example.org/wiki/Foo_Bar', + ), + array( + // View: Script path + 'url' => 'http://example.org/w/index.php?title=Foo_Bar', + 'query' => array( 'title' => 'Foo_Bar' ), + 'title' => 'Foo_Bar', + 'redirect' => 'http://example.org/wiki/Foo_Bar', + ), + array( + // View: Script path with implicit title from page id + 'url' => 'http://example.org/w/index.php?curid=123', + 'query' => array( 'curid' => '123' ), + 'title' => 'Foo_Bar', + 'redirect' => false, + ), + array( + // View: Script path with implicit title from revision id + 'url' => 'http://example.org/w/index.php?oldid=123', + 'query' => array( 'oldid' => '123' ), + 'title' => 'Foo_Bar', + 'redirect' => false, + ), + array( + // View: Script path without title + 'url' => 'http://example.org/w/index.php', + 'query' => array(), + 'title' => 'Main_Page', + 'redirect' => 'http://example.org/wiki/Main_Page', + ), + array( + // View: Script path with empty title + 'url' => 'http://example.org/w/index.php?title=', + 'query' => array( 'title' => '' ), + 'title' => 'Main_Page', + 'redirect' => 'http://example.org/wiki/Main_Page', + ), + array( + // View: Index with escaped title + 'url' => 'http://example.org/w/index.php?title=Foo%20Bar', + 'query' => array( 'title' => 'Foo Bar' ), + 'title' => 'Foo_Bar', + 'redirect' => 'http://example.org/wiki/Foo_Bar', + ), + array( + // View: Script path with escaped title + 'url' => 'http://example.org/w/?title=Foo_Bar', + 'query' => array( 'title' => 'Foo_Bar' ), + 'title' => 'Foo_Bar', + 'redirect' => 'http://example.org/wiki/Foo_Bar', + ), + array( + // View: Root path with escaped title + 'url' => 'http://example.org/?title=Foo_Bar', + 'query' => array( 'title' => 'Foo_Bar' ), + 'title' => 'Foo_Bar', + 'redirect' => 'http://example.org/wiki/Foo_Bar', + ), + array( + // View: Canonical with redundant query + 'url' => 'http://example.org/wiki/Foo_Bar?action=view', + 'query' => array( 'action' => 'view' ), + 'title' => 'Foo_Bar', + 'redirect' => 'http://example.org/wiki/Foo_Bar', + ), + array( + // Edit: Canonical view url with action query + 'url' => 'http://example.org/wiki/Foo_Bar?action=edit', + 'query' => array( 'action' => 'edit' ), + 'title' => 'Foo_Bar', + 'redirect' => false, + ), + array( + // View: Index with action query + 'url' => 'http://example.org/w/index.php?title=Foo_Bar&action=view', + 'query' => array( 'title' => 'Foo_Bar', 'action' => 'view' ), + 'title' => 'Foo_Bar', + 'redirect' => 'http://example.org/wiki/Foo_Bar', + ), + array( + // Edit: Index with action query + 'url' => 'http://example.org/w/index.php?title=Foo_Bar&action=edit', + 'query' => array( 'title' => 'Foo_Bar', 'action' => 'edit' ), + 'title' => 'Foo_Bar', + 'redirect' => false, + ), + ); + } + + /** + * @dataProvider provideTryNormaliseRedirect + * @covers MediaWiki::tryNormaliseRedirect + */ + public function testTryNormaliseRedirect( $url, $query, $title, $expectedRedirect = false ) { + // Set SERVER because interpolateTitle() doesn't use getRequestURL(), + // whereas tryNormaliseRedirect does(). + $_SERVER['REQUEST_URI'] = $url; + + $req = new FauxRequest( $query ); + $req->setRequestURL( $url ); + // This adds a virtual 'title' query parameter. Normally called from Setup.php + $req->interpolateTitle(); + + $titleObj = Title::newFromText( $title ); + + // Set global context since some involved code paths don't yet have context + $context = RequestContext::getMain(); + $context->setRequest( $req ); + $context->setTitle( $titleObj ); + + $mw = new MediaWiki( $context ); + + $method = new ReflectionMethod( $mw, 'tryNormaliseRedirect' ); + $method->setAccessible( true ); + $ret = $method->invoke( $mw, $titleObj ); + + $this->assertEquals( + $expectedRedirect !== false, + $ret, + 'Return true only when redirecting' + ); + + $this->assertEquals( + $expectedRedirect ?: '', + $context->getOutput()->getRedirect() + ); + } +} diff --git a/tests/phpunit/includes/MessageTest.php b/tests/phpunit/includes/MessageTest.php index 99ec2e42..9c953a6d 100644 --- a/tests/phpunit/includes/MessageTest.php +++ b/tests/phpunit/includes/MessageTest.php @@ -21,6 +21,17 @@ class MessageTest extends MediaWikiLangTestCase { $this->assertEquals( $key, $message->getKey() ); $this->assertEquals( $params, $message->getParams() ); $this->assertEquals( $expectedLang, $message->getLanguage() ); + + $messageSpecifier = $this->getMockForAbstractClass( 'MessageSpecifier' ); + $messageSpecifier->expects( $this->any() ) + ->method( 'getKey' )->will( $this->returnValue( $key ) ); + $messageSpecifier->expects( $this->any() ) + ->method( 'getParams' )->will( $this->returnValue( $params ) ); + $message = new Message( $messageSpecifier, array(), $language ); + + $this->assertEquals( $key, $message->getKey() ); + $this->assertEquals( $params, $message->getParams() ); + $this->assertEquals( $expectedLang, $message->getLanguage() ); } public static function provideConstructor() { @@ -548,4 +559,26 @@ class MessageTest extends MediaWikiLangTestCase { public function testInLanguageThrows() { wfMessage( 'foo' )->inLanguage( 123 ); } + + /** + * @covers Message::serialize + * @covers Message::unserialize + */ + public function testSerialization() { + $msg = new Message( 'parentheses' ); + $msg->rawParams( '<a>foo</a>' ); + $msg->title( Title::newFromText( 'Testing' ) ); + $this->assertEquals( '(<a>foo</a>)', $msg->parse(), 'Sanity check' ); + $msg = unserialize( serialize( $msg ) ); + $this->assertEquals( '(<a>foo</a>)', $msg->parse() ); + $title = TestingAccessWrapper::newFromObject( $msg )->title; + $this->assertInstanceOf( 'Title', $title ); + $this->assertEquals( 'Testing', $title->getFullText() ); + + $msg = new Message( 'mainpage' ); + $msg->inLanguage( 'de' ); + $this->assertEquals( 'Hauptseite', $msg->plain(), 'Sanity check' ); + $msg = unserialize( serialize( $msg ) ); + $this->assertEquals( 'Hauptseite', $msg->plain() ); + } } diff --git a/tests/phpunit/includes/MimeMagicTest.php b/tests/phpunit/includes/MimeMagicTest.php index 742d3827..3c45f305 100644 --- a/tests/phpunit/includes/MimeMagicTest.php +++ b/tests/phpunit/includes/MimeMagicTest.php @@ -1,5 +1,5 @@ <?php -class MimeMagicTest extends MediaWikiTestCase { +class MimeMagicTest extends PHPUnit_Framework_TestCase { /** @var MimeMagic */ private $mimeMagic; diff --git a/tests/phpunit/includes/MovePageTest.php b/tests/phpunit/includes/MovePageTest.php index 9501e452..0ef2fa63 100644 --- a/tests/phpunit/includes/MovePageTest.php +++ b/tests/phpunit/includes/MovePageTest.php @@ -57,7 +57,7 @@ class MovePageTest extends MediaWikiTestCase { WikiPage::factory( $oldTitle )->getRevision() ); $this->assertNotNull( - WikiPage::factory( $newTitle)->getRevision() + WikiPage::factory( $newTitle )->getRevision() ); } } diff --git a/tests/phpunit/includes/OutputPageTest.php b/tests/phpunit/includes/OutputPageTest.php index 6c6d95ee..f0d905e5 100644 --- a/tests/phpunit/includes/OutputPageTest.php +++ b/tests/phpunit/includes/OutputPageTest.php @@ -141,53 +141,36 @@ class OutputPageTest extends MediaWikiTestCase { // Load module script only array( array( 'test.foo', ResourceLoaderModule::TYPE_SCRIPTS ), - '<script>if(window.mw){ -document.write("\u003Cscript src=\"http://127.0.0.1:8080/w/load.php?debug=false\u0026amp;lang=en\u0026amp;modules=test.foo\u0026amp;only=scripts\u0026amp;skin=fallback\u0026amp;*\"\u003E\u003C/script\u003E"); -}</script> -' + "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n" + . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.foo\u0026only=scripts\u0026skin=fallback");' + . "\n} );</script>" ), array( // Don't condition wrap raw modules (like the startup module) array( 'test.raw', ResourceLoaderModule::TYPE_SCRIPTS ), - '<script src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.raw&only=scripts&skin=fallback&*"></script> -' + '<script async src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.raw&only=scripts&skin=fallback"></script>' ), // Load module styles only // This also tests the order the modules are put into the url array( array( array( 'test.baz', 'test.foo', 'test.bar' ), ResourceLoaderModule::TYPE_STYLES ), - '<link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.bar%2Cbaz%2Cfoo&only=styles&skin=fallback&*"> -' + + '<link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.bar%2Cbaz%2Cfoo&only=styles&skin=fallback">' ), // Load private module (only=scripts) array( array( 'test.quux', ResourceLoaderModule::TYPE_SCRIPTS ), - '<script>if(window.mw){ -mw.test.baz({token:123});mw.loader.state({"test.quux":"ready"}); - -}</script> -' + "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n" + . "mw.test.baz({token:123});mw.loader.state({\"test.quux\":\"ready\"});\n" + . "} );</script>" ), // Load private module (combined) array( array( 'test.quux', ResourceLoaderModule::TYPE_COMBINED ), - '<script>if(window.mw){ -mw.loader.implement("test.quux",function($,jQuery){mw.test.baz({token:123});},{"css":[".mw-icon{transition:none}\n"]}); - -}</script> -' - ), - // Load module script with ESI - array( - array( 'test.foo', ResourceLoaderModule::TYPE_SCRIPTS, true ), - '<script><esi:include src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.foo&only=scripts&skin=fallback&*" /></script> -' - ), - // Load module styles with ESI - array( - array( 'test.foo', ResourceLoaderModule::TYPE_STYLES, true ), - '<style><esi:include src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.foo&only=styles&skin=fallback&*" /></style> -', + "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n" + . "mw.loader.implement(\"test.quux\",function($,jQuery){" + . "mw.test.baz({token:123});},{\"css\":[\".mw-icon{transition:none}" + . "\"]});\n} );</script>" ), // Load no modules array( @@ -197,19 +180,17 @@ mw.loader.implement("test.quux",function($,jQuery){mw.test.baz({token:123});},{" // noscript group array( array( 'test.noscript', ResourceLoaderModule::TYPE_STYLES ), - '<noscript><link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.noscript&only=styles&skin=fallback&*"></noscript> -' + '<noscript><link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.noscript&only=styles&skin=fallback"></noscript>' ), // Load two modules in separate groups array( array( array( 'test.group.foo', 'test.group.bar' ), ResourceLoaderModule::TYPE_COMBINED ), - '<script>if(window.mw){ -document.write("\u003Cscript src=\"http://127.0.0.1:8080/w/load.php?debug=false\u0026amp;lang=en\u0026amp;modules=test.group.bar\u0026amp;skin=fallback\u0026amp;*\"\u003E\u003C/script\u003E"); -}</script> -<script>if(window.mw){ -document.write("\u003Cscript src=\"http://127.0.0.1:8080/w/load.php?debug=false\u0026amp;lang=en\u0026amp;modules=test.group.foo\u0026amp;skin=fallback\u0026amp;*\"\u003E\u003C/script\u003E"); -}</script> -' + "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n" + . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.group.bar\u0026skin=fallback");' + . "\n} );</script>\n" + . "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n" + . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.group.foo\u0026skin=fallback");' + . "\n} );</script>" ), ); } @@ -226,7 +207,6 @@ document.write("\u003Cscript src=\"http://127.0.0.1:8080/w/load.php?debug=false\ public function testMakeResourceLoaderLink( $args, $expectedHtml ) { $this->setMwGlobals( array( 'wgResourceLoaderDebug' => false, - 'wgResourceLoaderUseESI' => true, 'wgLoadScript' => 'http://127.0.0.1:8080/w/load.php', // Affects whether CDATA is inserted 'wgWellFormedXml' => false, @@ -244,63 +224,141 @@ document.write("\u003Cscript src=\"http://127.0.0.1:8080/w/load.php?debug=false\ 'test.foo' => new ResourceLoaderTestModule( array( 'script' => 'mw.test.foo( { a: true } );', 'styles' => '.mw-test-foo { content: "style"; }', - )), + ) ), 'test.bar' => new ResourceLoaderTestModule( array( 'script' => 'mw.test.bar( { a: true } );', 'styles' => '.mw-test-bar { content: "style"; }', - )), + ) ), 'test.baz' => new ResourceLoaderTestModule( array( 'script' => 'mw.test.baz( { a: true } );', 'styles' => '.mw-test-baz { content: "style"; }', - )), + ) ), 'test.quux' => new ResourceLoaderTestModule( array( 'script' => 'mw.test.baz( { token: 123 } );', 'styles' => '/* pref-animate=off */ .mw-icon { transition: none; }', 'group' => 'private', - )), + ) ), 'test.raw' => new ResourceLoaderTestModule( array( 'script' => 'mw.test.baz( { token: 123 } );', 'isRaw' => true, - )), + ) ), 'test.noscript' => new ResourceLoaderTestModule( array( 'styles' => '.mw-test-noscript { content: "style"; }', 'group' => 'noscript', - )), + ) ), 'test.group.bar' => new ResourceLoaderTestModule( array( 'styles' => '.mw-group-bar { content: "style"; }', 'group' => 'bar', - )), + ) ), 'test.group.foo' => new ResourceLoaderTestModule( array( 'styles' => '.mw-group-foo { content: "style"; }', 'group' => 'foo', - )), + ) ), ) ); $links = $method->invokeArgs( $out, $args ); - // Strip comments to avoid variation due to wgDBname in WikiID and cache key - $actualHtml = preg_replace( '#/\*[^*]+\*/#', '', $links['html'] ); + $actualHtml = implode( "\n", $links['html'] ); $this->assertEquals( $expectedHtml, $actualHtml ); } + + /** + * @dataProvider provideVaryHeaders + * @covers OutputPage::addVaryHeader + * @covers OutputPage::getVaryHeader + * @covers OutputPage::getXVO + */ + public function testVaryHeaders( $calls, $vary, $xvo ) { + // get rid of default Vary fields + $outputPage = $this->getMockBuilder( 'OutputPage' ) + ->setConstructorArgs( array( new RequestContext() ) ) + ->setMethods( array( 'getCacheVaryCookies' ) ) + ->getMock(); + $outputPage->expects( $this->any() ) + ->method( 'getCacheVaryCookies' ) + ->will( $this->returnValue( array() ) ); + TestingAccessWrapper::newFromObject( $outputPage )->mVaryHeader = array(); + + foreach ( $calls as $call ) { + call_user_func_array( array( $outputPage, 'addVaryHeader' ), $call ); + } + $this->assertEquals( $vary, $outputPage->getVaryHeader(), 'Vary:' ); + $this->assertEquals( $xvo, $outputPage->getXVO(), 'X-Vary-Options:' ); + } + + public function provideVaryHeaders() { + // note: getXVO() automatically adds Vary: Cookie + return array( + array( // single header + array( + array( 'Cookie' ), + ), + 'Vary: Cookie', + 'X-Vary-Options: Cookie', + ), + array( // non-unique headers + array( + array( 'Cookie' ), + array( 'Accept-Language' ), + array( 'Cookie' ), + ), + 'Vary: Cookie, Accept-Language', + 'X-Vary-Options: Cookie,Accept-Language', + ), + array( // two headers with single options + array( + array( 'Cookie', array( 'string-contains=phpsessid' ) ), + array( 'Accept-Language', array( 'string-contains=en' ) ), + ), + 'Vary: Cookie, Accept-Language', + 'X-Vary-Options: Cookie;string-contains=phpsessid,Accept-Language;string-contains=en', + ), + array( // one header with multiple options + array( + array( 'Cookie', array( 'string-contains=phpsessid', 'string-contains=userId' ) ), + ), + 'Vary: Cookie', + 'X-Vary-Options: Cookie;string-contains=phpsessid;string-contains=userId', + ), + array( // Duplicate option + array( + array( 'Cookie', array( 'string-contains=phpsessid' ) ), + array( 'Cookie', array( 'string-contains=phpsessid' ) ), + array( 'Accept-Language', array( 'string-contains=en', 'string-contains=en' ) ), + + + ), + 'Vary: Cookie, Accept-Language', + 'X-Vary-Options: Cookie;string-contains=phpsessid,Accept-Language;string-contains=en', + ), + array( // Same header, different options + array( + array( 'Cookie', array( 'string-contains=phpsessid' ) ), + array( 'Cookie', array( 'string-contains=userId' ) ), + ), + 'Vary: Cookie', + 'X-Vary-Options: Cookie;string-contains=phpsessid;string-contains=userId', + ), + ); + } } /** * MessageBlobStore that doesn't do anything */ class NullMessageBlobStore extends MessageBlobStore { - public function get ( ResourceLoader $resourceLoader, $modules, $lang ) { + public function get( ResourceLoader $resourceLoader, $modules, $lang ) { return array(); } - public function insertMessageBlob ( $name, ResourceLoaderModule $module, $lang ) { + public function insertMessageBlob( $name, ResourceLoaderModule $module, $lang ) { return false; } - public function updateModule ( $name, ResourceLoaderModule $module, $lang ) { + public function updateModule( $name, ResourceLoaderModule $module, $lang ) { return; } - public function updateMessage ( $key ) { + public function updateMessage( $key ) { } public function clear() { } } - diff --git a/tests/phpunit/includes/PrefixSearchTest.php b/tests/phpunit/includes/PrefixSearchTest.php index d63541b7..afd10e9a 100644 --- a/tests/phpunit/includes/PrefixSearchTest.php +++ b/tests/phpunit/includes/PrefixSearchTest.php @@ -6,6 +6,11 @@ class PrefixSearchTest extends MediaWikiLangTestCase { public function addDBData() { + if ( !$this->isWikitextNS( NS_MAIN ) ) { + // tests are skipped if NS_MAIN is not wikitext + return; + } + $this->insertPage( 'Sandbox' ); $this->insertPage( 'Bar' ); $this->insertPage( 'Example' ); diff --git a/tests/phpunit/includes/SanitizerTest.php b/tests/phpunit/includes/SanitizerTest.php index c615c460..d3dc512b 100644 --- a/tests/phpunit/includes/SanitizerTest.php +++ b/tests/phpunit/includes/SanitizerTest.php @@ -6,6 +6,11 @@ */ class SanitizerTest extends MediaWikiTestCase { + protected function tearDown() { + MWTidy::destroySingleton(); + parent::tearDown(); + } + /** * @covers Sanitizer::decodeCharReferences */ @@ -93,9 +98,7 @@ class SanitizerTest extends MediaWikiTestCase { * @param bool $escaped Whether sanitizer let the tag in or escape it (ie: '<video>') */ public function testRemovehtmltagsOnHtml5Tags( $tag, $escaped ) { - $this->setMwGlobals( array( - 'wgUseTidy' => false - ) ); + MWTidy::setInstance( false ); if ( $escaped ) { $this->assertEquals( "<$tag>", @@ -157,7 +160,7 @@ class SanitizerTest extends MediaWikiTestCase { * @covers Sanitizer::removeHTMLtags */ public function testRemoveHTMLtags( $input, $output, $msg = null ) { - $GLOBALS['wgUseTidy'] = false; + MWTidy::setInstance( false ); $this->assertEquals( $output, Sanitizer::removeHTMLtags( $input ), $msg ); } @@ -360,5 +363,4 @@ class SanitizerTest extends MediaWikiTestCase { array( '<script>foo</script>', '<script>foo</script>' ), ); } - } diff --git a/tests/phpunit/includes/SanitizerValidateEmailTest.php b/tests/phpunit/includes/SanitizerValidateEmailTest.php index 14911f04..f47e74e2 100644 --- a/tests/phpunit/includes/SanitizerValidateEmailTest.php +++ b/tests/phpunit/includes/SanitizerValidateEmailTest.php @@ -5,7 +5,7 @@ * @todo all test methods in this class should be refactored and... * use a single test method and a single data provider... */ -class SanitizerValidateEmailTest extends MediaWikiTestCase { +class SanitizerValidateEmailTest extends PHPUnit_Framework_TestCase { private function checkEmail( $addr, $expected = true, $msg = '' ) { if ( $msg == '' ) { diff --git a/tests/phpunit/includes/StatusTest.php b/tests/phpunit/includes/StatusTest.php index c013f4fc..291ed315 100644 --- a/tests/phpunit/includes/StatusTest.php +++ b/tests/phpunit/includes/StatusTest.php @@ -372,7 +372,7 @@ class StatusTest extends MediaWikiLangTestCase { ); $status = new Status(); - $status->warning( new Message( 'fooBar!', array( 'foo', 'bar' ) ) ); + $status->warning( new Message( 'fooBar!', array( 'foo', 'bar' ) ) ); $testCases['1MessageWarning'] = array( $status, "<fooBar!>", @@ -449,7 +449,7 @@ class StatusTest extends MediaWikiLangTestCase { // ); $status = new Status(); - $status->warning( new Message( 'fooBar!', array( 'foo', 'bar' ) ) ); + $status->warning( new Message( 'fooBar!', array( 'foo', 'bar' ) ) ); $testCases['1MessageWarning'] = array( $status, array( 'foo', 'bar' ), diff --git a/tests/phpunit/includes/TemplateParserTest.php b/tests/phpunit/includes/TemplateParserTest.php index 81854ff3..3b37f4a4 100644 --- a/tests/phpunit/includes/TemplateParserTest.php +++ b/tests/phpunit/includes/TemplateParserTest.php @@ -57,7 +57,20 @@ class TemplateParserTest extends MediaWikiTestCase { array(), false, 'RuntimeException', - ) + ), + array( + 'has_partial', + array( + 'planet' => 'world', + ), + "Partial hello world!\n in here\n", + ), + array( + 'bad_partial', + array(), + false, + 'Exception', + ), ); } } diff --git a/tests/phpunit/includes/TestingAccessWrapper.php b/tests/phpunit/includes/TestingAccessWrapper.php index 84c0f9b5..63d89719 100644 --- a/tests/phpunit/includes/TestingAccessWrapper.php +++ b/tests/phpunit/includes/TestingAccessWrapper.php @@ -34,16 +34,42 @@ class TestingAccessWrapper { return $methodReflection->invokeArgs( $this->object, $args ); } - public function __set( $name, $value ) { + /** + * ReflectionClass::getProperty() fails if the private property is defined + * in a parent class. This works more like ReflectionClass::getMethod(). + */ + private function getProperty( $name ) { $classReflection = new ReflectionClass( $this->object ); - $propertyReflection = $classReflection->getProperty( $name ); + try { + return $classReflection->getProperty( $name ); + } catch ( ReflectionException $ex ) { + while ( true ) { + $classReflection = $classReflection->getParentClass(); + if ( !$classReflection ) { + throw $ex; + } + try { + $propertyReflection = $classReflection->getProperty( $name ); + } catch ( ReflectionException $ex2 ) { + continue; + } + if ( $propertyReflection->isPrivate() ) { + return $propertyReflection; + } else { + throw $ex; + } + } + } + } + + public function __set( $name, $value ) { + $propertyReflection = $this->getProperty( $name ); $propertyReflection->setAccessible( true ); $propertyReflection->setValue( $this->object, $value ); } public function __get( $name ) { - $classReflection = new ReflectionClass( $this->object ); - $propertyReflection = $classReflection->getProperty( $name ); + $propertyReflection = $this->getProperty( $name ); $propertyReflection->setAccessible( true ); return $propertyReflection->getValue( $this->object ); } diff --git a/tests/phpunit/includes/TestingAccessWrapperTest.php b/tests/phpunit/includes/TestingAccessWrapperTest.php index 7e5b91a1..fc54afae 100644 --- a/tests/phpunit/includes/TestingAccessWrapperTest.php +++ b/tests/phpunit/includes/TestingAccessWrapperTest.php @@ -14,18 +14,36 @@ class TestingAccessWrapperTest extends MediaWikiTestCase { function testGetProperty() { $this->assertSame( 1, $this->wrapped->property ); + $this->assertSame( 42, $this->wrapped->privateProperty ); + $this->assertSame( 9000, $this->wrapped->privateParentProperty ); } function testSetProperty() { $this->wrapped->property = 10; $this->assertSame( 10, $this->wrapped->property ); $this->assertSame( 10, $this->raw->getProperty() ); + + $this->wrapped->privateProperty = 11; + $this->assertSame( 11, $this->wrapped->privateProperty ); + $this->assertSame( 11, $this->raw->getPrivateProperty() ); + + $this->wrapped->privateParentProperty = 12; + $this->assertSame( 12, $this->wrapped->privateParentProperty ); + $this->assertSame( 12, $this->raw->getPrivateParentProperty() ); } function testCallMethod() { $this->wrapped->incrementPropertyValue(); $this->assertSame( 2, $this->wrapped->property ); $this->assertSame( 2, $this->raw->getProperty() ); + + $this->wrapped->incrementPrivatePropertyValue(); + $this->assertSame( 43, $this->wrapped->privateProperty ); + $this->assertSame( 43, $this->raw->getPrivateProperty() ); + + $this->wrapped->incrementPrivateParentPropertyValue(); + $this->assertSame( 9001, $this->wrapped->privateParentProperty ); + $this->assertSame( 9001, $this->raw->getPrivateParentProperty() ); } function testCallMethodTwoArgs() { diff --git a/tests/phpunit/includes/TitleArrayFromResultTest.php b/tests/phpunit/includes/TitleArrayFromResultTest.php index 0f7069ae..6654a5b6 100644 --- a/tests/phpunit/includes/TitleArrayFromResultTest.php +++ b/tests/phpunit/includes/TitleArrayFromResultTest.php @@ -4,7 +4,7 @@ * @author Adam Shorland * @covers TitleArrayFromResult */ -class TitleArrayFromResultTest extends MediaWikiTestCase { +class TitleArrayFromResultTest extends PHPUnit_Framework_TestCase { private function getMockResultWrapper( $row = null, $numRows = 1 ) { $resultWrapper = $this->getMockBuilder( 'ResultWrapper' ) diff --git a/tests/phpunit/includes/TitlePermissionTest.php b/tests/phpunit/includes/TitlePermissionTest.php index 022c7d53..f588ed63 100644 --- a/tests/phpunit/includes/TitlePermissionTest.php +++ b/tests/phpunit/includes/TitlePermissionTest.php @@ -664,7 +664,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->setTitle( NS_MAIN, "test page" ); $this->title->mTitleProtection['permission'] = ''; $this->title->mTitleProtection['user'] = $this->user->getID(); - $this->title->mTitleProtection['expiry'] = wfGetDB( DB_SLAVE )->getInfinity(); + $this->title->mTitleProtection['expiry'] = 'infinity'; $this->title->mTitleProtection['reason'] = 'test'; $this->title->mCascadeRestriction = false; @@ -753,8 +753,14 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $prev = time(); $now = time() + 120; $this->user->mBlockedby = $this->user->getId(); - $this->user->mBlock = new Block( '127.0.8.1', 0, $this->user->getId(), - 'no reason given', $prev + 3600, 1, 0 ); + $this->user->mBlock = new Block( array( + 'address' => '127.0.8.1', + 'by' => $this->user->getId(), + 'reason' => 'no reason given', + 'timestamp' => $prev + 3600, + 'auto' => true, + 'expiry' => 0 + ) ); $this->user->mBlock->mTimestamp = 0; $this->assertEquals( array( array( 'autoblockedtext', '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1', @@ -770,8 +776,14 @@ class TitlePermissionTest extends MediaWikiLangTestCase { global $wgLocalTZoffset; $wgLocalTZoffset = -60; $this->user->mBlockedby = $this->user->getName(); - $this->user->mBlock = new Block( '127.0.8.1', 0, $this->user->getId(), - 'no reason given', $now, 0, 10 ); + $this->user->mBlock = new Block( array( + 'address' => '127.0.8.1', + 'by' => $this->user->getId(), + 'reason' => 'no reason given', + 'timestamp' => $now, + 'auto' => false, + 'expiry' => 10, + ) ); $this->assertEquals( array( array( 'blockedtext', '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1', 'Useruser', null, '23:00, 31 December 1969', '127.0.8.1', diff --git a/tests/phpunit/includes/TitleTest.php b/tests/phpunit/includes/TitleTest.php index d55f958b..a2c6f23d 100644 --- a/tests/phpunit/includes/TitleTest.php +++ b/tests/phpunit/includes/TitleTest.php @@ -1,6 +1,7 @@ <?php /** + * @group Database * @group Title */ class TitleTest extends MediaWikiTestCase { @@ -79,22 +80,22 @@ class TitleTest extends MediaWikiTestCase { public static function provideInvalidSecureAndSplit() { return array( - array( '' ), - array( ':' ), - array( '__ __' ), - array( ' __ ' ), + array( '', 'title-invalid-empty' ), + array( ':', 'title-invalid-empty' ), + array( '__ __', 'title-invalid-empty' ), + array( ' __ ', 'title-invalid-empty' ), // Bad characters forbidden regardless of wgLegalTitleChars - array( 'A [ B' ), - array( 'A ] B' ), - array( 'A { B' ), - array( 'A } B' ), - array( 'A < B' ), - array( 'A > B' ), - array( 'A | B' ), + array( 'A [ B', 'title-invalid-characters' ), + array( 'A ] B', 'title-invalid-characters' ), + array( 'A { B', 'title-invalid-characters' ), + array( 'A } B', 'title-invalid-characters' ), + array( 'A < B', 'title-invalid-characters' ), + array( 'A > B', 'title-invalid-characters' ), + array( 'A | B', 'title-invalid-characters' ), // URL encoding - array( 'A%20B' ), - array( 'A%23B' ), - array( 'A%2523B' ), + array( 'A%20B', 'title-invalid-characters' ), + array( 'A%23B', 'title-invalid-characters' ), + array( 'A%2523B', 'title-invalid-characters' ), // XML/HTML character entity references // Note: Commented out because they are not marked invalid by the PHP test as // Title::newFromText runs Sanitizer::decodeCharReferencesAndNormalize first. @@ -102,29 +103,30 @@ class TitleTest extends MediaWikiTestCase { //'A é B', //'A é B', // Subject of NS_TALK does not roundtrip to NS_MAIN - array( 'Talk:File:Example.svg' ), + array( 'Talk:File:Example.svg', 'title-invalid-talk-namespace' ), // Directory navigation - array( '.' ), - array( '..' ), - array( './Sandbox' ), - array( '../Sandbox' ), - array( 'Foo/./Sandbox' ), - array( 'Foo/../Sandbox' ), - array( 'Sandbox/.' ), - array( 'Sandbox/..' ), + array( '.', 'title-invalid-relative' ), + array( '..', 'title-invalid-relative' ), + array( './Sandbox', 'title-invalid-relative' ), + array( '../Sandbox', 'title-invalid-relative' ), + array( 'Foo/./Sandbox', 'title-invalid-relative' ), + array( 'Foo/../Sandbox', 'title-invalid-relative' ), + array( 'Sandbox/.', 'title-invalid-relative' ), + array( 'Sandbox/..', 'title-invalid-relative' ), // Tilde - array( 'A ~~~ Name' ), - array( 'A ~~~~ Signature' ), - array( 'A ~~~~~ Timestamp' ), - array( str_repeat( 'x', 256 ) ), + array( 'A ~~~ Name', 'title-invalid-magic-tilde' ), + array( 'A ~~~~ Signature', 'title-invalid-magic-tilde' ), + array( 'A ~~~~~ Timestamp', 'title-invalid-magic-tilde' ), + // Length + array( str_repeat( 'x', 256 ), 'title-invalid-too-long' ), // Namespace prefix without actual title - array( 'Talk:' ), - array( 'Talk:#' ), - array( 'Category: ' ), - array( 'Category: #bar' ), + array( 'Talk:', 'title-invalid-empty' ), + array( 'Talk:#', 'title-invalid-empty' ), + array( 'Category: ', 'title-invalid-empty' ), + array( 'Category: #bar', 'title-invalid-empty' ), // interwiki prefix - array( 'localtestiw: Talk: # anchor' ), - array( 'localtestiw: Talk:' ) + array( 'localtestiw: Talk: # anchor', 'title-invalid-empty' ), + array( 'localtestiw: Talk:', 'title-invalid-empty' ) ); } @@ -143,7 +145,7 @@ class TitleTest extends MediaWikiTestCase { } ) ) - )); + ) ); } /** @@ -163,9 +165,14 @@ class TitleTest extends MediaWikiTestCase { * @dataProvider provideInvalidSecureAndSplit * @note This mainly tests MediaWikiTitleCodec::parseTitle(). */ - public function testSecureAndSplitInvalid( $text ) { + public function testSecureAndSplitInvalid( $text, $expectedErrorMessage ) { $this->secureAndSplitGlobals(); - $this->assertNull( Title::newFromText( $text ), "Invalid: $text" ); + try { + Title::newFromTextThrow( $text ); // should throw + $this->assertTrue( false, "Invalid: $text" ); + } catch ( MalformedTitleException $ex ) { + $this->assertEquals( $expectedErrorMessage, $ex->getErrorMessage(), "Invalid: $text" ); + } } public static function provideConvertByteClassToUnicodeClass() { @@ -631,4 +638,26 @@ class TitleTest extends MediaWikiTestCase { $title = Title::makeTitle( NS_MAIN, 'Interwiki link', '', 'externalwiki' ); $this->assertTrue( $title->isAlwaysKnown() ); } + + /** + * @covers Title::exists + */ + public function testExists() { + $title = Title::makeTitle( NS_PROJECT, 'New page' ); + $linkCache = LinkCache::singleton(); + + $article = new Article( $title ); + $page = $article->getPage(); + $page->doEditContent( new WikitextContent( 'Some [[link]]' ), 'summary' ); + + // Tell Title it doesn't know whether it exists + $title->mArticleID = -1; + + // Tell the link cache it doesn't exists when it really does + $linkCache->clearLink( $title ); + $linkCache->addBadLinkObj( $title ); + + $this->assertEquals( false, $title->exists(), 'exists() should rely on link cache unless GAID_FOR_UPDATE is used' ); + $this->assertEquals( true, $title->exists( Title::GAID_FOR_UPDATE ), 'exists() should re-query database when GAID_FOR_UPDATE is used' ); + } } diff --git a/tests/phpunit/includes/UserTest.php b/tests/phpunit/includes/UserTest.php index b74a7ead..77132bbb 100644 --- a/tests/phpunit/includes/UserTest.php +++ b/tests/phpunit/includes/UserTest.php @@ -307,9 +307,30 @@ class UserTest extends MediaWikiTestCase { */ public function testCheckPasswordValidity() { $this->setMwGlobals( array( - 'wgMinimalPasswordLength' => 6, - 'wgMaximalPasswordLength' => 30, + 'wgPasswordPolicy' => array( + 'policies' => array( + 'sysop' => array( + 'MinimalPasswordLength' => 8, + 'MinimumPasswordLengthToLogin' => 1, + 'PasswordCannotMatchUsername' => 1, + ), + 'default' => array( + 'MinimalPasswordLength' => 6, + 'PasswordCannotMatchUsername' => true, + 'PasswordCannotMatchBlacklist' => true, + 'MaximalPasswordLength' => 30, + ), + ), + 'checks' => array( + 'MinimalPasswordLength' => 'PasswordPolicyChecks::checkMinimalPasswordLength', + 'MinimumPasswordLengthToLogin' => 'PasswordPolicyChecks::checkMinimumPasswordLengthToLogin', + 'PasswordCannotMatchUsername' => 'PasswordPolicyChecks::checkPasswordCannotMatchUsername', + 'PasswordCannotMatchBlacklist' => 'PasswordPolicyChecks::checkPasswordCannotMatchBlacklist', + 'MaximalPasswordLength' => 'PasswordPolicyChecks::checkMaximalPasswordLength', + ), + ), ) ); + $user = User::newFromName( 'Useruser' ); // Sanity $this->assertTrue( $user->isValidPassword( 'Password1234' ) ); @@ -425,4 +446,109 @@ class UserTest extends MediaWikiTestCase { $this->assertFalse( $user->isLoggedIn() ); $this->assertTrue( $user->isAnon() ); } + + /** + * @covers User::checkAndSetTouched + */ + public function testCheckAndSetTouched() { + $user = TestingAccessWrapper::newFromObject( User::newFromName( 'UTSysop' ) ); + $this->assertTrue( $user->isLoggedIn() ); + + $touched = $user->getDBTouched(); + $this->assertTrue( + $user->checkAndSetTouched(), "checkAndSetTouched() succeded" ); + $this->assertGreaterThan( + $touched, $user->getDBTouched(), "user_touched increased with casOnTouched()" ); + + $touched = $user->getDBTouched(); + $this->assertTrue( + $user->checkAndSetTouched(), "checkAndSetTouched() succeded #2" ); + $this->assertGreaterThan( + $touched, $user->getDBTouched(), "user_touched increased with casOnTouched() #2" ); + } + + public static function setExtendedLoginCookieDataProvider() { + $data = array(); + $now = time(); + + $secondsInDay = 86400; + + // Arbitrary durations, in units of days, to ensure it chooses the + // right one. There is a 5-minute grace period (see testSetExtendedLoginCookie) + // to work around slow tests, since we're not currently mocking time() for PHP. + + $durationOne = $secondsInDay * 5; + $durationTwo = $secondsInDay * 29; + $durationThree = $secondsInDay * 17; + + // If $wgExtendedLoginCookieExpiration is null, then the expiry passed to + // set cookie is time() + $wgCookieExpiration + $data[] = array( + null, + $durationOne, + $now + $durationOne, + ); + + // If $wgExtendedLoginCookieExpiration isn't null, then the expiry passed to + // set cookie is $now + $wgExtendedLoginCookieExpiration + $data[] = array( + $durationTwo, + $durationThree, + $now + $durationTwo, + ); + + return $data; + } + + /** + * @dataProvider setExtendedLoginCookieDataProvider + * @covers User::getRequest + * @covers User::setCookie + * @backupGlobals enabled + */ + public function testSetExtendedLoginCookie( + $extendedLoginCookieExpiration, + $cookieExpiration, + $expectedExpiry + ) { + $this->setMwGlobals( array( + 'wgExtendedLoginCookieExpiration' => $extendedLoginCookieExpiration, + 'wgCookieExpiration' => $cookieExpiration, + ) ); + + $response = $this->getMock( 'WebResponse' ); + $setcookieSpy = $this->any(); + $response->expects( $setcookieSpy ) + ->method( 'setcookie' ); + + $request = new MockWebRequest( $response ); + $user = new UserProxy( User::newFromSession( $request ) ); + $user->setExtendedLoginCookie( 'name', 'value', true ); + + $setcookieInvocations = $setcookieSpy->getInvocations(); + $setcookieInvocation = end( $setcookieInvocations ); + $actualExpiry = $setcookieInvocation->parameters[ 2 ]; + + // TODO: ± 300 seconds compensates for + // slow-running tests. However, the dependency on the time + // function should be removed. This requires some way + // to mock/isolate User->setExtendedLoginCookie's call to time() + $this->assertEquals( $expectedExpiry, $actualExpiry, '', 300 ); + } +} + +class UserProxy extends User { + + /** + * @var User + */ + protected $user; + + public function __construct( User $user ) { + $this->user = $user; + } + + public function setExtendedLoginCookie( $name, $value, $secure ) { + $this->user->setExtendedLoginCookie( $name, $value, $secure ); + } } diff --git a/tests/phpunit/includes/WikiMapTest.php b/tests/phpunit/includes/WikiMapTest.php new file mode 100644 index 00000000..9233416c --- /dev/null +++ b/tests/phpunit/includes/WikiMapTest.php @@ -0,0 +1,108 @@ +<?php + +/** + * @covers WikiMap + */ + +class WikiMapTest extends MediaWikiLangTestCase { + + public function setUp() { + parent::setUp(); + + $conf = new SiteConfiguration(); + $conf->settings = array( + 'wgServer' => array( + 'enwiki' => 'http://en.example.org', + 'ruwiki' => '//ru.example.org', + ), + 'wgArticlePath' => array( + 'enwiki' => '/w/$1', + 'ruwiki' => '/wiki/$1', + ), + ); + $conf->suffixes = array( 'wiki' ); + $this->setMwGlobals( array( + 'wgConf' => $conf, + ) ); + } + + public function provideGetWiki() { + $enwiki = new WikiReference( 'wiki', 'en', 'http://en.example.org', '/w/$1' ); + $ruwiki = new WikiReference( 'wiki', 'ru', '//ru.example.org', '/wiki/$1' ); + + return array( + 'unknown' => array( false, 'xyzzy' ), + 'enwiki' => array( $enwiki, 'enwiki' ), + 'ruwiki' => array( $ruwiki, 'ruwiki' ), + ); + } + + /** + * @dataProvider provideGetWiki + */ + public function testGetWiki( $expected, $wikiId ) { + $this->assertEquals( $expected, WikiMap::getWiki( $wikiId ) ); + } + + public function provideGetWikiName() { + return array( + 'unknown' => array( 'xyzzy', 'xyzzy' ), + 'enwiki' => array( 'en.example.org', 'enwiki' ), + 'ruwiki' => array( 'ru.example.org', 'ruwiki' ), + ); + } + + /** + * @dataProvider provideGetWikiName + */ + public function testGetWikiName( $expected, $wikiId ) { + $this->assertEquals( $expected, WikiMap::getWikiName( $wikiId ) ); + } + + public function provideMakeForeignLink() { + return array( + 'unknown' => array( false, 'xyzzy', 'Foo' ), + 'enwiki' => array( '<a class="external" rel="nofollow" href="http://en.example.org/w/Foo">Foo</a>', 'enwiki', 'Foo', ), + 'ruwiki' => array( '<a class="external" rel="nofollow" href="//ru.example.org/wiki/%D0%A4%D1%83">вар</a>', 'ruwiki', 'Фу', 'вар' ), + ); + } + + /** + * @dataProvider provideMakeForeignLink + */ + public function testMakeForeignLink( $expected, $wikiId, $page, $text = null ) { + $this->assertEquals( $expected, WikiMap::makeForeignLink( $wikiId, $page, $text ) ); + } + + public function provideForeignUserLink() { + return array( + 'unknown' => array( false, 'xyzzy', 'Foo' ), + 'enwiki' => array( '<a class="external" rel="nofollow" href="http://en.example.org/w/User:Foo">User:Foo</a>', 'enwiki', 'Foo', ), + 'ruwiki' => array( '<a class="external" rel="nofollow" href="//ru.example.org/wiki/User:%D0%A4%D1%83">вар</a>', 'ruwiki', 'Фу', 'вар' ), + ); + } + + /** + * @dataProvider provideForeignUserLink + */ + public function testForeignUserLink( $expected, $wikiId, $user, $text = null ) { + $this->assertEquals( $expected, WikiMap::foreignUserLink( $wikiId, $user, $text ) ); + } + + public function provideGetForeignURL() { + return array( + 'unknown' => array( false, 'xyzzy', 'Foo' ), + 'enwiki' => array( 'http://en.example.org/w/Foo', 'enwiki', 'Foo', ), + 'ruwiki with fragement' => array( '//ru.example.org/wiki/%D0%A4%D1%83#%D0%B2%D0%B0%D1%80', 'ruwiki', 'Фу', 'вар' ), + ); + } + + /** + * @dataProvider provideGetForeignURL + */ + public function testGetForeignURL( $expected, $wikiId, $page, $fragment = null ) { + $this->assertEquals( $expected, WikiMap::getForeignURL( $wikiId, $page, $fragment ) ); + } + +} + diff --git a/tests/phpunit/includes/WikiReferenceTest.php b/tests/phpunit/includes/WikiReferenceTest.php new file mode 100644 index 00000000..4fe2e855 --- /dev/null +++ b/tests/phpunit/includes/WikiReferenceTest.php @@ -0,0 +1,80 @@ +<?php + +/** + * @covers WikiReference + */ + +class WikiReferenceTest extends PHPUnit_Framework_TestCase { + + public function provideGetDisplayName() { + return array( + 'http' => array( 'foo.bar', 'http://foo.bar' ), + 'https' => array( 'foo.bar', 'http://foo.bar' ), + + // apparently, this is the expected behavior + 'invalid' => array( 'purple kittens', 'purple kittens' ), + ); + } + + /** + * @dataProvider provideGetDisplayName + */ + public function testGetDisplayName( $expected, $canonicalServer ) { + $reference = new WikiReference( 'wiki', 'xx', $canonicalServer, '/wiki/$1' ); + $this->assertEquals( $expected, $reference->getDisplayName() ); + } + + public function testGetCanonicalServer() { + $reference = new WikiReference( 'wiki', 'xx', 'https://acme.com', '/wiki/$1', '//acme.com' ); + $this->assertEquals( 'https://acme.com', $reference->getCanonicalServer() ); + } + + public function provideGetCanonicalUrl() { + return array( + 'no fragement' => array( 'https://acme.com/wiki/Foo', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', null ), + 'empty fragement' => array( 'https://acme.com/wiki/Foo', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', '' ), + 'fragment' => array( 'https://acme.com/wiki/Foo#Bar', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', 'Bar' ), + 'double fragment' => array( 'https://acme.com/wiki/Foo#Bar%23Xus', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', 'Bar#Xus' ), + 'escaped fragement' => array( 'https://acme.com/wiki/Foo%23Bar', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo#Bar', null ), + 'empty path' => array( 'https://acme.com/Foo', 'https://acme.com', '//acme.com', '/$1', 'Foo', null ), + ); + } + + /** + * @dataProvider provideGetCanonicalUrl + */ + public function testGetCanonicalUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) { + $reference = new WikiReference( 'wiki', 'xx', $canonicalServer, $path, $server ); + $this->assertEquals( $expected, $reference->getCanonicalUrl( $page, $fragmentId ) ); + } + + /** + * @dataProvider provideGetCanonicalUrl + * @note getUrl is an alias for getCanonicalUrl + */ + public function testGetUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) { + $reference = new WikiReference( 'wiki', 'xx', $canonicalServer, $path, $server ); + $this->assertEquals( $expected, $reference->getUrl( $page, $fragmentId ) ); + } + + public function provideGetFullUrl() { + return array( + 'no fragement' => array( '//acme.com/wiki/Foo', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', null ), + 'empty fragement' => array( '//acme.com/wiki/Foo', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', '' ), + 'fragment' => array( '//acme.com/wiki/Foo#Bar', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', 'Bar' ), + 'double fragment' => array( '//acme.com/wiki/Foo#Bar%23Xus', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', 'Bar#Xus' ), + 'escaped fragement' => array( '//acme.com/wiki/Foo%23Bar', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo#Bar', null ), + 'empty path' => array( '//acme.com/Foo', 'https://acme.com', '//acme.com', '/$1', 'Foo', null ), + ); + } + + /** + * @dataProvider provideGetFullUrl + */ + public function testGetFullUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) { + $reference = new WikiReference( 'wiki', 'xx', $canonicalServer, $path, $server ); + $this->assertEquals( $expected, $reference->getFullUrl( $page, $fragmentId ) ); + } + +} + diff --git a/tests/phpunit/includes/XmlJsTest.php b/tests/phpunit/includes/XmlJsTest.php index 0dbb0109..21819b7e 100644 --- a/tests/phpunit/includes/XmlJsTest.php +++ b/tests/phpunit/includes/XmlJsTest.php @@ -3,7 +3,7 @@ /** * @group Xml */ -class XmlJs extends MediaWikiTestCase { +class XmlJs extends PHPUnit_Framework_TestCase { /** * @covers XmlJsCode::__construct diff --git a/tests/phpunit/includes/XmlTest.php b/tests/phpunit/includes/XmlTest.php index 382e3d89..bea338de 100644 --- a/tests/phpunit/includes/XmlTest.php +++ b/tests/phpunit/includes/XmlTest.php @@ -154,7 +154,7 @@ class XmlTest extends MediaWikiTestCase { '<label for="year">From year (and earlier):</label> ' . '<input id="year" maxlength="4" size="7" type="number" value="2011" name="year" /> ' . '<label for="month">From month (and earlier):</label> ' . - '<select id="month" name="month" class="mw-month-selector">' . + '<select name="month" id="month" class="mw-month-selector">' . '<option value="-1">all</option>' . "\n" . '<option value="1">January</option>' . "\n" . '<option value="2" selected="">February</option>' . "\n" . @@ -175,7 +175,7 @@ class XmlTest extends MediaWikiTestCase { '<label for="year">From year (and earlier):</label> ' . '<input id="year" maxlength="4" size="7" type="number" value="2011" name="year" /> ' . '<label for="month">From month (and earlier):</label> ' . - '<select id="month" name="month" class="mw-month-selector">' . + '<select name="month" id="month" class="mw-month-selector">' . '<option value="-1">all</option>' . "\n" . '<option value="1">January</option>' . "\n" . '<option value="2">February</option>' . "\n" . @@ -209,7 +209,7 @@ class XmlTest extends MediaWikiTestCase { '<label for="year">From year (and earlier):</label> ' . '<input id="year" maxlength="4" size="7" type="number" name="year" /> ' . '<label for="month">From month (and earlier):</label> ' . - '<select id="month" name="month" class="mw-month-selector">' . + '<select name="month" id="month" class="mw-month-selector">' . '<option value="-1">all</option>' . "\n" . '<option value="1">January</option>' . "\n" . '<option value="2">February</option>' . "\n" . diff --git a/tests/phpunit/includes/api/ApiBlockTest.php b/tests/phpunit/includes/api/ApiBlockTest.php index d98eec6a..575efd6d 100644 --- a/tests/phpunit/includes/api/ApiBlockTest.php +++ b/tests/phpunit/includes/api/ApiBlockTest.php @@ -53,7 +53,7 @@ class ApiBlockTest extends ApiTestCase { 'action' => 'block', 'user' => 'UTApiBlockee', 'reason' => 'Some reason', - 'token' => $tokens['blocktoken'] ), null, false, self::$users['sysop']->user ); + 'token' => $tokens['blocktoken'] ), null, false, self::$users['sysop']->getUser() ); $block = Block::newFromTarget( 'UTApiBlockee' ); @@ -68,7 +68,7 @@ class ApiBlockTest extends ApiTestCase { * @expectedException UsageException * @expectedExceptionMessage The token parameter must be set */ - public function testBlockingActionWithNoToken( ) { + public function testBlockingActionWithNoToken() { $this->doApiRequest( array( 'action' => 'block', @@ -77,7 +77,7 @@ class ApiBlockTest extends ApiTestCase { ), null, false, - self::$users['sysop']->user + self::$users['sysop']->getUser() ); } } diff --git a/tests/phpunit/includes/api/ApiEditPageTest.php b/tests/phpunit/includes/api/ApiEditPageTest.php index 3179a452..61a8ad11 100644 --- a/tests/phpunit/includes/api/ApiEditPageTest.php +++ b/tests/phpunit/includes/api/ApiEditPageTest.php @@ -27,9 +27,14 @@ class ApiEditPageTest extends ApiTestCase { $wgExtraNamespaces[12312] = 'Dummy'; $wgExtraNamespaces[12313] = 'Dummy_talk'; + $wgExtraNamespaces[12314] = 'DummyNonText'; + $wgExtraNamespaces[12315] = 'DummyNonText_talk'; $wgNamespaceContentModels[12312] = "testing"; + $wgNamespaceContentModels[12314] = "testing-nontext"; + $wgContentHandlers["testing"] = 'DummyContentHandlerForTesting'; + $wgContentHandlers["testing-nontext"] = 'DummyNonTextContentHandler'; MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache $wgContLang->resetNamespaces(); # reset namespace cache @@ -96,33 +101,6 @@ class ApiEditPageTest extends ApiTestCase { ); } - public function testNonTextEdit() { - $name = 'Dummy:ApiEditPageTest_testNonTextEdit'; - $data = serialize( 'some bla bla text' ); - - // -- test new page -------------------------------------------- - $apiResult = $this->doApiRequestWithToken( array( - 'action' => 'edit', - 'title' => $name, - 'text' => $data, ) ); - $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'] ); - - // validate resulting revision - $page = WikiPage::factory( Title::newFromText( $name ) ); - $this->assertEquals( "testing", $page->getContentModel() ); - $this->assertEquals( $data, $page->getContent()->serialize() ); - } - /** * @return array */ @@ -240,7 +218,7 @@ class ApiEditPageTest extends ApiTestCase { 'section' => 'new', 'text' => 'test', 'summary' => 'header', - )); + ) ); $this->assertEquals( 'Success', $re['edit']['result'] ); // Check the page text is correct @@ -257,7 +235,7 @@ class ApiEditPageTest extends ApiTestCase { 'section' => 'new', 'text' => 'test', 'summary' => 'header', - )); + ) ); $this->assertEquals( 'Success', $re2['edit']['result'] ); $text = WikiPage::factory( Title::newFromText( $name ) ) @@ -284,18 +262,18 @@ class ApiEditPageTest extends ApiTestCase { // base edit for content $page->doEditContent( new WikitextContent( "Foo" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $page, '20120101000000' ); $baseTime = $page->getRevision()->getTimestamp(); // base edit for redirect $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $rpage, '20120101000000' ); // conflicting edit to redirect $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]\n\n[[Category:Test]]" ), - "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() ); $this->forceRevisionDate( $rpage, '20120101020202' ); // try to save edit, following the redirect @@ -306,7 +284,7 @@ class ApiEditPageTest extends ApiTestCase { 'basetimestamp' => $baseTime, 'section' => 'new', 'redirect' => true, - ), null, self::$users['sysop']->user ); + ), null, self::$users['sysop']->getUser() ); $this->assertEquals( 'Success', $re['edit']['result'], "no problems expected when following redirect" ); @@ -330,18 +308,18 @@ class ApiEditPageTest extends ApiTestCase { // base edit for content $page->doEditContent( new WikitextContent( "Foo" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $page, '20120101000000' ); $baseTime = $page->getRevision()->getTimestamp(); // base edit for redirect $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $rpage, '20120101000000' ); // conflicting edit to redirect $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]\n\n[[Category:Test]]" ), - "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() ); $this->forceRevisionDate( $rpage, '20120101020202' ); // try to save edit, following the redirect but without creating a section @@ -352,7 +330,7 @@ class ApiEditPageTest extends ApiTestCase { 'text' => 'nix bar!', 'basetimestamp' => $baseTime, 'redirect' => true, - ), null, self::$users['sysop']->user ); + ), null, self::$users['sysop']->getUser() ); $this->fail( 'redirect-appendonly error expected' ); } catch ( UsageException $ex ) { @@ -372,13 +350,13 @@ class ApiEditPageTest extends ApiTestCase { // base edit $page->doEditContent( new WikitextContent( "Foo" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $page, '20120101000000' ); $baseTime = $page->getRevision()->getTimestamp(); // conflicting edit $page->doEditContent( new WikitextContent( "Foo bar" ), - "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() ); $this->forceRevisionDate( $page, '20120101020202' ); // try to save edit, expect conflict @@ -388,7 +366,7 @@ class ApiEditPageTest extends ApiTestCase { 'title' => $name, 'text' => 'nix bar!', 'basetimestamp' => $baseTime, - ), null, self::$users['sysop']->user ); + ), null, self::$users['sysop']->getUser() ); $this->fail( 'edit conflict expected' ); } catch ( UsageException $ex ) { @@ -411,13 +389,13 @@ class ApiEditPageTest extends ApiTestCase { // base edit $page->doEditContent( new WikitextContent( "Foo" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $page, '20120101000000' ); $baseTime = $page->getRevision()->getTimestamp(); // conflicting edit $page->doEditContent( new WikitextContent( "Foo bar" ), - "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() ); $this->forceRevisionDate( $page, '20120101020202' ); // try to save edit, expect no conflict @@ -427,7 +405,7 @@ class ApiEditPageTest extends ApiTestCase { 'text' => 'nix bar!', 'basetimestamp' => $baseTime, 'section' => 'new', - ), null, self::$users['sysop']->user ); + ), null, self::$users['sysop']->getUser() ); $this->assertEquals( 'Success', $re['edit']['result'], "no edit conflict expected here" ); @@ -454,17 +432,17 @@ class ApiEditPageTest extends ApiTestCase { // base edit for content $page->doEditContent( new WikitextContent( "Foo" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $page, '20120101000000' ); // base edit for redirect $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $rpage, '20120101000000' ); // new edit to content $page->doEditContent( new WikitextContent( "Foo bar" ), - "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() ); $this->forceRevisionDate( $rpage, '20120101020202' ); // try to save edit; should work, following the redirect. @@ -474,7 +452,7 @@ class ApiEditPageTest extends ApiTestCase { 'text' => 'nix bar!', 'section' => 'new', 'redirect' => true, - ), null, self::$users['sysop']->user ); + ), null, self::$users['sysop']->getUser() ); $this->assertEquals( 'Success', $re['edit']['result'], "no edit conflict expected here" ); @@ -493,4 +471,45 @@ class ApiEditPageTest extends ApiTestCase { $page->clear(); } + + public function testCheckDirectApiEditingDisallowed_forNonTextContent() { + $this->setExpectedException( + 'UsageException', + 'Direct editing via API is not supported for content model testing used by Dummy:ApiEditPageTest_nonTextPageEdit' + ); + + $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => 'Dummy:ApiEditPageTest_nonTextPageEdit', + 'text' => '{"animals":["kittens!"]}' + ) ); + } + + public function testSupportsDirectApiEditing_withContentHandlerOverride() { + $name = 'DummyNonText:ApiEditPageTest_testNonTextEdit'; + $data = serialize( 'some bla bla text' ); + + $result = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'text' => $data, + ) ); + + $apiResult = $result[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'] ); + + // validate resulting revision + $page = WikiPage::factory( Title::newFromText( $name ) ); + $this->assertEquals( "testing-nontext", $page->getContentModel() ); + $this->assertEquals( $data, $page->getContent()->serialize() ); + } } diff --git a/tests/phpunit/includes/api/ApiLoginTest.php b/tests/phpunit/includes/api/ApiLoginTest.php index 88a99e9b..7dfd14f3 100644 --- a/tests/phpunit/includes/api/ApiLoginTest.php +++ b/tests/phpunit/includes/api/ApiLoginTest.php @@ -23,7 +23,7 @@ class ApiLoginTest extends ApiTestCase { global $wgServer; $user = self::$users['sysop']; - $user->user->logOut(); + $user->getUser()->logOut(); if ( !isset( $wgServer ) ) { $this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' ); @@ -68,7 +68,7 @@ class ApiLoginTest extends ApiTestCase { } $user = self::$users['sysop']; - $user->user->logOut(); + $user->getUser()->logOut(); $ret = $this->doApiRequest( array( "action" => "login", diff --git a/tests/phpunit/includes/api/ApiMainTest.php b/tests/phpunit/includes/api/ApiMainTest.php index e8ef1804..94b741dc 100644 --- a/tests/phpunit/includes/api/ApiMainTest.php +++ b/tests/phpunit/includes/api/ApiMainTest.php @@ -71,7 +71,7 @@ class ApiMainTest extends ApiTestCase { new FauxRequest( array( 'action' => 'query', 'meta' => 'siteinfo' ) ) ); $modules = $api->getModuleManager()->getNamesWithClasses(); - foreach( $modules as $name => $class ) { + foreach ( $modules as $name => $class ) { $this->assertArrayHasKey( $class, $classes, @@ -79,4 +79,173 @@ class ApiMainTest extends ApiTestCase { ); } } + + /** + * Test HTTP precondition headers + * + * @covers ApiMain::checkConditionalRequestHeaders + * @dataProvider provideCheckConditionalRequestHeaders + * @param array $headers HTTP headers + * @param array $conditions Return data for ApiBase::getConditionalRequestData + * @param int $status Expected response status + * @param bool $post Request is a POST + */ + public function testCheckConditionalRequestHeaders( $headers, $conditions, $status, $post = false ) { + $request = new FauxRequest( array( 'action' => 'query', 'meta' => 'siteinfo' ), $post ); + $request->setHeaders( $headers ); + $request->response()->statusHeader( 200 ); // Why doesn't it default? + + $api = new ApiMain( $request ); + $priv = TestingAccessWrapper::newFromObject( $api ); + $priv->mInternalMode = false; + + $module = $this->getMockBuilder( 'ApiBase' ) + ->setConstructorArgs( array( $api, 'mock' ) ) + ->setMethods( array( 'getConditionalRequestData' ) ) + ->getMockForAbstractClass(); + $module->expects( $this->any() ) + ->method( 'getConditionalRequestData' ) + ->will( $this->returnCallback( function ( $condition ) use ( $conditions ) { + return isset( $conditions[$condition] ) ? $conditions[$condition] : null; + } ) ); + + $ret = $priv->checkConditionalRequestHeaders( $module ); + + $this->assertSame( $status, $request->response()->getStatusCode() ); + $this->assertSame( $status === 200, $ret ); + } + + public static function provideCheckConditionalRequestHeaders() { + $now = time(); + + return array( + // Non-existing from module is ignored + array( array( 'If-None-Match' => '"foo", "bar"' ), array(), 200 ), + array( array( 'If-Modified-Since' => 'Tue, 18 Aug 2015 00:00:00 GMT' ), array(), 200 ), + + // No headers + array( + array(), + array( + 'etag' => '""', + 'last-modified' => '20150815000000', + ), + 200 + ), + + // Basic If-None-Match + array( array( 'If-None-Match' => '"foo", "bar"' ), array( 'etag' => '"bar"' ), 304 ), + array( array( 'If-None-Match' => '"foo", "bar"' ), array( 'etag' => '"baz"' ), 200 ), + array( array( 'If-None-Match' => '"foo"' ), array( 'etag' => 'W/"foo"' ), 304 ), + array( array( 'If-None-Match' => 'W/"foo"' ), array( 'etag' => '"foo"' ), 304 ), + array( array( 'If-None-Match' => 'W/"foo"' ), array( 'etag' => 'W/"foo"' ), 304 ), + + // Pointless, but supported + array( array( 'If-None-Match' => '*' ), array(), 304 ), + + // Basic If-Modified-Since + array( array( 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 304 ), + array( array( 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ), + array( 'last-modified' => wfTimestamp( TS_MW, $now ) ), 304 ), + array( array( 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ), + array( 'last-modified' => wfTimestamp( TS_MW, $now + 1 ) ), 200 ), + + // If-Modified-Since ignored when If-None-Match is given too + array( array( 'If-None-Match' => '""', 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ), + array( 'etag' => '"x"', 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 200 ), + array( array( 'If-None-Match' => '""', 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 304 ), + + // Ignored for POST + array( array( 'If-None-Match' => '"foo", "bar"' ), array( 'etag' => '"bar"' ), 200, true ), + array( array( 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 200, true ), + + // Other date formats allowed by the RFC + array( array( 'If-Modified-Since' => gmdate( 'l, d-M-y H:i:s', $now ) . ' GMT' ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 304 ), + array( array( 'If-Modified-Since' => gmdate( 'D M j H:i:s Y', $now ) ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 304 ), + + // Old browser extension to HTTP/1.0 + array( array( 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) . '; length=123' ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 304 ), + + // Invalid date formats should be ignored + array( array( 'If-Modified-Since' => gmdate( 'Y-m-d H:i:s', $now ) . ' GMT' ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 200 ), + ); + } + + /** + * Test conditional headers output + * @dataProvider provideConditionalRequestHeadersOutput + * @param array $conditions Return data for ApiBase::getConditionalRequestData + * @param array $headers Expected output headers + * @param bool $isError $isError flag + * @param bool $post Request is a POST + */ + public function testConditionalRequestHeadersOutput( $conditions, $headers, $isError = false, $post = false ) { + $request = new FauxRequest( array( 'action' => 'query', 'meta' => 'siteinfo' ), $post ); + $response = $request->response(); + + $api = new ApiMain( $request ); + $priv = TestingAccessWrapper::newFromObject( $api ); + $priv->mInternalMode = false; + + $module = $this->getMockBuilder( 'ApiBase' ) + ->setConstructorArgs( array( $api, 'mock' ) ) + ->setMethods( array( 'getConditionalRequestData' ) ) + ->getMockForAbstractClass(); + $module->expects( $this->any() ) + ->method( 'getConditionalRequestData' ) + ->will( $this->returnCallback( function ( $condition ) use ( $conditions ) { + return isset( $conditions[$condition] ) ? $conditions[$condition] : null; + } ) ); + $priv->mModule = $module; + + $priv->sendCacheHeaders( $isError ); + + foreach ( array( 'Last-Modified', 'ETag' ) as $header ) { + $this->assertEquals( + isset( $headers[$header] ) ? $headers[$header] : null, + $response->getHeader( $header ), + $header + ); + } + } + + public static function provideConditionalRequestHeadersOutput() { + return array( + array( + array(), + array() + ), + array( + array( 'etag' => '"foo"' ), + array( 'ETag' => '"foo"' ) + ), + array( + array( 'last-modified' => '20150818000102' ), + array( 'Last-Modified' => 'Tue, 18 Aug 2015 00:01:02 GMT' ) + ), + array( + array( 'etag' => '"foo"', 'last-modified' => '20150818000102' ), + array( 'ETag' => '"foo"', 'Last-Modified' => 'Tue, 18 Aug 2015 00:01:02 GMT' ) + ), + array( + array( 'etag' => '"foo"', 'last-modified' => '20150818000102' ), + array(), + true, + ), + array( + array( 'etag' => '"foo"', 'last-modified' => '20150818000102' ), + array(), + false, + true, + ), + ); + } + } diff --git a/tests/phpunit/includes/api/ApiMessageTest.php b/tests/phpunit/includes/api/ApiMessageTest.php index 6c3ce60d..08a984eb 100644 --- a/tests/phpunit/includes/api/ApiMessageTest.php +++ b/tests/phpunit/includes/api/ApiMessageTest.php @@ -14,9 +14,13 @@ class ApiMessageTest extends MediaWikiTestCase { $msg = TestingAccessWrapper::newFromObject( $msg ); $msg2 = TestingAccessWrapper::newFromObject( $msg2 ); - foreach ( array( 'interface', 'useDatabase', 'title' ) as $key ) { - $this->assertSame( $msg->$key, $msg2->$key, $key ); - } + $this->assertSame( $msg->interface, $msg2->interface, 'interface' ); + $this->assertSame( $msg->useDatabase, $msg2->useDatabase, 'useDatabase' ); + $this->assertSame( + $msg->title ? $msg->title->getFullText() : null, + $msg2->title ? $msg2->title->getFullText() : null, + 'title' + ); } /** @@ -30,6 +34,11 @@ class ApiMessageTest extends MediaWikiTestCase { $this->assertEquals( 'code', $msg2->getApiCode() ); $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + $msg2 = unserialize( serialize( $msg2 ) ); + $this->compareMessages( $msg, $msg2 ); + $this->assertEquals( 'code', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + $msg = new Message( array( 'foo', 'bar' ), array( 'baz' ) ); $msg2 = new ApiMessage( array( array( 'foo', 'bar' ), 'baz' ), 'code', array( 'data' ) ); $this->compareMessages( $msg, $msg2 ); @@ -63,6 +72,11 @@ class ApiMessageTest extends MediaWikiTestCase { $this->assertEquals( 'code', $msg2->getApiCode() ); $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + $msg2 = unserialize( serialize( $msg2 ) ); + $this->compareMessages( $msg, $msg2 ); + $this->assertEquals( 'code', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + $msg = new RawMessage( 'foo', array( 'baz' ) ); $msg2 = new ApiRawMessage( array( 'foo', 'baz' ), 'code', array( 'data' ) ); $this->compareMessages( $msg, $msg2 ); diff --git a/tests/phpunit/includes/api/ApiQueryAllPagesTest.php b/tests/phpunit/includes/api/ApiQueryAllPagesTest.php index 124988f3..0ac00eea 100644 --- a/tests/phpunit/includes/api/ApiQueryAllPagesTest.php +++ b/tests/phpunit/includes/api/ApiQueryAllPagesTest.php @@ -13,9 +13,11 @@ class ApiQueryAllPagesTest extends ApiTestCase { } /** - * @todo give this test a real name explaining what is being tested here + *Test bug 25702 + *Prefixes of API search requests are not handled with case sensitivity and may result + *in wrong search results */ - public function testBug25702() { + public function testPrefixNormalizationSearchBug() { $title = Title::newFromText( 'Category:Template:xyz' ); $page = WikiPage::factory( $title ); $page->doEdit( 'Some text', 'inserting content' ); diff --git a/tests/phpunit/includes/api/ApiResultTest.php b/tests/phpunit/includes/api/ApiResultTest.php index f0d84552..2f31677e 100644 --- a/tests/phpunit/includes/api/ApiResultTest.php +++ b/tests/phpunit/includes/api/ApiResultTest.php @@ -181,6 +181,19 @@ class ApiResultTest extends MediaWikiTestCase { ); } + ApiResult::setValue( $arr, null, NAN, ApiResult::NO_VALIDATE ); + + try { + ApiResult::setValue( $arr, null, NAN, ApiResult::NO_SIZE_CHECK ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add non-finite floats to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + $arr = array(); $result2 = new ApiResult( 8388608 ); $result2->addValue( null, 'foo', 'bar' ); @@ -408,6 +421,19 @@ class ApiResultTest extends MediaWikiTestCase { ); } + $result->addValue( null, null, NAN, ApiResult::NO_VALIDATE ); + + try { + $result->addValue( null, null, NAN, ApiResult::NO_SIZE_CHECK ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add non-finite floats to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + $result->reset(); $result->addParsedLimit( 'foo', 12 ); $this->assertSame( array( @@ -444,6 +470,12 @@ class ApiResultTest extends MediaWikiTestCase { $result->removeValue( null, 'foo' ); $this->assertTrue( $result->addValue( null, 'foo', '1' ) ); + $result = new ApiResult( 10 ); + $obj = new ApiResultTestSerializableObject( 'ok' ); + $obj->foobar = 'foobaz'; + $this->assertTrue( $result->addValue( null, 'foo', $obj ) ); + $this->assertSame( 2, $result->getSize() ); + $result = new ApiResult( 8388608 ); $result2 = new ApiResult( 8388608 ); $result2->addValue( null, 'foo', 'bar' ); @@ -674,6 +706,10 @@ class ApiResultTest extends MediaWikiTestCase { ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key', ), + 'kvpmerge' => array( 'x' => 'a', 'y' => array( 'b' ), 'z' => array( 'c' => 'd' ), + ApiResult::META_TYPE => 'kvp', + ApiResult::META_KVP_MERGE => true, + ), 'emptyDefault' => array( '_dummy' => 1 ), 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), '_dummy' => 1, @@ -858,6 +894,13 @@ class ApiResultTest extends MediaWikiTestCase { ApiResult::META_TYPE => 'assoc', ApiResult::META_KVP_KEY_NAME => 'key', ), + 'kvpmerge' => array( + 'x' => 'a', + 'y' => array( 'b', ApiResult::META_TYPE => 'array' ), + 'z' => array( 'c' => 'd', ApiResult::META_TYPE => 'assoc' ), + ApiResult::META_TYPE => 'assoc', + ApiResult::META_KVP_MERGE => true, + ), 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), '_dummy' => 1, @@ -871,8 +914,12 @@ class ApiResultTest extends MediaWikiTestCase { array( 'Types' => array( 'AssocAsObject' => true ) ), (object)array( 'defaultArray' => array( 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ), - 'defaultAssoc' => (object)array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), - 'defaultAssoc2' => (object)array( 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'defaultAssoc' => (object)array( 'x' => 'a', + 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' + ), + 'defaultAssoc2' => (object)array( 2 => 'a', 3 => 'b', + 0 => 'c', ApiResult::META_TYPE => 'assoc' + ), 'array' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), 'BCarray' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), 'BCassoc' => (object)array( 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ), @@ -885,6 +932,13 @@ class ApiResultTest extends MediaWikiTestCase { ApiResult::META_TYPE => 'assoc', ApiResult::META_KVP_KEY_NAME => 'key', ), + 'kvpmerge' => (object)array( + 'x' => 'a', + 'y' => array( 'b', ApiResult::META_TYPE => 'array' ), + 'z' => (object)array( 'c' => 'd', ApiResult::META_TYPE => 'assoc' ), + ApiResult::META_TYPE => 'assoc', + ApiResult::META_KVP_MERGE => true, + ), 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), 'emptyAssoc' => (object)array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), '_dummy' => 1, @@ -916,6 +970,13 @@ class ApiResultTest extends MediaWikiTestCase { ApiResult::META_TYPE => 'array', ApiResult::META_KVP_KEY_NAME => 'key', ), + 'kvpmerge' => array( + $kvp( 'name', 'x', 'value', 'a' ), + $kvp( 'name', 'y', 'value', array( 'b', ApiResult::META_TYPE => 'array' ) ), + array( 'name' => 'z', 'c' => 'd', ApiResult::META_TYPE => 'assoc', ApiResult::META_PRESERVE_KEYS => array( 'name' ) ), + ApiResult::META_TYPE => 'array', + ApiResult::META_KVP_MERGE => true, + ), 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), '_dummy' => 1, @@ -947,6 +1008,13 @@ class ApiResultTest extends MediaWikiTestCase { ApiResult::META_TYPE => 'array', ApiResult::META_KVP_KEY_NAME => 'key', ), + 'kvpmerge' => array( + $kvp( 'name', 'x', '*', 'a' ), + $kvp( 'name', 'y', '*', array( 'b', ApiResult::META_TYPE => 'array' ) ), + array( 'name' => 'z', 'c' => 'd', ApiResult::META_TYPE => 'assoc', ApiResult::META_PRESERVE_KEYS => array( 'name' ) ), + ApiResult::META_TYPE => 'array', + ApiResult::META_KVP_MERGE => true, + ), 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), '_dummy' => 1, @@ -960,8 +1028,12 @@ class ApiResultTest extends MediaWikiTestCase { array( 'Types' => array( 'ArmorKVP' => 'name', 'AssocAsObject' => true ) ), (object)array( 'defaultArray' => array( 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ), - 'defaultAssoc' => (object)array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), - 'defaultAssoc2' => (object)array( 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'defaultAssoc' => (object)array( 'x' => 'a', 1 => 'b', + 0 => 'c', ApiResult::META_TYPE => 'assoc' + ), + 'defaultAssoc2' => (object)array( 2 => 'a', 3 => 'b', + 0 => 'c', ApiResult::META_TYPE => 'assoc' + ), 'array' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), 'BCarray' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), 'BCassoc' => (object)array( 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ), @@ -978,6 +1050,13 @@ class ApiResultTest extends MediaWikiTestCase { ApiResult::META_TYPE => 'array', ApiResult::META_KVP_KEY_NAME => 'key', ), + 'kvpmerge' => array( + (object)$kvp( 'name', 'x', 'value', 'a' ), + (object)$kvp( 'name', 'y', 'value', array( 'b', ApiResult::META_TYPE => 'array' ) ), + (object)array( 'name' => 'z', 'c' => 'd', ApiResult::META_TYPE => 'assoc', ApiResult::META_PRESERVE_KEYS => array( 'name' ) ), + ApiResult::META_TYPE => 'array', + ApiResult::META_KVP_MERGE => true, + ), 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), 'emptyAssoc' => (object)array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), '_dummy' => 1, @@ -1017,6 +1096,11 @@ class ApiResultTest extends MediaWikiTestCase { (object)array( 'key' => 'x', 'value' => 'a' ), (object)array( 'key' => 'y', 'value' => 'b' ), ), + 'kvpmerge' => array( + (object)array( 'name' => 'x', 'value' => 'a' ), + (object)array( 'name' => 'y', 'value' => array( 'b' ) ), + (object)array( 'name' => 'z', 'c' => 'd' ), + ), 'emptyDefault' => array(), 'emptyAssoc' => (object)array(), '_dummy' => 1, @@ -1127,13 +1211,84 @@ class ApiResultTest extends MediaWikiTestCase { /** * @covers ApiResult */ + public function testAddMetadataToResultVars() { + $arr = array( + 'a' => "foo", + 'b' => false, + 'c' => 10, + 'sequential_numeric_keys' => array( 'a', 'b', 'c' ), + 'non_sequential_numeric_keys' => array( 'a', 'b', 4 => 'c' ), + 'string_keys' => array( + 'one' => 1, + 'two' => 2 + ), + 'object_sequential_keys' => (object)array( 'a', 'b', 'c' ), + '_type' => "should be overwritten in result", + ); + $this->assertSame( array( + ApiResult::META_TYPE => 'kvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ApiResult::META_PRESERVE_KEYS => array( + 'a', 'b', 'c', + 'sequential_numeric_keys', 'non_sequential_numeric_keys', + 'string_keys', 'object_sequential_keys' + ), + ApiResult::META_BC_BOOLS => array( 'b' ), + ApiResult::META_INDEXED_TAG_NAME => 'var', + 'a' => "foo", + 'b' => false, + 'c' => 10, + 'sequential_numeric_keys' => array( + ApiResult::META_TYPE => 'array', + ApiResult::META_BC_BOOLS => array(), + ApiResult::META_INDEXED_TAG_NAME => 'value', + 0 => 'a', + 1 => 'b', + 2 => 'c', + ), + 'non_sequential_numeric_keys' => array( + ApiResult::META_TYPE => 'kvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ApiResult::META_PRESERVE_KEYS => array( 0, 1, 4 ), + ApiResult::META_BC_BOOLS => array(), + ApiResult::META_INDEXED_TAG_NAME => 'var', + 0 => 'a', + 1 => 'b', + 4 => 'c', + ), + 'string_keys' => array( + ApiResult::META_TYPE => 'kvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ApiResult::META_PRESERVE_KEYS => array( 'one', 'two' ), + ApiResult::META_BC_BOOLS => array(), + ApiResult::META_INDEXED_TAG_NAME => 'var', + 'one' => 1, + 'two' => 2, + ), + 'object_sequential_keys' => array( + ApiResult::META_TYPE => 'kvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ApiResult::META_PRESERVE_KEYS => array( 0, 1, 2 ), + ApiResult::META_BC_BOOLS => array(), + ApiResult::META_INDEXED_TAG_NAME => 'var', + 0 => 'a', + 1 => 'b', + 2 => 'c', + ), + ), ApiResult::addMetadataToResultVars( $arr ) ); + } + + /** + * @covers ApiResult + */ public function testDeprecatedFunctions() { // Ignore ApiResult deprecation warnings during this test set_error_handler( function ( $errno, $errstr ) use ( &$warnings ) { if ( preg_match( '/Use of ApiResult::\S+ was deprecated in MediaWiki \d+.\d+\./', $errstr ) ) { return true; } - if ( preg_match( '/Use of ApiMain to ApiResult::__construct was deprecated in MediaWiki \d+.\d+\./', $errstr ) ) { + if ( preg_match( '/Use of ApiMain to ApiResult::__construct ' . + 'was deprecated in MediaWiki \d+.\d+\./', $errstr ) ) { return true; } return false; @@ -1166,17 +1321,6 @@ class ApiResultTest extends MediaWikiTestCase { ), '*' => 'content', ), $result->getData() ); - $result->setRawMode(); - $this->assertSame( array( - 'foo' => array( - 'bar' => array( - '*' => 'content', - ), - ), - '*' => 'content', - '_element' => 'itn', - '_subelements' => array( 'sub' ), - ), $result->getData() ); $arr = array(); ApiResult::setContent( $arr, 'value' ); @@ -1451,7 +1595,8 @@ class ApiResultTest extends MediaWikiTestCase { $result = new ApiResult( 8388608 ); $result->setMainForContinuation( $main ); - $result->beginContinuation( '||mock2', array_slice( $allModules, 0, 2 ), array( 'mock1', 'mock2' ) ); + $result->beginContinuation( '||mock2', array_slice( $allModules, 0, 2 ), + array( 'mock1', 'mock2' ) ); try { $result->setContinueParam( $allModules[1], 'm2continue', 1 ); $this->fail( 'Expected exception not thrown' ); @@ -1467,7 +1612,8 @@ class ApiResultTest extends MediaWikiTestCase { $this->fail( 'Expected exception not thrown' ); } catch ( UnexpectedValueException $ex ) { $this->assertSame( - 'Module \'mocklist\' called ApiContinuationManager::addContinueParam but was not passed to ApiContinuationManager::__construct', + 'Module \'mocklist\' called ApiContinuationManager::addContinueParam ' . + 'but was not passed to ApiContinuationManager::__construct', $ex->getMessage(), 'Expected exception' ); @@ -1495,13 +1641,14 @@ class ApiResultTest extends MediaWikiTestCase { try { $arr = array(); - ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( + ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( new ApiResultTestStringifiableObject() ) ); $this->fail( 'Expected exception not thrown' ); } catch ( UnexpectedValueException $ex ) { $this->assertSame( - 'ApiResultTestSerializableObject::serializeForApiResult() returned an object of class ApiResultTestStringifiableObject', + 'ApiResultTestSerializableObject::serializeForApiResult() ' . + 'returned an object of class ApiResultTestStringifiableObject', $ex->getMessage(), 'Expected exception' ); @@ -1509,18 +1656,19 @@ class ApiResultTest extends MediaWikiTestCase { try { $arr = array(); - ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( NAN ) ); + ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( NAN ) ); $this->fail( 'Expected exception not thrown' ); } catch ( UnexpectedValueException $ex ) { $this->assertSame( - 'ApiResultTestSerializableObject::serializeForApiResult() returned an invalid value: Cannot add non-finite floats to ApiResult', + 'ApiResultTestSerializableObject::serializeForApiResult() ' . + 'returned an invalid value: Cannot add non-finite floats to ApiResult', $ex->getMessage(), 'Expected exception' ); } $arr = array(); - ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( + ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( array( 'one' => new ApiResultTestStringifiableObject( '1' ), 'two' => new ApiResultTestSerializableObject( 2 ), diff --git a/tests/phpunit/includes/api/ApiRevisionDeleteTest.php b/tests/phpunit/includes/api/ApiRevisionDeleteTest.php index b03836eb..362d647f 100644 --- a/tests/phpunit/includes/api/ApiRevisionDeleteTest.php +++ b/tests/phpunit/includes/api/ApiRevisionDeleteTest.php @@ -25,7 +25,7 @@ class ApiRevisionDeleteTest extends ApiTestCase { } public function testHidingRevisions() { - $user = self::$users['sysop']->user; + $user = self::$users['sysop']->getUser(); $revid = array_shift( $this->revs ); $out = $this->doApiRequest( array( 'action' => 'revisiondelete', @@ -80,7 +80,7 @@ class ApiRevisionDeleteTest extends ApiTestCase { } public function testUnhidingOutput() { - $user = self::$users['sysop']->user; + $user = self::$users['sysop']->getUser(); $revid = array_shift( $this->revs ); // Hide revisions $this->doApiRequest( array( diff --git a/tests/phpunit/includes/api/ApiTestCase.php b/tests/phpunit/includes/api/ApiTestCase.php index da62bb0a..21345ac1 100644 --- a/tests/phpunit/includes/api/ApiTestCase.php +++ b/tests/phpunit/includes/api/ApiTestCase.php @@ -105,6 +105,7 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { $wgRequest = new FauxRequest( $params, true, $session ); RequestContext::getMain()->setRequest( $wgRequest ); + RequestContext::getMain()->setUser( $wgUser ); // set up local environment $context = $this->apiContext->newTestContext( $wgRequest, $wgUser ); diff --git a/tests/phpunit/includes/api/ApiUnblockTest.php b/tests/phpunit/includes/api/ApiUnblockTest.php index 2c2370a8..a374f094 100644 --- a/tests/phpunit/includes/api/ApiUnblockTest.php +++ b/tests/phpunit/includes/api/ApiUnblockTest.php @@ -16,7 +16,7 @@ class ApiUnblockTest extends ApiTestCase { /** * @expectedException UsageException */ - public function testWithNoToken( ) { + public function testWithNoToken() { $this->doApiRequest( array( 'action' => 'unblock', @@ -25,7 +25,7 @@ class ApiUnblockTest extends ApiTestCase { ), null, false, - self::$users['sysop']->user + self::$users['sysop']->getUser() ); } } diff --git a/tests/phpunit/includes/api/ApiUploadTest.php b/tests/phpunit/includes/api/ApiUploadTest.php index f74fc354..c852d72b 100644 --- a/tests/phpunit/includes/api/ApiUploadTest.php +++ b/tests/phpunit/includes/api/ApiUploadTest.php @@ -80,7 +80,7 @@ class ApiUploadTest extends ApiTestCaseUpload { try { $this->doApiRequestWithToken( array( 'action' => 'upload', - ), $session, self::$users['uploader']->user ); + ), $session, self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $exception = true; $this->assertEquals( "One of the parameters filekey, file, url, statuskey is required", @@ -126,7 +126,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result, , ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $exception = true; } @@ -165,7 +165,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { - $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user ); + $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $this->assertContains( 'The file you submitted was empty', $e->getMessage() ); $exception = true; @@ -215,7 +215,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $exception = true; } @@ -232,7 +232,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result, , ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); // FIXME: leaks a temporary file + self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file } catch ( UsageException $e ) { $exception = true; } @@ -286,7 +286,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $exception = true; } @@ -311,7 +311,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); // FIXME: leaks a temporary file + self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file } catch ( UsageException $e ) { $exception = true; } @@ -331,7 +331,7 @@ class ApiUploadTest extends ApiTestCaseUpload { */ public function testUploadStash( $session ) { $this->setMwGlobals( array( - 'wgUser' => self::$users['uploader']->user, // @todo FIXME: still used somewhere + 'wgUser' => self::$users['uploader']->getUser(), // @todo FIXME: still used somewhere ) ); $extension = 'png'; @@ -368,7 +368,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); // FIXME: leaks a temporary file + self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file } catch ( UsageException $e ) { $exception = true; } @@ -397,7 +397,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $exception = true; } @@ -415,7 +415,7 @@ class ApiUploadTest extends ApiTestCaseUpload { public function testUploadChunks( $session ) { $this->setMwGlobals( array( // @todo FIXME: still used somewhere - 'wgUser' => self::$users['uploader']->user, + 'wgUser' => self::$users['uploader']->getUser(), ) ); $chunkSize = 1048576; @@ -451,9 +451,9 @@ class ApiUploadTest extends ApiTestCaseUpload { $chunkSessionKey = false; $resultOffset = 0; // Open the file: - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $handle = fopen( $filePath, "r" ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( $handle === false ) { $this->markTestIncomplete( "could not open file: $filePath" ); @@ -461,9 +461,9 @@ class ApiUploadTest extends ApiTestCaseUpload { while ( !feof( $handle ) ) { // Get the current chunk - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $chunkData = fread( $handle, $chunkSize ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); // Upload the current chunk into the $_FILE object: $this->fakeUploadChunk( 'chunk', 'blob', $mimeType, $chunkData ); @@ -473,7 +473,7 @@ class ApiUploadTest extends ApiTestCaseUpload { // Upload fist chunk ( and get the session key ) try { list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -501,7 +501,7 @@ class ApiUploadTest extends ApiTestCaseUpload { // Upload current chunk try { list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -541,7 +541,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $exception = true; } diff --git a/tests/phpunit/includes/api/format/ApiFormatDumpTest.php b/tests/phpunit/includes/api/format/ApiFormatDumpTest.php deleted file mode 100644 index c0f67f8d..00000000 --- a/tests/phpunit/includes/api/format/ApiFormatDumpTest.php +++ /dev/null @@ -1,63 +0,0 @@ -<?php - -/** - * @group API - * @covers ApiFormatDump - */ -class ApiFormatDumpTest extends ApiFormatTestBase { - - protected $printerName = 'dump'; - - public static function provideGeneralEncoding() { - // Sigh. Docs claim it's a boolean, but can have values 0, 1, or 2. - // Fortunately wfIniGetBool does the right thing. - if ( wfIniGetBool( 'xdebug.overload_var_dump' ) ) { - return array( - array( array(), 'Cannot test ApiFormatDump when xDebug overloads var_dump', array( 'SKIP' => true ) ), - ); - } - - $warning = "\n [\"warnings\"]=>\n array(1) {\n [\"dump\"]=>\n array(1) {\n [\"*\"]=>\n" . - " string(64) \"format=dump has been deprecated. Please use format=json instead.\"\n" . - " }\n }"; - - return array( - // Basic types - array( array( null ), "array(2) {{$warning}\n [0]=>\n NULL\n}\n" ), - array( array( true ), "array(2) {{$warning}\n [0]=>\n string(0) \"\"\n}\n" ), - array( array( false ), "array(1) {{$warning}\n}\n" ), - array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), - "array(2) {{$warning}\n [0]=>\n bool(true)\n}\n" ), - array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), - "array(2) {{$warning}\n [0]=>\n bool(false)\n}\n" ), - array( array( 42 ), "array(2) {{$warning}\n [0]=>\n int(42)\n}\n" ), - array( array( 42.5 ), "array(2) {{$warning}\n [0]=>\n float(42.5)\n}\n" ), - array( array( 1e42 ), "array(2) {{$warning}\n [0]=>\n float(1.0E+42)\n}\n" ), - array( array( 'foo' ), "array(2) {{$warning}\n [0]=>\n string(3) \"foo\"\n}\n" ), - array( array( 'fóo' ), "array(2) {{$warning}\n [0]=>\n string(4) \"fóo\"\n}\n" ), - - // Arrays - array( array( array() ), "array(2) {{$warning}\n [0]=>\n array(0) {\n }\n}\n" ), - array( array( array( 1 ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [0]=>\n int(1)\n }\n}\n" ), - array( array( array( 'x' => 1 ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [\"x\"]=>\n int(1)\n }\n}\n" ), - array( array( array( 2 => 1 ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [2]=>\n int(1)\n }\n}\n" ), - array( array( (object)array() ), "array(2) {{$warning}\n [0]=>\n array(0) {\n }\n}\n" ), - array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [0]=>\n int(1)\n }\n}\n" ), - array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [0]=>\n int(1)\n }\n}\n" ), - array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [\"x\"]=>\n int(1)\n }\n}\n" ), - array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), - "array(2) {{$warning}\n [0]=>\n array(1) {\n [0]=>\n array(2) {\n [\"key\"]=>\n string(1) \"x\"\n [\"*\"]=>\n int(1)\n }\n }\n}\n" ), - array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [\"x\"]=>\n int(1)\n }\n}\n" ), - array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), "array(2) {{$warning}\n [0]=>\n array(2) {\n [0]=>\n string(1) \"a\"\n [1]=>\n string(1) \"b\"\n }\n}\n" ), - - // Content - array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), - "array(2) {{$warning}\n [\"*\"]=>\n string(3) \"foo\"\n}\n" ), - - // BC Subelements - array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), - "array(2) {{$warning}\n [\"foo\"]=>\n array(1) {\n [\"*\"]=>\n string(3) \"foo\"\n }\n}\n" ), - ); - } - -} diff --git a/tests/phpunit/includes/api/format/ApiFormatWddxTest.php b/tests/phpunit/includes/api/format/ApiFormatWddxTest.php deleted file mode 100644 index 07111300..00000000 --- a/tests/phpunit/includes/api/format/ApiFormatWddxTest.php +++ /dev/null @@ -1,80 +0,0 @@ -<?php - -/** - * @group API - * @covers ApiFormatWddx - */ -class ApiFormatWddxTest extends ApiFormatTestBase { - - protected $printerName = 'wddx'; - - public static function provideGeneralEncoding() { - if ( ApiFormatWddx::useSlowPrinter() ) { - return array( - array( array(), 'Fast Wddx printer is unavailable', array( 'SKIP' => true ) ) - ); - } - return self::provideEncoding(); - } - - public static function provideEncoding() { - $p = '<wddxPacket version=\'1.0\'><header/><data><struct><var name=\'warnings\'><struct><var name=\'wddx\'><struct><var name=\'*\'><string>format=wddx has been deprecated. Please use format=json instead.</string></var></struct></var></struct></var>'; - $s = '</struct></data></wddxPacket>'; - - return array( - // Basic types - array( array( null ), "{$p}<var name='0'><null/></var>{$s}" ), - array( array( true ), "{$p}<var name='0'><string></string></var>{$s}" ), - array( array( false ), "{$p}{$s}" ), - array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), - "{$p}<var name='0'><boolean value='true'/></var>{$s}" ), - array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), - "{$p}<var name='0'><boolean value='false'/></var>{$s}" ), - array( array( 42 ), "{$p}<var name='0'><number>42</number></var>{$s}" ), - array( array( 42.5 ), "{$p}<var name='0'><number>42.5</number></var>{$s}" ), - array( array( 1e42 ), "{$p}<var name='0'><number>1.0E+42</number></var>{$s}" ), - array( array( 'foo' ), "{$p}<var name='0'><string>foo</string></var>{$s}" ), - array( array( 'fóo' ), "{$p}<var name='0'><string>fóo</string></var>{$s}" ), - - // Arrays and objects - array( array( array() ), "{$p}<var name='0'><array length='0'></array></var>{$s}" ), - array( array( array( 1 ) ), "{$p}<var name='0'><array length='1'><number>1</number></array></var>{$s}" ), - array( array( array( 'x' => 1 ) ), "{$p}<var name='0'><struct><var name='x'><number>1</number></var></struct></var>{$s}" ), - array( array( array( 2 => 1 ) ), "{$p}<var name='0'><struct><var name='2'><number>1</number></var></struct></var>{$s}" ), - array( array( (object)array() ), "{$p}<var name='0'><struct></struct></var>{$s}" ), - array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), "{$p}<var name='0'><struct><var name='0'><number>1</number></var></struct></var>{$s}" ), - array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), "{$p}<var name='0'><array length='1'><number>1</number></array></var>{$s}" ), - array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), "{$p}<var name='0'><struct><var name='x'><number>1</number></var></struct></var>{$s}" ), - array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), - "{$p}<var name='0'><array length='1'><struct><var name='key'><string>x</string></var><var name='*'><number>1</number></var></struct></array></var>{$s}" ), - array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), "{$p}<var name='0'><struct><var name='x'><number>1</number></var></struct></var>{$s}" ), - array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), "{$p}<var name='0'><array length='2'><string>a</string><string>b</string></array></var>{$s}" ), - - // Content - array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), - "{$p}<var name='*'><string>foo</string></var>{$s}" ), - - // BC Subelements - array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), - "{$p}<var name='foo'><struct><var name='*'><string>foo</string></var></struct></var>{$s}" ), - ); - } - - /** - * @dataProvider provideEncoding - */ - public function testSlowEncoding( array $data, $expect, array $params = array() ) { - // Adjust expectation for differences between fast and slow printers. - $expect = str_replace( '\'', '"', $expect ); - $expect = str_replace( '/>', ' />', $expect ); - $expect = '<?xml version="1.0"?>' . $expect; - - $this->assertSame( $expect, $this->encodeData( $params, $data, 'ApiFormatWddxTest_SlowWddx' ) ); - } -} - -class ApiFormatWddxTest_SlowWddx extends ApiFormatWddx { - public static function useSlowPrinter() { - return true; - } -} diff --git a/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php b/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php index ce2f70de..db61bc80 100644 --- a/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php +++ b/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php @@ -57,10 +57,9 @@ abstract class ApiQueryContinueTestBase extends ApiQueryTestBase { } else { $params['action'] = 'query'; } - if ( $useContinue && !isset( $params['continue'] ) ) { + // Silence warning + if ( !isset( $params['continue'] ) ) { $params['continue'] = ''; - } else { - $params['rawcontinue'] = '1'; } $count = 0; $result = array(); diff --git a/tests/phpunit/includes/api/query/ApiQueryTest.php b/tests/phpunit/includes/api/query/ApiQueryTest.php index 5f061b50..61b992ba 100644 --- a/tests/phpunit/includes/api/query/ApiQueryTest.php +++ b/tests/phpunit/includes/api/query/ApiQueryTest.php @@ -131,7 +131,7 @@ class ApiQueryTest extends ApiTestCase { ); $queryApi = new ApiQuery( $api, 'query' ); $modules = $queryApi->getModuleManager()->getNamesWithClasses(); - foreach( $modules as $name => $class ) { + foreach ( $modules as $name => $class ) { $this->assertArrayHasKey( $class, $classes, diff --git a/tests/phpunit/includes/api/query/ApiQueryTestBase.php b/tests/phpunit/includes/api/query/ApiQueryTestBase.php index dabf72e0..d5fa4542 100644 --- a/tests/phpunit/includes/api/query/ApiQueryTestBase.php +++ b/tests/phpunit/includes/api/query/ApiQueryTestBase.php @@ -56,12 +56,12 @@ STR; * @return array */ private function validateRequestExpectedPair( $v ) { - $this->assertType( 'array', $v, self::PARAM_ASSERT ); + $this->assertInternalType( 'array', $v, self::PARAM_ASSERT ); $this->assertEquals( 2, count( $v ), self::PARAM_ASSERT ); $this->assertArrayHasKey( 0, $v, self::PARAM_ASSERT ); $this->assertArrayHasKey( 1, $v, self::PARAM_ASSERT ); - $this->assertType( 'array', $v[0], self::PARAM_ASSERT ); - $this->assertType( 'array', $v[1], self::PARAM_ASSERT ); + $this->assertInternalType( 'array', $v[0], self::PARAM_ASSERT ); + $this->assertInternalType( 'array', $v[1], self::PARAM_ASSERT ); return $v; } @@ -87,6 +87,7 @@ STR; /** * Checks that the request's result matches the expected results. + * Assumes no rawcontinue and a complete batch. * @param array $values Array is a two element array( request, expected_results ) * @param array $session * @param bool $appendModule @@ -99,8 +100,9 @@ STR; if ( !array_key_exists( 'action', $req ) ) { $req['action'] = 'query'; } - if ( !array_key_exists( 'continue', $req ) ) { - $req['rawcontinue'] = '1'; + // Silence warning + if ( !isset( $params['continue'] ) ) { + $params['continue'] = ''; } foreach ( $req as &$val ) { if ( is_array( $val ) ) { @@ -108,7 +110,7 @@ STR; } } $result = $this->doApiRequest( $req, $session, $appendModule, $user ); - $this->assertResult( array( 'query' => $exp ), $result[0], $req ); + $this->assertResult( array( 'batchcomplete' => true, 'query' => $exp ), $result[0], $req ); } protected function assertResult( $exp, $result, $message = '' ) { diff --git a/tests/phpunit/includes/cache/MessageCacheTest.php b/tests/phpunit/includes/cache/MessageCacheTest.php index 442e9f9f..5302b363 100644 --- a/tests/phpunit/includes/cache/MessageCacheTest.php +++ b/tests/phpunit/includes/cache/MessageCacheTest.php @@ -52,7 +52,7 @@ class MessageCacheTest extends MediaWikiLangTestCase { $this->makePage( 'MessageCacheTest-FullKeyTest', 'ru' ); // In content language -- get base if no derivative - $this->makePage( 'FallbackLanguageTest-NoDervContLang', 'de', 'de/none', false ); + $this->makePage( 'FallbackLanguageTest-NoDervContLang', 'de', 'de/none' ); } /** @@ -61,15 +61,14 @@ class MessageCacheTest extends MediaWikiLangTestCase { * @param string $title Title of page to be created * @param string $lang Language and content of the created page * @param string|null $content Content of the created page, or null for a generic string - * @param bool $createSubPage Set to false if a root page should be created */ - protected function makePage( $title, $lang, $content = null, $createSubPage = true ) { + protected function makePage( $title, $lang, $content = null ) { global $wgContLang; if ( $content === null ) { $content = $lang; } - if ( $lang !== $wgContLang->getCode() || $createSubPage ) { + if ( $lang !== $wgContLang->getCode() ) { $title = "$title/$lang"; } @@ -125,4 +124,26 @@ class MessageCacheTest extends MediaWikiLangTestCase { ); } + /** + * @dataProvider provideNormalizeKey + */ + public function testNormalizeKey( $key, $expected ) { + $actual = MessageCache::normalizeKey( $key ); + $this->assertEquals( $expected, $actual ); + } + + public function provideNormalizeKey() { + return array( + array( 'Foo', 'foo' ), + array( 'foo', 'foo' ), + array( 'fOo', 'fOo' ), + array( 'FOO', 'fOO' ), + array( 'Foo bar', 'foo_bar' ), + array( 'Ćab', 'ćab' ), + array( 'Ćab_e 3', 'ćab_e_3' ), + array( 'ĆAB', 'ćAB' ), + array( 'ćab', 'ćab' ), + array( 'ćaB', 'ćaB' ), + ); + } } diff --git a/tests/phpunit/includes/changes/RecentChangeTest.php b/tests/phpunit/includes/changes/RecentChangeTest.php index b3cb7b52..4d1a936e 100644 --- a/tests/phpunit/includes/changes/RecentChangeTest.php +++ b/tests/phpunit/includes/changes/RecentChangeTest.php @@ -10,8 +10,8 @@ class RecentChangeTest extends MediaWikiTestCase { protected $user_comment; protected $context; - public function __construct() { - parent::__construct(); + public function setUp() { + parent::setUp(); $this->title = Title::newFromText( 'SomeTitle' ); $this->target = Title::newFromText( 'TestTarget' ); @@ -22,6 +22,26 @@ class RecentChangeTest extends MediaWikiTestCase { } /** + * @covers RecentChange::newFromRow + * @covers RecentChange::loadFromRow + */ + public function testNewFromRow() { + $row = new stdClass(); + $row->rc_foo = 'AAA'; + $row->rc_timestamp = '20150921134808'; + $row->rc_deleted = 'bar'; + + $rc = RecentChange::newFromRow( $row ); + + $expected = array( + 'rc_foo' => 'AAA', + 'rc_timestamp' => '20150921134808', + 'rc_deleted' => 'bar', + ); + $this->assertEquals( $expected, $rc->getAttributes() ); + } + + /** * The testIrcMsgForAction* tests are supposed to cover the hacky * LogFormatter::getIRCActionText / bug 34508 * @@ -46,6 +66,7 @@ class RecentChangeTest extends MediaWikiTestCase { * - protect/protect * - protect/modifyprotect * - protect/unprotect + * - protect/move_prot * - upload/upload * - merge/merge * - import/upload @@ -59,289 +80,95 @@ class RecentChangeTest extends MediaWikiTestCase { */ /** - * @covers LogFormatter::getIRCActionText - */ - public function testIrcMsgForLogTypeBlock() { - $sep = $this->context->msg( 'colon-separator' )->text(); - - # block/block - $this->assertIRCComment( - $this->context->msg( 'blocklogentry', 'SomeTitle', 'duration', '(flags)' )->plain() - . $sep . $this->user_comment, - 'block', 'block', - array( - '5::duration' => 'duration', - '6::flags' => 'flags', - ), - $this->user_comment - ); - # block/unblock - $this->assertIRCComment( - $this->context->msg( 'unblocklogentry', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'block', 'unblock', - array(), - $this->user_comment - ); - # block/reblock - $this->assertIRCComment( - $this->context->msg( 'reblock-logentry', 'SomeTitle', 'duration', '(flags)' )->plain() - . $sep . $this->user_comment, - 'block', 'reblock', - array( - '5::duration' => 'duration', - '6::flags' => 'flags', - ), - $this->user_comment - ); - } - - /** - * @covers LogFormatter::getIRCActionText - */ - public function testIrcMsgForLogTypeDelete() { - $sep = $this->context->msg( 'colon-separator' )->text(); - - # delete/delete - $this->assertIRCComment( - $this->context->msg( 'deletedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'delete', 'delete', - array(), - $this->user_comment - ); - - # delete/restore - $this->assertIRCComment( - $this->context->msg( 'undeletedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'delete', 'restore', - array(), - $this->user_comment - ); - } - - /** - * @covers LogFormatter::getIRCActionText - */ - public 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 - */ - public function testIrcMsgForLogTypeMove() { - $move_params = array( - '4::target' => $this->target->getPrefixedText(), - '5::noredir' => 0, - ); - $sep = $this->context->msg( 'colon-separator' )->text(); - - # move/move - $this->assertIRCComment( - $this->context->msg( '1movedto2', 'SomeTitle', 'TestTarget' ) - ->plain() . $sep . $this->user_comment, - 'move', 'move', - $move_params, - $this->user_comment - ); - - # move/move_redir - $this->assertIRCComment( - $this->context->msg( '1movedto2_redir', 'SomeTitle', 'TestTarget' ) - ->plain() . $sep . $this->user_comment, - 'move', 'move_redir', - $move_params, - $this->user_comment - ); - } - - /** - * @covers LogFormatter::getIRCActionText + * @covers RecentChange::parseParams */ - public 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, + public function testParseParams() { + $params = array( + 'root' => array( + 'A' => 1, + 'B' => 'two' ) ); - } - /** - * @covers LogFormatter::getIRCActionText - */ - public function testIrcMsgForLogTypeProtect() { - $protectParams = array( - '[edit=sysop] (indefinite) [move=sysop] (indefinite)' + $this->assertParseParams( + $params, + 'a:1:{s:4:"root";a:2:{s:1:"A";i:1;s:1:"B";s:3:"two";}}' ); - $sep = $this->context->msg( 'colon-separator' )->text(); - # protect/protect - $this->assertIRCComment( - $this->context->msg( 'protectedarticle', 'SomeTitle ' . $protectParams[0] ) - ->plain() . $sep . $this->user_comment, - 'protect', 'protect', - $protectParams, - $this->user_comment + $this->assertParseParams( + null, + null ); - # protect/unprotect - $this->assertIRCComment( - $this->context->msg( 'unprotectedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'protect', 'unprotect', - array(), - $this->user_comment + $this->assertParseParams( + null, + serialize( false ) ); - # protect/modify - $this->assertIRCComment( - $this->context->msg( 'modifiedarticleprotection', 'SomeTitle ' . $protectParams[0] ) - ->plain() . $sep . $this->user_comment, - 'protect', 'modify', - $protectParams, - $this->user_comment + $this->assertParseParams( + null, + 'not-an-array' ); } /** - * @covers LogFormatter::getIRCActionText + * @param array $expectedParseParams + * @param string|null $rawRcParams */ - public function testIrcMsgForLogTypeUpload() { - $sep = $this->context->msg( 'colon-separator' )->text(); + protected function assertParseParams( $expectedParseParams, $rawRcParams ) { + $rc = new RecentChange; + $rc->setAttribs( array( 'rc_params' => $rawRcParams ) ); - # upload/upload - $this->assertIRCComment( - $this->context->msg( 'uploadedimage', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'upload', 'upload', - array(), - $this->user_comment - ); + $actualParseParams = $rc->parseParams(); - # upload/overwrite - $this->assertIRCComment( - $this->context->msg( 'overwroteimage', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'upload', 'overwrite', - array(), - $this->user_comment - ); + $this->assertEquals( $expectedParseParams, $actualParseParams ); } /** - * @covers LogFormatter::getIRCActionText + * 50 mins and 100 mins are used here as the tests never take that long! + * @return array */ - public function testIrcMsgForLogTypeMerge() { - $sep = $this->context->msg( 'colon-separator' )->text(); - - # merge/merge - $this->assertIRCComment( - $this->context->msg( 'pagemerge-logentry', 'SomeTitle', 'Dest', 'timestamp' )->plain() - . $sep . $this->user_comment, - 'merge', 'merge', - array( - '4::dest' => 'Dest', - '5::mergepoint' => 'timestamp', - ), - $this->user_comment + public function provideIsInRCLifespan() { + return array( + array( 6000, time() - 3000, 0, true ), + array( 3000, time() - 6000, 0, false ), + array( 6000, time() - 3000, 6000, true ), + array( 3000, time() - 6000, 6000, true ), ); } /** - * @covers LogFormatter::getIRCActionText + * @covers RecentChange::isInRCLifespan + * @dataProvider provideIsInRCLifespan */ - public function testIrcMsgForLogTypeImport() { - $sep = $this->context->msg( 'colon-separator' )->text(); - - # import/upload - $this->assertIRCComment( - $this->context->msg( 'import-logentry-upload', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'import', 'upload', - array(), - $this->user_comment - ); + public function testIsInRCLifespan( $maxAge, $timestamp, $tolerance, $expected ) { + $this->setMwGlobals( 'wgRCMaxAge', $maxAge ); + $this->assertEquals( $expected, RecentChange::isInRCLifespan( $timestamp, $tolerance ) ); + } - # import/interwiki - $this->assertIRCComment( - $this->context->msg( 'import-logentry-interwiki', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'import', 'interwiki', - array(), - $this->user_comment + public function provideRCTypes() { + return array( + array( RC_EDIT, 'edit' ), + array( RC_NEW, 'new' ), + array( RC_LOG, 'log' ), + array( RC_EXTERNAL, 'external' ), ); } /** - * @todo Emulate these edits somehow and extract - * raw edit summary from RecentChange object - * -- + * @dataProvider provideRCTypes + * @covers RecentChange::parseFromRCType */ - /* - public function testIrcMsgForBlankingAES() { - // $this->context->msg( 'autosumm-blank', .. ); - } - - public function testIrcMsgForReplaceAES() { - // $this->context->msg( 'autosumm-replace', .. ); - } - - public function testIrcMsgForRollbackAES() { - // $this->context->msg( 'revertpage', .. ); + public function testParseFromRCType( $rcType, $type ) { + $this->assertEquals( $type, RecentChange::parseFromRCType( $rcType ) ); } - public function testIrcMsgForUndoAES() { - // $this->context->msg( 'undo-summary', .. ); - } - */ - /** - * @param string $expected Expected IRC text without colors codes - * @param string $type Log type (move, delete, suppress, patrol ...) - * @param string $action A log type action - * @param array $params - * @param string $comment (optional) A comment for the log action - * @param string $msg (optional) A message for PHPUnit :-) + * @dataProvider provideRCTypes + * @covers RecentChange::parseToRCType */ - protected 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 IRCColourfulRCFeedFormatter::getLine for rc_comment - $ircRcComment = IRCColourfulRCFeedFormatter::cleanupForIRC( $formatter->getIRCActionComment() ); - - $this->assertEquals( - $expected, - $ircRcComment, - $msg - ); + public function testParseToRCType( $rcType, $type ) { + $this->assertEquals( $rcType, RecentChange::parseToRCType( $type ) ); } + } diff --git a/tests/phpunit/includes/config/HashConfigTest.php b/tests/phpunit/includes/config/HashConfigTest.php index 06973b09..4aa3e30c 100644 --- a/tests/phpunit/includes/config/HashConfigTest.php +++ b/tests/phpunit/includes/config/HashConfigTest.php @@ -30,7 +30,7 @@ class HashConfigTest extends MediaWikiTestCase { public function testGet() { $conf = new HashConfig( array( 'one' => '1', - )); + ) ); $this->assertEquals( '1', $conf->get( 'one' ) ); $this->setExpectedException( 'ConfigException', 'HashConfig::get: undefined option' ); $conf->get( 'two' ); diff --git a/tests/phpunit/includes/content/ContentHandlerTest.php b/tests/phpunit/includes/content/ContentHandlerTest.php index 988a59ee..8178c12e 100644 --- a/tests/phpunit/includes/content/ContentHandlerTest.php +++ b/tests/phpunit/includes/content/ContentHandlerTest.php @@ -22,6 +22,7 @@ class ContentHandlerTest extends MediaWikiTestCase { 'wgContentHandlers' => array( CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler', CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler', + CONTENT_MODEL_JSON => 'JsonContentHandler', CONTENT_MODEL_CSS => 'CssContentHandler', CONTENT_MODEL_TEXT => 'TextContentHandler', 'testing' => 'DummyContentHandlerForTesting', @@ -51,19 +52,27 @@ class ContentHandlerTest extends MediaWikiTestCase { return array( array( 'Help:Foo', CONTENT_MODEL_WIKITEXT ), array( 'Help:Foo.js', CONTENT_MODEL_WIKITEXT ), + array( 'Help:Foo.css', CONTENT_MODEL_WIKITEXT ), + array( 'Help:Foo.json', CONTENT_MODEL_WIKITEXT ), array( 'Help:Foo/bar.js', CONTENT_MODEL_WIKITEXT ), array( 'User:Foo', CONTENT_MODEL_WIKITEXT ), array( 'User:Foo.js', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo.css', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo.json', CONTENT_MODEL_WIKITEXT ), array( 'User:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ), array( 'User:Foo/bar.css', CONTENT_MODEL_CSS ), + array( 'User:Foo/bar.json', CONTENT_MODEL_JSON ), + array( 'User:Foo/bar.json.nope', CONTENT_MODEL_WIKITEXT ), array( 'User talk:Foo/bar.css', CONTENT_MODEL_WIKITEXT ), array( 'User:Foo/bar.js.xxx', CONTENT_MODEL_WIKITEXT ), array( 'User:Foo/bar.xxx', CONTENT_MODEL_WIKITEXT ), array( 'MediaWiki:Foo.js', CONTENT_MODEL_JAVASCRIPT ), - array( 'MediaWiki:Foo.css', CONTENT_MODEL_CSS ), array( 'MediaWiki:Foo.JS', CONTENT_MODEL_WIKITEXT ), - array( 'MediaWiki:Foo.CSS', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.css', CONTENT_MODEL_CSS ), array( 'MediaWiki:Foo.css.xxx', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.CSS', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.json', CONTENT_MODEL_JSON ), + array( 'MediaWiki:Foo.JSON', CONTENT_MODEL_WIKITEXT ), ); } @@ -338,6 +347,11 @@ class ContentHandlerTest extends MediaWikiTestCase { } */ + public function testSupportsDirectEditing() { + $handler = new DummyContentHandlerForTesting( CONTENT_MODEL_JSON ); + $this->assertFalse( $handler->supportsDirectEditing(), 'direct editing is not supported' ); + } + /** * @covers ContentHandler::runLegacyHooks */ @@ -365,164 +379,3 @@ class ContentHandlerTest extends MediaWikiTestCase { return true; } } - -class DummyContentHandlerForTesting extends ContentHandler { - - public function __construct( $dataModel ) { - parent::__construct( $dataModel, array( "testing" ) ); - } - - /** - * @see ContentHandler::serializeContent - * - * @param Content $content - * @param string $format - * - * @return string - */ - public function serializeContent( Content $content, $format = null ) { - return $content->serialize(); - } - - /** - * @see ContentHandler::unserializeContent - * - * @param string $blob - * @param string $format Unused. - * - * @return Content - */ - public function unserializeContent( $blob, $format = null ) { - $d = unserialize( $blob ); - - return new DummyContentForTesting( $d ); - } - - /** - * Creates an empty Content object of the type supported by this ContentHandler. - * - */ - public function makeEmptyContent() { - return new DummyContentForTesting( '' ); - } -} - -class DummyContentForTesting extends AbstractContent { - - public function __construct( $data ) { - parent::__construct( "testing" ); - - $this->data = $data; - } - - public function serialize( $format = null ) { - return serialize( $this->data ); - } - - /** - * @return string A string representing the content in a way useful for - * building a full text search index. If no useful representation exists, - * this method returns an empty string. - */ - public function getTextForSearchIndex() { - return ''; - } - - /** - * @return string|bool The wikitext to include when another page includes this content, - * or false if the content is not includable in a wikitext page. - */ - public function getWikitextForTransclusion() { - return false; - } - - /** - * Returns a textual representation of the content suitable for use in edit - * summaries and log messages. - * - * @param int $maxlength Maximum length of the summary text. - * @return string The summary text. - */ - public function getTextForSummary( $maxlength = 250 ) { - return ''; - } - - /** - * Returns native represenation of the data. Interpretation depends on the data model used, - * as given by getDataModel(). - * - * @return mixed The native representation of the content. Could be a string, a nested array - * structure, an object, a binary blob... anything, really. - */ - public function getNativeData() { - return $this->data; - } - - /** - * returns the content's nominal size in bogo-bytes. - * - * @return int - */ - public function getSize() { - return strlen( $this->data ); - } - - /** - * Return a copy of this Content object. The following must be true for the object returned - * if $copy = $original->copy() - * - * * get_class($original) === get_class($copy) - * * $original->getModel() === $copy->getModel() - * * $original->equals( $copy ) - * - * If and only if the Content object is imutable, the copy() method can and should - * return $this. That is, $copy === $original may be true, but only for imutable content - * objects. - * - * @return Content A copy of this object - */ - public function copy() { - return $this; - } - - /** - * Returns true if this content is countable as a "real" wiki page, provided - * that it's also in a countable location (e.g. a current revision in the main namespace). - * - * @param bool $hasLinks If it is known whether this content contains links, - * provide this information here, to avoid redundant parsing to find out. - * @return bool - */ - public function isCountable( $hasLinks = null ) { - return false; - } - - /** - * @param Title $title - * @param int $revId Unused. - * @param null|ParserOptions $options - * @param bool $generateHtml Whether to generate Html (default: true). If false, the result - * of calling getText() on the ParserOutput object returned by this method is undefined. - * - * @return ParserOutput - */ - public function getParserOutput( Title $title, $revId = null, - ParserOptions $options = null, $generateHtml = true - ) { - return new ParserOutput( $this->getNativeData() ); - } - - /** - * @see AbstractContent::fillParserOutput() - * - * @param Title $title Context title for parsing - * @param int|null $revId Revision ID (for {{REVISIONID}}) - * @param ParserOptions $options Parser options - * @param bool $generateHtml Whether or not to generate HTML - * @param ParserOutput &$output The output object to fill (reference). - */ - protected function fillParserOutput( Title $title, $revId, - ParserOptions $options, $generateHtml, ParserOutput &$output ) { - $output = new ParserOutput( $this->getNativeData() ); - } -} diff --git a/tests/phpunit/includes/content/CssContentHandlerTest.php b/tests/phpunit/includes/content/CssContentHandlerTest.php new file mode 100644 index 00000000..e1785a96 --- /dev/null +++ b/tests/phpunit/includes/content/CssContentHandlerTest.php @@ -0,0 +1,30 @@ +<?php + +class CssContentHandlerTest extends MediaWikiTestCase { + + /** + * @dataProvider provideMakeRedirectContent + * @covers CssContentHandler::makeRedirectContent + */ + public function testMakeRedirectContent( $title, $expected ) { + $this->setMwGlobals( array( + 'wgServer' => '//example.org', + 'wgScript' => '/w/index.php', + ) ); + $ch = new CssContentHandler(); + $content = $ch->makeRedirectContent( Title::newFromText( $title ) ); + $this->assertInstanceOf( 'CssContent', $content ); + $this->assertEquals( $expected, $content->serialize( CONTENT_FORMAT_CSS ) ); + } + + /** + * Keep this in sync with CssContentTest::provideGetRedirectTarget() + */ + public static function provideMakeRedirectContent() { + return array( + array( 'MediaWiki:MonoBook.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=MediaWiki:MonoBook.css&action=raw&ctype=text/css);" ), + array( 'User:FooBar/common.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=User:FooBar/common.css&action=raw&ctype=text/css);" ), + array( 'Gadget:FooBaz.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=Gadget:FooBaz.css&action=raw&ctype=text/css);" ), + ); + } +} diff --git a/tests/phpunit/includes/content/CssContentTest.php b/tests/phpunit/includes/content/CssContentTest.php index 40484d3a..c4d87c24 100644 --- a/tests/phpunit/includes/content/CssContentTest.php +++ b/tests/phpunit/includes/content/CssContentTest.php @@ -4,6 +4,8 @@ * @group ContentHandler * @group Database * ^--- needed, because we do need the database to test link updates + * + * @FIXME this should not extend JavaScriptContentTest. */ class CssContentTest extends JavaScriptContentTest { @@ -68,7 +70,48 @@ class CssContentTest extends JavaScriptContentTest { $this->assertEquals( CONTENT_MODEL_CSS, $content->getContentHandler()->getModelID() ); } - public static function dataEquals() { + /** + * Redirects aren't supported + */ + public static function provideUpdateRedirect() { + return array( + array( + '#REDIRECT [[Someplace]]', + '#REDIRECT [[Someplace]]', + ), + ); + } + + /** + * @dataProvider provideGetRedirectTarget + */ + public function testGetRedirectTarget( $title, $text ) { + $this->setMwGlobals( array( + 'wgServer' => '//example.org', + 'wgScriptPath' => '/w', + 'wgScript' => '/w/index.php', + ) ); + $content = new CssContent( $text ); + $target = $content->getRedirectTarget(); + $this->assertEquals( $title, $target ? $target->getPrefixedText() : null ); + } + + /** + * Keep this in sync with CssContentHandlerTest::provideMakeRedirectContent() + */ + public static function provideGetRedirectTarget() { + return array( + array( 'MediaWiki:MonoBook.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=MediaWiki:MonoBook.css&action=raw&ctype=text/css);" ), + array( 'User:FooBar/common.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=User:FooBar/common.css&action=raw&ctype=text/css);" ), + array( 'Gadget:FooBaz.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=Gadget:FooBaz.css&action=raw&ctype=text/css);" ), + # No #REDIRECT comment + array( null, "@import url(//example.org/w/index.php?title=Gadget:FooBaz.css&action=raw&ctype=text/css);" ), + # Wrong domain + array( null, "/* #REDIRECT */@import url(//example.com/w/index.php?title=Gadget:FooBaz.css&action=raw&ctype=text/css);" ), + ); + } + + public static function dataEquals() { return array( array( new CssContent( 'hallo' ), null, false ), array( new CssContent( 'hallo' ), new CssContent( 'hallo' ), true ), diff --git a/tests/phpunit/includes/content/JavaScriptContentHandlerTest.php b/tests/phpunit/includes/content/JavaScriptContentHandlerTest.php new file mode 100644 index 00000000..0f41020f --- /dev/null +++ b/tests/phpunit/includes/content/JavaScriptContentHandlerTest.php @@ -0,0 +1,30 @@ +<?php + +class JavaScriptContentHandlerTest extends MediaWikiTestCase { + + /** + * @dataProvider provideMakeRedirectContent + * @covers JavaScriptContentHandler::makeRedirectContent + */ + public function testMakeRedirectContent( $title, $expected ) { + $this->setMwGlobals( array( + 'wgServer' => '//example.org', + 'wgScript' => '/w/index.php', + ) ); + $ch = new JavaScriptContentHandler(); + $content = $ch->makeRedirectContent( Title::newFromText( $title ) ); + $this->assertInstanceOf( 'JavaScriptContent', $content ); + $this->assertEquals( $expected, $content->serialize( CONTENT_FORMAT_JAVASCRIPT ) ); + } + + /** + * Keep this in sync with JavaScriptContentTest::provideGetRedirectTarget() + */ + public static function provideMakeRedirectContent() { + return array( + array( 'MediaWiki:MonoBook.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=MediaWiki:MonoBook.js\u0026action=raw\u0026ctype=text/javascript");' ), + array( 'User:FooBar/common.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=User:FooBar/common.js\u0026action=raw\u0026ctype=text/javascript");' ), + array( 'Gadget:FooBaz.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=Gadget:FooBaz.js\u0026action=raw\u0026ctype=text/javascript");' ), + ); + } +} diff --git a/tests/phpunit/includes/content/JavaScriptContentTest.php b/tests/phpunit/includes/content/JavaScriptContentTest.php index 7193ec9f..0ee27129 100644 --- a/tests/phpunit/includes/content/JavaScriptContentTest.php +++ b/tests/phpunit/includes/content/JavaScriptContentTest.php @@ -251,16 +251,31 @@ class JavaScriptContentTest extends TextContentTest { /** * @covers JavaScriptContent::updateRedirect + * @dataProvider provideUpdateRedirect */ - public function testUpdateRedirect() { + public function testUpdateRedirect( $oldText, $expectedText) { + $this->setMwGlobals( array( + 'wgServer' => '//example.org', + 'wgScriptPath' => '/w/index.php', + ) ); $target = Title::newFromText( "testUpdateRedirect_target" ); - $content = $this->newContent( "#REDIRECT [[Someplace]]" ); + $content = new JavaScriptContent( $oldText ); $newContent = $content->updateRedirect( $target ); - $this->assertTrue( - $content->equals( $newContent ), - "content should be unchanged since it's not wikitext" + $this->assertEquals( $expectedText, $newContent->getNativeData() ); + } + + public static function provideUpdateRedirect() { + return array( + array( + '#REDIRECT [[Someplace]]', + '#REDIRECT [[Someplace]]', + ), + array( + '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=MediaWiki:MonoBook.js\u0026action=raw\u0026ctype=text/javascript");', + '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=TestUpdateRedirect_target\u0026action=raw\u0026ctype=text/javascript");' + ) ); } @@ -290,4 +305,32 @@ class JavaScriptContentTest extends TextContentTest { array( new JavaScriptContent( "hallo" ), new JavaScriptContent( "HALLO" ), false ), ); } + + /** + * @dataProvider provideGetRedirectTarget + */ + public function testGetRedirectTarget( $title, $text ) { + $this->setMwGlobals( array( + 'wgServer' => '//example.org', + 'wgScriptPath' => '/w/index.php', + ) ); + $content = new JavaScriptContent( $text ); + $target = $content->getRedirectTarget(); + $this->assertEquals( $title, $target ? $target->getPrefixedText() : null ); + } + + /** + * Keep this in sync with JavaScriptContentHandlerTest::provideMakeRedirectContent() + */ + public static function provideGetRedirectTarget() { + return array( + array( 'MediaWiki:MonoBook.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=MediaWiki:MonoBook.js\u0026action=raw\u0026ctype=text/javascript");' ), + array( 'User:FooBar/common.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=User:FooBar/common.js\u0026action=raw\u0026ctype=text/javascript");' ), + array( 'Gadget:FooBaz.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=Gadget:FooBaz.js\u0026action=raw\u0026ctype=text/javascript");' ), + // No #REDIRECT comment + array( null, 'mw.loader.load("//example.org/w/index.php?title=MediaWiki:NoRedirect.js\u0026action=raw\u0026ctype=text/javascript");' ), + // Different domain + array( null, '/* #REDIRECT */mw.loader.load("//example.com/w/index.php?title=MediaWiki:OtherWiki.js\u0026action=raw\u0026ctype=text/javascript");' ), + ); + } } diff --git a/tests/phpunit/includes/content/JsonContentTest.php b/tests/phpunit/includes/content/JsonContentTest.php index cccfe7b1..8a9d2ab0 100644 --- a/tests/phpunit/includes/content/JsonContentTest.php +++ b/tests/phpunit/includes/content/JsonContentTest.php @@ -138,7 +138,7 @@ class JsonContentTest extends MediaWikiLangTestCase { '<tr><th>0</th><td class="value">"bar"</td></tr></tbody></table>' ), array( - (object)array( '<script>alert("evil!")</script>'), + (object)array( '<script>alert("evil!")</script>' ), '<table class="mw-json"><tbody><tr><th>0</th><td class="value">"' . '<script>alert("evil!")</script>"' . '</td></tr></tbody></table>', diff --git a/tests/phpunit/includes/content/TextContentHandlerTest.php b/tests/phpunit/includes/content/TextContentHandlerTest.php new file mode 100644 index 00000000..492fec6b --- /dev/null +++ b/tests/phpunit/includes/content/TextContentHandlerTest.php @@ -0,0 +1,12 @@ +<?php + +/** + * @group ContentHandler + */ +class TextContentHandlerTest extends MediaWikiLangTestCase { + public function testSupportsDirectEditing() { + $handler = new TextContentHandler(); + $this->assertTrue( $handler->supportsDirectEditing(), 'direct editing is supported' ); + } + +} diff --git a/tests/phpunit/includes/content/TextContentTest.php b/tests/phpunit/includes/content/TextContentTest.php index dd61f85b..fe263756 100644 --- a/tests/phpunit/includes/content/TextContentTest.php +++ b/tests/phpunit/includes/content/TextContentTest.php @@ -27,10 +27,16 @@ class TextContentTest extends MediaWikiLangTestCase { CONTENT_MODEL_JAVASCRIPT, ), 'wgUseTidy' => false, - 'wgAlwaysUseTidy' => false, 'wgCapitalLinks' => true, 'wgHooks' => array(), // bypass hook ContentGetParserOutput that force custom rendering ) ); + + MWTidy::destroySingleton(); + } + + protected function tearDown() { + MWTidy::destroySingleton(); + parent::tearDown(); } public function newContent( $text ) { diff --git a/tests/phpunit/includes/content/WikitextContentHandlerTest.php b/tests/phpunit/includes/content/WikitextContentHandlerTest.php index 38fb5733..361238b7 100644 --- a/tests/phpunit/includes/content/WikitextContentHandlerTest.php +++ b/tests/phpunit/includes/content/WikitextContentHandlerTest.php @@ -115,6 +115,11 @@ class WikitextContentHandlerTest extends MediaWikiLangTestCase { $this->assertEquals( $supported, $this->handler->isSupportedFormat( $format ) ); } + public function testSupportsDirectEditing() { + $handler = new WikiTextContentHandler(); + $this->assertTrue( $handler->supportsDirectEditing(), 'direct editing is supported' ); + } + public static function dataMerge3() { return array( array( diff --git a/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php b/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php index b4292a60..42ea58e5 100644 --- a/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php +++ b/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php @@ -181,7 +181,7 @@ class DatabaseMysqlBaseTest extends MediaWikiTestCase { array( 'Tables_in_' => 'view2' ), array( 'Tables_in_' => 'myview' ), false # no more rows - )); + ) ); return $db; } /** diff --git a/tests/phpunit/includes/db/DatabaseSqliteTest.php b/tests/phpunit/includes/db/DatabaseSqliteTest.php index 645baf1f..3db9172a 100644 --- a/tests/phpunit/includes/db/DatabaseSqliteTest.php +++ b/tests/phpunit/includes/db/DatabaseSqliteTest.php @@ -1,12 +1,13 @@ <?php -class MockDatabaseSqlite extends DatabaseSqlite { +class DatabaseSqliteMock extends DatabaseSqlite { private $lastQuery; public static function newInstance( array $p = array() ) { $p['dbFilePath'] = ':memory:'; + $p['schema'] = false; - return new self( $p ); + return DatabaseBase::factory( 'SqliteMock', $p ); } function query( $sql, $fname = '', $tempIgnore = false ) { @@ -29,7 +30,7 @@ class MockDatabaseSqlite extends DatabaseSqlite { * @group medium */ class DatabaseSqliteTest extends MediaWikiTestCase { - /** @var MockDatabaseSqlite */ + /** @var DatabaseSqliteMock */ protected $db; protected function setUp() { @@ -38,7 +39,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { if ( !Sqlite::isPresent() ) { $this->markTestSkipped( 'No SQLite support detected' ); } - $this->db = MockDatabaseSqlite::newInstance(); + $this->db = DatabaseSqliteMock::newInstance(); if ( version_compare( $this->db->getServerVersion(), '3.6.0', '<' ) ) { $this->markTestSkipped( "SQLite at least 3.6 required, {$this->db->getServerVersion()} found" ); } @@ -188,18 +189,34 @@ class DatabaseSqliteTest extends MediaWikiTestCase { public function testDuplicateTableStructure() { $db = DatabaseSqlite::newStandaloneInstance( ':memory:' ); $db->query( 'CREATE TABLE foo(foo, barfoo)' ); + $db->query( 'CREATE INDEX index1 ON foo(foo)' ); + $db->query( 'CREATE UNIQUE INDEX index2 ON foo(barfoo)' ); $db->duplicateTableStructure( 'foo', 'bar' ); $this->assertEquals( 'CREATE TABLE "bar"(foo, barfoo)', $db->selectField( 'sqlite_master', 'sql', array( 'name' => 'bar' ) ), 'Normal table duplication' ); + $indexList = $db->query( 'PRAGMA INDEX_LIST("bar")' ); + $index = $indexList->next(); + $this->assertEquals( 'bar_index1', $index->name ); + $this->assertEquals( '0', $index->unique ); + $index = $indexList->next(); + $this->assertEquals( 'bar_index2', $index->name ); + $this->assertEquals( '1', $index->unique ); $db->duplicateTableStructure( 'foo', 'baz', true ); $this->assertEquals( 'CREATE TABLE "baz"(foo, barfoo)', $db->selectField( 'sqlite_temp_master', 'sql', array( 'name' => 'baz' ) ), 'Creation of temporary duplicate' ); + $indexList = $db->query( 'PRAGMA INDEX_LIST("baz")' ); + $index = $indexList->next(); + $this->assertEquals( 'baz_index1', $index->name ); + $this->assertEquals( '0', $index->unique ); + $index = $indexList->next(); + $this->assertEquals( 'baz_index2', $index->name ); + $this->assertEquals( '1', $index->unique ); $this->assertEquals( 0, $db->selectField( 'sqlite_master', 'COUNT(*)', array( 'name' => 'baz' ) ), 'Create a temporary duplicate only' diff --git a/tests/phpunit/includes/db/ORMTableTest.php b/tests/phpunit/includes/db/ORMTableTest.php index 338d931f..764560d5 100644 --- a/tests/phpunit/includes/db/ORMTableTest.php +++ b/tests/phpunit/includes/db/ORMTableTest.php @@ -68,25 +68,6 @@ class ORMTableTest extends MediaWikiTestCase { $this->assertInstanceOf( $class, $class::singleton() ); $this->assertTrue( $class::singleton() === $class::singleton() ); } - - /** - * @since 1.21 - */ - public function testIgnoreErrorsOverride() { - $table = $this->getTable(); - - $db = $table->getReadDbConnection(); - $db->ignoreErrors( true ); - - try { - $table->rawSelect( "this is invalid" ); - $this->fail( "An invalid query should trigger a DBQueryError even if ignoreErrors is enabled." ); - } catch ( DBQueryError $ex ) { - $this->assertTrue( true, "just making phpunit happy" ); - } - - $db->ignoreErrors( false ); - } } /** diff --git a/tests/phpunit/includes/debug/MWDebugTest.php b/tests/phpunit/includes/debug/MWDebugTest.php index 1abb47e7..7280a97b 100644 --- a/tests/phpunit/includes/debug/MWDebugTest.php +++ b/tests/phpunit/includes/debug/MWDebugTest.php @@ -12,11 +12,11 @@ class MWDebugTest extends MediaWikiTestCase { } /** Clear log before each test */ MWDebug::clearLog(); - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); } protected function tearDown() { - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); parent::tearDown(); } diff --git a/tests/phpunit/includes/debug/logging/LegacyLoggerTest.php b/tests/phpunit/includes/debug/logger/LegacyLoggerTest.php index 415fa045..1b3ce2ca 100644 --- a/tests/phpunit/includes/debug/logging/LegacyLoggerTest.php +++ b/tests/phpunit/includes/debug/logger/LegacyLoggerTest.php @@ -35,6 +35,8 @@ class LegacyLoggerTest extends MediaWikiTestCase { } public function provideInterpolate() { + $e = new \Exception( 'boom!' ); + $d = new \DateTime(); return array( array( 'no-op', @@ -68,6 +70,57 @@ class LegacyLoggerTest extends MediaWikiTestCase { ), '{ not interpolated }', ), + array( + '{null}', + array( + 'null' => null, + ), + '[Null]', + ), + array( + '{bool}', + array( + 'bool' => true, + ), + 'true', + ), + array( + '{float}', + array( + 'float' => 1.23, + ), + '1.23', + ), + array( + '{array}', + array( + 'array' => array( 1, 2, 3 ), + ), + '[Array(3)]', + ), + array( + '{exception}', + array( + 'exception' => $e, + ), + '[Exception ' . get_class( $e ) . '( ' . + $e->getFile() . ':' . $e->getLine() . ') ' . + $e->getMessage() . ']', + ), + array( + '{datetime}', + array( + 'datetime' => $d, + ), + $d->format( 'c' ), + ), + array( + '{object}', + array( + 'object' => new \stdClass, + ), + '[Object stdClass]', + ), ); } diff --git a/tests/phpunit/includes/debug/logger/MonologSpiTest.php b/tests/phpunit/includes/debug/logger/MonologSpiTest.php new file mode 100644 index 00000000..aa0a54ff --- /dev/null +++ b/tests/phpunit/includes/debug/logger/MonologSpiTest.php @@ -0,0 +1,136 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +namespace MediaWiki\Logger; + +use MediaWikiTestCase; +use TestingAccessWrapper; + +class MonologSpiTest extends MediaWikiTestCase { + + /** + * @covers MonologSpi::mergeConfig + */ + public function testMergeConfig() { + $base = array( + 'loggers' => array( + '@default' => array( + 'processors' => array( 'constructor' ), + 'handlers' => array( 'constructor' ), + ), + ), + 'processors' => array( + 'constructor' => array( + 'class' => 'constructor', + ), + ), + 'handlers' => array( + 'constructor' => array( + 'class' => 'constructor', + 'formatter' => 'constructor', + ), + ), + 'formatters' => array( + 'constructor' => array( + 'class' => 'constructor', + ), + ), + ); + + $fixture = new MonologSpi( $base ); + $this->assertSame( + $base, + TestingAccessWrapper::newFromObject( $fixture )->config + ); + + $fixture->mergeConfig( array( + 'loggers' => array( + 'merged' => array( + 'processors' => array( 'merged' ), + 'handlers' => array( 'merged' ), + ), + ), + 'processors' => array( + 'merged' => array( + 'class' => 'merged', + ), + ), + 'magic' => array( + 'idkfa' => array( 'xyzzy' ), + ), + 'handlers' => array( + 'merged' => array( + 'class' => 'merged', + 'formatter' => 'merged', + ), + ), + 'formatters' => array( + 'merged' => array( + 'class' => 'merged', + ), + ), + ) ); + $this->assertSame( + array( + 'loggers' => array( + '@default' => array( + 'processors' => array( 'constructor' ), + 'handlers' => array( 'constructor' ), + ), + 'merged' => array( + 'processors' => array( 'merged' ), + 'handlers' => array( 'merged' ), + ), + ), + 'processors' => array( + 'constructor' => array( + 'class' => 'constructor', + ), + 'merged' => array( + 'class' => 'merged', + ), + ), + 'handlers' => array( + 'constructor' => array( + 'class' => 'constructor', + 'formatter' => 'constructor', + ), + 'merged' => array( + 'class' => 'merged', + 'formatter' => 'merged', + ), + ), + 'formatters' => array( + 'constructor' => array( + 'class' => 'constructor', + ), + 'merged' => array( + 'class' => 'merged', + ), + ), + 'magic' => array( + 'idkfa' => array( 'xyzzy' ), + ), + ), + TestingAccessWrapper::newFromObject( $fixture )->config + ); + } + +} diff --git a/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php b/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php new file mode 100644 index 00000000..44242ed2 --- /dev/null +++ b/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php @@ -0,0 +1,64 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +namespace MediaWiki\Logger\Monolog; + +use MediaWikiTestCase; +use PHPUnit_Framework_Error_Notice; + +class AvroFormatterTest extends MediaWikiTestCase { + + protected function setUp() { + if ( !class_exists( 'AvroStringIO' ) ) { + $this->markTestSkipped( 'Avro is required for the AvroFormatterTest' ); + } + parent::setUp(); + } + + public function testSchemaNotAvailable() { + $formatter = new AvroFormatter( array() ); + $this->setExpectedException( 'PHPUnit_Framework_Error_Notice', "The schema for channel 'marty' is not available" ); + $formatter->format( array( 'channel' => 'marty' ) ); + } + + public function testSchemaNotAvailableReturnValue() { + $formatter = new AvroFormatter( array() ); + $noticeEnabled = PHPUnit_Framework_Error_Notice::$enabled; + // disable conversion of notices + PHPUnit_Framework_Error_Notice::$enabled = false; + // have to keep the user notice from being output + wfSuppressWarnings(); + $res = $formatter->format( array( 'channel' => 'marty' ) ); + wfRestoreWarnings(); + PHPUnit_Framework_Error_Notice::$enabled = $noticeEnabled; + $this->assertNull( $res ); + } + + public function testDoesSomethingWhenSchemaAvailable() { + $formatter = new AvroFormatter( array( 'string' => array( 'type' => 'string' ) ) ); + $res = $formatter->format( array( + 'channel' => 'string', + 'context' => 'better to be', + ) ); + $this->assertNotNull( $res ); + // basically just tell us if avro changes its string encoding + $this->assertEquals( base64_decode( 'GGJldHRlciB0byBiZQ==' ), $res ); + } +} diff --git a/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php b/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php new file mode 100644 index 00000000..090f439e --- /dev/null +++ b/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php @@ -0,0 +1,207 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +namespace MediaWiki\Logger\Monolog; + +use MediaWikiTestCase; +use Monolog\Logger; + +// not available in the version of phpunit mw uses, so copied into repo +require_once __DIR__ . '/../../../ConsecutiveParametersMatcher.php'; + +class KafkaHandlerTest extends MediaWikiTestCase { + + protected function setUp() { + if ( !class_exists( 'Monolog\Handler\AbstractProcessingHandler' ) + || !class_exists( 'Kafka\Produce' ) + ) { + $this->markTestSkipped( 'Monolog and Kafka are required for the KafkaHandlerTest' ); + } + + parent::setUp(); + } + + public function topicNamingProvider() { + return array( + array( array(), 'monolog_foo' ), + array( array( 'alias' => array( 'foo' => 'bar' ) ), 'bar' ) + ); + } + + /** + * @dataProvider topicNamingProvider + */ + public function testTopicNaming( $options, $expect ) { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects($this->any()) + ->method('getAvailablePartitions') + ->will($this->returnValue( array( 'A' ) ) ); + $produce->expects($this->once()) + ->method( 'setMessages' ) + ->with( $expect, $this->anything(), $this->anything() ); + + $handler = new KafkaHandler( $produce, $options ); + $handler->handle( array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ) ); + } + + public function swallowsExceptionsWhenRequested() { + return array( + // defaults to false + array( array(), true ), + // also try false explicitly + array( array( 'swallowExceptions' => false ), true ), + // turn it on + array( array( 'swallowExceptions' => true ), false ), + ); + } + + /** + * @dataProvider swallowsExceptionsWhenRequested + */ + public function testGetAvailablePartitionsException( $options, $expectException ) { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects( $this->any() ) + ->method( 'getAvailablePartitions' ) + ->will( $this->throwException( new \Kafka\Exception ) ); + + if ( $expectException ) { + $this->setExpectedException( 'Kafka\Exception' ); + } + + $handler = new KafkaHandler( $produce, $options ); + $handler->handle( array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ) ); + + if ( !$expectException ) { + $this->assertTrue( true, 'no exception was thrown' ); + } + } + + /** + * @dataProvider swallowsExceptionsWhenRequested + */ + public function testSendException( $options, $expectException ) { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects( $this->any() ) + ->method( 'getAvailablePartitions' ) + ->will( $this->returnValue( array( 'A' ) ) ); + $produce->expects( $this->any() ) + ->method( 'send' ) + ->will( $this->throwException( new \Kafka\Exception ) ); + + if ( $expectException ) { + $this->setExpectedException( 'Kafka\Exception' ); + } + + $handler = new KafkaHandler( $produce, $options ); + $handler->handle( array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ) ); + + if ( !$expectException ) { + $this->assertTrue( true, 'no exception was thrown' ); + } + } + + public function testHandlesNullFormatterResult() { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects( $this->any() ) + ->method( 'getAvailablePartitions' ) + ->will( $this->returnValue( array( 'A' ) ) ); + $mockMethod = $produce->expects( $this->exactly( 2 ) ) + ->method( 'setMessages' ); + // evil hax + \TestingAccessWrapper::newFromObject( $mockMethod )->matcher->parametersMatcher = + new \PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters( array( + array( $this->anything(), $this->anything(), array( 'words' ) ), + array( $this->anything(), $this->anything(), array( 'lines' ) ) + ) ); + + $formatter = $this->getMock( 'Monolog\Formatter\FormatterInterface' ); + $formatter->expects( $this->any() ) + ->method( 'format' ) + ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) ); + + $handler = new KafkaHandler( $produce, array() ); + $handler->setFormatter( $formatter ); + for ( $i = 0; $i < 3; ++$i ) { + $handler->handle( array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ) ); + } + } + + + public function testBatchHandlesNullFormatterResult() { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects( $this->any() ) + ->method( 'getAvailablePartitions' ) + ->will( $this->returnValue( array( 'A' ) ) ); + $produce->expects( $this->once() ) + ->method( 'setMessages' ) + ->with( $this->anything(), $this->anything(), array( 'words', 'lines' ) ); + + $formatter = $this->getMock( 'Monolog\Formatter\FormatterInterface' ); + $formatter->expects( $this->any() ) + ->method( 'format' ) + ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) ); + + $handler = new KafkaHandler( $produce, array() ); + $handler->setFormatter( $formatter ); + $handler->handleBatch( array( + array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ), + array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ), + array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ), + ) ); + } +} diff --git a/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php b/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php new file mode 100644 index 00000000..be23c4a2 --- /dev/null +++ b/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php @@ -0,0 +1,75 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +namespace MediaWiki\Logger\Monolog; + +use InvalidArgumentException; +use LengthException; +use LogicException; +use MediaWikiTestCase; +use TestingAccessWrapper; + +class LineFormatterTest extends MediaWikiTestCase { + + protected function setUp() { + if ( !class_exists( 'Monolog\Formatter\LineFormatter' ) ) { + $this->markTestSkipped( 'This test requires monolog to be installed' ); + } + parent::setUp(); + } + + /** + * @covers LineFormatter::normalizeException + */ + public function testNormalizeExceptionNoTrace() { + $fixture = new LineFormatter(); + $fixture->includeStacktraces( false ); + $fixture = TestingAccessWrapper::newFromObject( $fixture ); + $boom = new InvalidArgumentException( 'boom', 0, + new LengthException( 'too long', 0, + new LogicException( 'Spock wuz here' ) + ) + ); + $out = $fixture->normalizeException( $boom ); + $this->assertContains( "\n[Exception InvalidArgumentException]", $out ); + $this->assertContains( "\nCaused by: [Exception LengthException]", $out ); + $this->assertContains( "\nCaused by: [Exception LogicException]", $out ); + $this->assertNotContains( "\n #0", $out ); + } + + /** + * @covers LineFormatter::normalizeException + */ + public function testNormalizeExceptionTrace() { + $fixture = new LineFormatter(); + $fixture->includeStacktraces( true ); + $fixture = TestingAccessWrapper::newFromObject( $fixture ); + $boom = new InvalidArgumentException( 'boom', 0, + new LengthException( 'too long', 0, + new LogicException( 'Spock wuz here' ) + ) + ); + $out = $fixture->normalizeException( $boom ); + $this->assertContains( "\n[Exception InvalidArgumentException]", $out ); + $this->assertContains( "\nCaused by: [Exception LengthException]", $out ); + $this->assertContains( "\nCaused by: [Exception LogicException]", $out ); + $this->assertContains( "\n #0", $out ); + } +} diff --git a/tests/phpunit/includes/deferred/DeferredUpdatesTest.php b/tests/phpunit/includes/deferred/DeferredUpdatesTest.php index 5348c854..df4213ab 100644 --- a/tests/phpunit/includes/deferred/DeferredUpdatesTest.php +++ b/tests/phpunit/includes/deferred/DeferredUpdatesTest.php @@ -1,8 +1,9 @@ <?php class DeferredUpdatesTest extends MediaWikiTestCase { + public function testDoUpdatesWeb() { + $this->setMwGlobals( 'wgCommandLineMode', false ); - public function testDoUpdates() { $updates = array( '1' => 'deferred update 1', '2' => 'deferred update 2', @@ -35,4 +36,38 @@ class DeferredUpdatesTest extends MediaWikiTestCase { DeferredUpdates::doUpdates(); } + public function testDoUpdatesCLI() { + $this->setMwGlobals( 'wgCommandLineMode', true ); + + $updates = array( + '1' => 'deferred update 1', + '2' => 'deferred update 2', + '2-1' => 'deferred update 1 within deferred update 2', + '3' => 'deferred update 3', + ); + DeferredUpdates::addCallableUpdate( + function () use ( $updates ) { + echo $updates['1']; + } + ); + DeferredUpdates::addCallableUpdate( + function () use ( $updates ) { + echo $updates['2']; + DeferredUpdates::addCallableUpdate( + function () use ( $updates ) { + echo $updates['2-1']; + } + ); + } + ); + DeferredUpdates::addCallableUpdate( + function () use ( $updates ) { + echo $updates[3]; + } + ); + + $this->expectOutputString( implode( '', $updates ) ); + + DeferredUpdates::doUpdates(); + } } diff --git a/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php b/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php index 3bea9b31..a546bec1 100644 --- a/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php +++ b/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php @@ -69,11 +69,11 @@ class ArrayDiffFormatterTest extends MediaWikiTestCase { $otherTestCases = array(); $otherTestCases[] = array( - $this->getMockDiff( array( $this->getMockDiffOp( 'add', array( ), array( 'a1' ) ) ) ), + $this->getMockDiff( array( $this->getMockDiffOp( 'add', array(), array( 'a1' ) ) ) ), array( array( 'action' => 'add', 'new' => 'a1', 'newline' => 1 ) ), ); $otherTestCases[] = array( - $this->getMockDiff( array( $this->getMockDiffOp( 'add', array( ), array( 'a1', 'a2' ) ) ) ), + $this->getMockDiff( array( $this->getMockDiffOp( 'add', array(), array( 'a1', 'a2' ) ) ) ), array( array( 'action' => 'add', 'new' => 'a1', 'newline' => 1 ), array( 'action' => 'add', 'new' => 'a2', 'newline' => 2 ), diff --git a/tests/phpunit/includes/exception/HttpErrorTest.php b/tests/phpunit/includes/exception/HttpErrorTest.php new file mode 100644 index 00000000..66fe90c9 --- /dev/null +++ b/tests/phpunit/includes/exception/HttpErrorTest.php @@ -0,0 +1,65 @@ +<?php + +/** + * @todo tests for HttpError::report + * + * @covers HttpError + */ +class HttpErrorTest extends MediaWikiTestCase { + + public function testIsLoggable() { + $httpError = new HttpError( 500, 'server error!' ); + $this->assertFalse( $httpError->isLoggable(), 'http error is not loggable' ); + } + + public function testGetStatusCode() { + $httpError = new HttpError( 500, 'server error!' ); + $this->assertEquals( 500, $httpError->getStatusCode() ); + } + + /** + * @dataProvider getHtmlProvider + */ + public function testGetHtml( array $expected, $content, $header ) { + $httpError = new HttpError( 500, $content, $header ); + $errorHtml = $httpError->getHtml(); + + foreach ( $expected as $key => $html ) { + $this->assertContains( $html, $errorHtml, $key ); + } + } + + public function getHtmlProvider() { + return array( + array( + array( + 'head html' => '<head><title>Server Error 123</title></head>', + 'body html' => '<body><h1>Server Error 123</h1>' + . '<p>a server error!</p></body>' + ), + 'a server error!', + 'Server Error 123' + ), + array( + array( + 'head html' => '<head><title>loginerror</title></head>', + 'body html' => '<body><h1>loginerror</h1>' + . '<p>suspicious-userlogout</p></body>' + ), + new RawMessage( 'suspicious-userlogout' ), + new RawMessage( 'loginerror' ) + ), + array( + array( + 'head html' => '<html><head><title>Internal Server Error</title></head>', + 'body html' => '<body><h1>Internal Server Error</h1>' + . '<p>a server error!</p></body></html>' + ), + 'a server error!', + null + ) + ); + } + + +} diff --git a/tests/phpunit/includes/exception/MWExceptionTest.php b/tests/phpunit/includes/exception/MWExceptionTest.php index ef0f2a9e..f11fda32 100644 --- a/tests/phpunit/includes/exception/MWExceptionTest.php +++ b/tests/phpunit/includes/exception/MWExceptionTest.php @@ -178,7 +178,7 @@ class MWExceptionTest extends MediaWikiTestCase { $this->setMwGlobals( array( 'wgLogExceptionBacktrace' => true ) ); $json = json_decode( - MWExceptionHandler::jsonSerializeException( new $exClass()) + MWExceptionHandler::jsonSerializeException( new $exClass() ) ); $this->assertObjectHasAttribute( $key, $json, "JSON serialized exception is missing key '$key'" diff --git a/tests/phpunit/includes/filebackend/FileBackendTest.php b/tests/phpunit/includes/filebackend/FileBackendTest.php index bfca75ad..2e4942f0 100644 --- a/tests/phpunit/includes/filebackend/FileBackendTest.php +++ b/tests/phpunit/includes/filebackend/FileBackendTest.php @@ -2376,7 +2376,7 @@ class FileBackendTest extends MediaWikiTestCase { $status = Status::newGood(); $sl = $this->backend->getScopedFileLocks( $paths, LockManager::LOCK_EX, $status ); - $this->assertType( 'ScopedLock', $sl, + $this->assertInstanceOf( 'ScopedLock', $sl, "Scoped locking of files succeeded ($backendName)." ); $this->assertEquals( array(), $status->errors, "Scoped locking of files succeeded ($backendName)." ); @@ -2392,6 +2392,56 @@ class FileBackendTest extends MediaWikiTestCase { "Scoped unlocking of files succeeded with OK status ($backendName)." ); } + public function testReadAffinity() { + $be = TestingAccessWrapper::newFromObject( + new FileBackendMultiWrite( array( + 'name' => 'localtesting', + 'wikiId' => wfWikiId() . mt_rand(), + 'backends' => array( + array( // backend 0 + 'name' => 'multitesting0', + 'class' => 'MemoryFileBackend', + 'isMultiMaster' => false, + 'readAffinity' => true + ), + array( // backend 1 + 'name' => 'multitesting1', + 'class' => 'MemoryFileBackend', + 'isMultiMaster' => true + ) + ) + ) ) + ); + + $this->assertEquals( + 1, + $be->getReadIndexFromParams( array( 'latest' => 1 ) ), + 'Reads with "latest" flag use backend 1' + ); + $this->assertEquals( + 0, + $be->getReadIndexFromParams( array( 'latest' => 0 ) ), + 'Reads without "latest" flag use backend 0' + ); + + $p = 'container/test-cont/file.txt'; + $be->backends[0]->quickCreate( array( + 'dst' => "mwstore://multitesting0/$p", 'content' => 'cattitude' ) ); + $be->backends[1]->quickCreate( array( + 'dst' => "mwstore://multitesting1/$p", 'content' => 'princess of power' ) ); + + $this->assertEquals( + 'cattitude', + $be->getFileContents( array( 'src' => "mwstore://localtesting/$p" ) ), + "Non-latest read came from backend 0" + ); + $this->assertEquals( + 'princess of power', + $be->getFileContents( array( 'src' => "mwstore://localtesting/$p", 'latest' => 1 ) ), + "Latest read came from backend1" + ); + } + // helper function private function listToArray( $iter ) { return is_array( $iter ) ? $iter : iterator_to_array( $iter ); diff --git a/tests/phpunit/includes/filebackend/SwiftFileBackendTest.php b/tests/phpunit/includes/filebackend/SwiftFileBackendTest.php new file mode 100644 index 00000000..a618889c --- /dev/null +++ b/tests/phpunit/includes/filebackend/SwiftFileBackendTest.php @@ -0,0 +1,148 @@ +<?php + +/** + * @group FileRepo + * @group FileBackend + * @group medium + */ +class SwiftFileBackendTest extends MediaWikiTestCase { + /** @var TestingAccessWrapper Proxy to SwiftFileBackend */ + private $backend; + + protected function setUp() { + parent::setUp(); + + $this->backend = TestingAccessWrapper::newFromObject( + new SwiftFileBackend( array( + 'name' => 'local-swift-testing', + 'class' => 'SwiftFileBackend', + 'wikiId' => 'unit-testing', + 'lockManager' => LockManagerGroup::singleton()->get( 'fsLockManager' ), + 'swiftAuthUrl' => 'http://127.0.0.1:8080/auth', // unused + 'swiftUser' => 'test:tester', + 'swiftKey' => 'testing', + 'swiftTempUrlKey' => 'b3968d0207b54ece87cccc06515a89d4' // unused + ) ) + ); + } + + /** + * @dataProvider provider_testSanitzeHdrs + * @covers SwiftFileBackend::sanitzeHdrs + * @covers SwiftFileBackend::getCustomHeaders + */ + public function testSanitzeHdrs( $raw, $sanitized ) { + $hdrs = $this->backend->sanitizeHdrs( array( 'headers' => $raw ) ); + + $this->assertEquals( $hdrs, $sanitized, 'sanitizeHdrs() has expected result' ); + } + + public static function provider_testSanitzeHdrs() { + return array( + array( + array( + 'content-length' => 345, + 'content-type' => 'image+bitmap/jpeg', + 'content-disposition' => 'inline', + 'content-duration' => 35.6363, + 'content-Custom' => 'hello', + 'x-content-custom' => 'hello' + ), + array( + 'content-disposition' => 'inline', + 'content-duration' => 35.6363, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello' + ) + ), + array( + array( + 'content-length' => 345, + 'content-type' => 'image+bitmap/jpeg', + 'content-Disposition' => 'inline; filename=xxx; ' . str_repeat( 'o', 1024 ), + 'content-duration' => 35.6363, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello' + ), + array( + 'content-disposition' => 'inline;filename=xxx', + 'content-duration' => 35.6363, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello' + ) + ), + array( + array( + 'content-length' => 345, + 'content-type' => 'image+bitmap/jpeg', + 'content-disposition' => 'filename='. str_repeat( 'o', 1024 ) . ';inline', + 'content-duration' => 35.6363, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello' + ), + array( + 'content-disposition' => '', + 'content-duration' => 35.6363, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello' + ) + ) + ); + } + + /** + * @dataProvider provider_testGetMetadataHeaders + * @covers SwiftFileBackend::getMetadataHeaders + */ + public function testGetMetadataHeaders( $raw, $sanitized ) { + $hdrs = $this->backend->getMetadataHeaders( $raw ); + + $this->assertEquals( $hdrs, $sanitized, 'getMetadataHeaders() has expected result' ); + } + + public static function provider_testGetMetadataHeaders() { + return array( + array( + array( + 'content-length' => 345, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello', + 'x-object-meta-custom' => 5, + 'x-object-meta-sha1Base36' => 'a3deadfg...', + ), + array( + 'x-object-meta-custom' => 5, + 'x-object-meta-sha1base36' => 'a3deadfg...', + ) + ) + ); + } + + /** + * @dataProvider provider_testGetMetadata + * @covers SwiftFileBackend::getMetadata + */ + public function testGetMetadata( $raw, $sanitized ) { + $hdrs = $this->backend->getMetadata( $raw ); + + $this->assertEquals( $hdrs, $sanitized, 'getMetadata() has expected result' ); + } + + public static function provider_testGetMetadata() { + return array( + array( + array( + 'content-length' => 345, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello', + 'x-object-meta-custom' => 5, + 'x-object-meta-sha1Base36' => 'a3deadfg...', + ), + array( + 'custom' => 5, + 'sha1base36' => 'a3deadfg...', + ) + ) + ); + } +}
\ No newline at end of file diff --git a/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php b/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php new file mode 100644 index 00000000..681e3681 --- /dev/null +++ b/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php @@ -0,0 +1,138 @@ +<?php + +class FileBackendDBRepoWrapperTest extends MediaWikiTestCase { + protected $backendName = 'foo-backend'; + protected $repoName = 'pureTestRepo'; + + /** + * @dataProvider getBackendPathsProvider + * @covers FileBackendDBRepoWrapper::getBackendPaths + */ + public function testGetBackendPaths( + $mocks, + $latest, + $dbReadsExpected, + $dbReturnValue, + $originalPath, + $expectedBackendPath, + $message ) { + list( $dbMock, $backendMock, $wrapperMock ) = $mocks; + + $dbMock->expects( $dbReadsExpected ) + ->method( 'selectField' ) + ->will( $this->returnValue( $dbReturnValue ) ); + + $newPaths = $wrapperMock->getBackendPaths( array( $originalPath ), $latest ); + + $this->assertEquals( + $expectedBackendPath, + $newPaths[0], + $message ); + } + + public function getBackendPathsProvider() { + $prefix = 'mwstore://' . $this->backendName . '/' . $this->repoName; + $mocksForCaching = $this->getMocks(); + + return array( + array( + $mocksForCaching, + false, + $this->once(), + '96246614d75ba1703bdfd5d7660bb57407aaf5d9', + $prefix . '-public/f/o/foobar.jpg', + $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9', + 'Public path translated correctly', + ), + array( + $mocksForCaching, + false, + $this->never(), + '96246614d75ba1703bdfd5d7660bb57407aaf5d9', + $prefix . '-public/f/o/foobar.jpg', + $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9', + 'LRU cache leveraged', + ), + array( + $this->getMocks(), + true, + $this->once(), + '96246614d75ba1703bdfd5d7660bb57407aaf5d9', + $prefix . '-public/f/o/foobar.jpg', + $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9', + 'Latest obtained', + ), + array( + $this->getMocks(), + true, + $this->never(), + '96246614d75ba1703bdfd5d7660bb57407aaf5d9', + $prefix . '-deleted/f/o/foobar.jpg', + $prefix . '-original/f/o/o/foobar', + 'Deleted path translated correctly', + ), + array( + $this->getMocks(), + true, + $this->once(), + null, + $prefix . '-public/b/a/baz.jpg', + $prefix . '-public/b/a/baz.jpg', + 'Path left untouched if no sha1 can be found', + ), + ); + } + + /** + * @covers FileBackendDBRepoWrapper::getFileContentsMulti + */ + public function testGetFileContentsMulti() { + list( $dbMock, $backendMock, $wrapperMock ) = $this->getMocks(); + + $sha1Path = 'mwstore://' . $this->backendName . '/' . $this->repoName + . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9'; + $filenamePath = 'mwstore://' . $this->backendName . '/' . $this->repoName + . '-public/f/o/foobar.jpg'; + + $dbMock->expects( $this->once() ) + ->method( 'selectField' ) + ->will( $this->returnValue( '96246614d75ba1703bdfd5d7660bb57407aaf5d9' ) ); + + $backendMock->expects( $this->once() ) + ->method( 'getFileContentsMulti') + ->will( $this->returnValue( array( $sha1Path => 'foo' ) ) ); + + $result = $wrapperMock->getFileContentsMulti( array( 'srcs' => array( $filenamePath ) ) ); + + $this->assertEquals( + array( $filenamePath => 'foo' ), + $result, + 'File contents paths translated properly' + ); + } + + protected function getMocks() { + $dbMock = $this->getMockBuilder( 'DatabaseMysql' ) + ->disableOriginalConstructor() + ->getMock(); + + $backendMock = $this->getMock( 'FSFileBackend', + array(), + array( array( + 'name' => $this->backendName, + 'wikiId' => wfWikiId() + ) ) ); + + $wrapperMock = $this->getMock( 'FileBackendDBRepoWrapper', + array( 'getDB' ), + array( array( + 'backend' => $backendMock, + 'repoName' => $this->repoName, + 'dbHandleFactory' => null + ) ) ); + + $wrapperMock->expects( $this->any() )->method( 'getDB' )->will( $this->returnValue( $dbMock ) ); + + return array( $dbMock, $backendMock, $wrapperMock ); + } +} diff --git a/tests/phpunit/includes/filerepo/MigrateFileRepoLayoutTest.php b/tests/phpunit/includes/filerepo/MigrateFileRepoLayoutTest.php new file mode 100644 index 00000000..551d3a76 --- /dev/null +++ b/tests/phpunit/includes/filerepo/MigrateFileRepoLayoutTest.php @@ -0,0 +1,114 @@ +<?php + +class MigrateFileRepoLayoutTest extends MediaWikiTestCase { + protected $tmpPrefix; + protected $migratorMock; + protected $tmpFilepath; + protected $text = 'testing'; + + protected function setUp() { + parent::setUp(); + + $filename = 'Foo.png'; + + $this->tmpPrefix = wfTempDir() . '/migratefilelayout-test-' . time() . '-' . mt_rand(); + + $backend = new FSFileBackend( array( + 'name' => 'local-migratefilerepolayouttest', + 'wikiId' => wfWikiID(), + 'containerPaths' => array( + 'migratefilerepolayouttest-original' => "{$this->tmpPrefix}-original", + 'migratefilerepolayouttest-public' => "{$this->tmpPrefix}-public", + 'migratefilerepolayouttest-thumb' => "{$this->tmpPrefix}-thumb", + 'migratefilerepolayouttest-temp' => "{$this->tmpPrefix}-temp", + 'migratefilerepolayouttest-deleted' => "{$this->tmpPrefix}-deleted", + ) + ) ); + + $dbMock = $this->getMockBuilder( 'DatabaseMysql' ) + ->disableOriginalConstructor() + ->getMock(); + + $imageRow = new stdClass; + $imageRow->img_name = $filename; + $imageRow->img_sha1 = sha1( $this->text ); + + $dbMock->expects( $this->any() ) + ->method( 'select' ) + ->will( $this->onConsecutiveCalls( + new FakeResultWrapper( array( $imageRow ) ), // image + new FakeResultWrapper( array() ), // image + new FakeResultWrapper( array() ) // filearchive + ) ); + + $repoMock = $this->getMock( 'LocalRepo', + array( 'getMasterDB' ), + array( array( + 'name' => 'migratefilerepolayouttest', + 'backend' => $backend + ) ) ); + + $repoMock->expects( $this->any() )->method( 'getMasterDB' )->will( $this->returnValue( $dbMock ) ); + + $this->migratorMock = $this->getMock( 'MigrateFileRepoLayout', array( 'getRepo' ) ); + $this->migratorMock->expects( $this->any() )->method( 'getRepo' )->will( $this->returnValue( $repoMock ) ); + + $this->tmpFilepath = TempFSFile::factory( 'migratefilelayout-test-', 'png' )->getPath(); + + file_put_contents( $this->tmpFilepath, $this->text ); + + $hashPath = $repoMock->getHashPath( $filename ); + + $status = $repoMock->store( $this->tmpFilepath, 'public', $hashPath . $filename, FileRepo::OVERWRITE ); + } + + protected function deleteFilesRecursively( $directory ) { + foreach ( glob( $directory . '/*' ) as $file ) { + if ( is_dir( $file ) ) { + $this->deleteFilesRecursively( $file ); + } else { + unlink( $file ); + } + } + + rmdir( $directory ); + } + + protected function tearDown() { + foreach ( glob( $this->tmpPrefix . '*' ) as $directory ) { + $this->deleteFilesRecursively( $directory ); + } + + unlink( $this->tmpFilepath ); + + parent::tearDown(); + } + + public function testMigration() { + $this->migratorMock->loadParamsAndArgs( null, array( 'oldlayout' => 'name', 'newlayout' => 'sha1' ) ); + + ob_start(); + + $this->migratorMock->execute(); + + ob_end_clean(); + + $sha1 = sha1( $this->text ); + + $expectedOriginalFilepath = $this->tmpPrefix + . '-original/' + . substr( $sha1, 0, 1 ) + . '/' + . substr( $sha1, 1, 1 ) + . '/' + . substr( $sha1, 2, 1 ) + . '/' + . $sha1 ; + + $this->assertEquals( file_get_contents( $expectedOriginalFilepath ), $this->text, 'New sha1 file should be exist and have the right contents' ); + + $expectedPublicFilepath = $this->tmpPrefix . '-public/f/f8/Foo.png'; + + $this->assertEquals( file_get_contents( $expectedPublicFilepath ), $this->text, 'Existing name file should still and have the right contents' ); + } +} diff --git a/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php b/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php index 2c7f50c9..3b5347cd 100644 --- a/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php +++ b/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php @@ -6,7 +6,7 @@ */ class HtmlAutoCompleteSelectFieldTest extends MediaWikiTestCase { - var $options = array( + public $options = array( 'Bulgaria' => 'BGR', 'Burkina Faso' => 'BFA', 'Burundi' => 'BDI', diff --git a/tests/phpunit/includes/json/FormatJsonTest.php b/tests/phpunit/includes/json/FormatJsonTest.php index f0ac6acc..8bca3331 100644 --- a/tests/phpunit/includes/json/FormatJsonTest.php +++ b/tests/phpunit/includes/json/FormatJsonTest.php @@ -159,12 +159,12 @@ class FormatJsonTest extends MediaWikiTestCase { $this->assertJson( $json ); $st = FormatJson::parse( $json ); - $this->assertType( 'Status', $st ); + $this->assertInstanceOf( 'Status', $st ); $this->assertTrue( $st->isGood() ); $this->assertEquals( $expected, $st->getValue() ); $st = FormatJson::parse( $json, FormatJson::FORCE_ASSOC ); - $this->assertType( 'Status', $st ); + $this->assertInstanceOf( 'Status', $st ); $this->assertTrue( $st->isGood() ); $this->assertEquals( $value, $st->getValue() ); } @@ -230,7 +230,7 @@ class FormatJsonTest extends MediaWikiTestCase { } $st = FormatJson::parse( $value, FormatJson::TRY_FIXING ); - $this->assertType( 'Status', $st ); + $this->assertInstanceOf( 'Status', $st ); if ( $expected === false ) { $this->assertFalse( $st->isOK(), 'Expected isOK() == false' ); } else { @@ -256,7 +256,7 @@ class FormatJsonTest extends MediaWikiTestCase { */ public function testParseErrors( $value ) { $st = FormatJson::parse( $value ); - $this->assertType( 'Status', $st ); + $this->assertInstanceOf( 'Status', $st ); $this->assertFalse( $st->isOK() ); } @@ -313,7 +313,7 @@ class FormatJsonTest extends MediaWikiTestCase { */ public function testParseStripComments( $json, $expect ) { $st = FormatJson::parse( $json, FormatJson::STRIP_COMMENTS ); - $this->assertType( 'Status', $st ); + $this->assertInstanceOf( 'Status', $st ); $this->assertTrue( $st->isGood() ); $this->assertEquals( $expect, $st->getValue() ); } diff --git a/tests/phpunit/includes/libs/ArrayUtilsTest.php b/tests/phpunit/includes/libs/ArrayUtilsTest.php index b5ea7b72..32b150c7 100644 --- a/tests/phpunit/includes/libs/ArrayUtilsTest.php +++ b/tests/phpunit/includes/libs/ArrayUtilsTest.php @@ -23,11 +23,11 @@ class ArrayUtilsTest extends PHPUnit_Framework_TestCase { } function provideFindLowerBound() { - $self = $this; - $indexValueCallback = function ( $size ) use ( $self ) { - return function ( $val ) use ( $self, $size ) { - $self->assertTrue( $val >= 0 ); - $self->assertTrue( $val < $size ); + $that = $this; + $indexValueCallback = function ( $size ) use ( $that ) { + return function ( $val ) use ( $that, $size ) { + $that->assertTrue( $val >= 0 ); + $that->assertTrue( $val < $size ); return $val; }; }; @@ -212,7 +212,7 @@ class ArrayUtilsTest extends PHPUnit_Framework_TestCase { array(), array( 1 => 1 ), array( 1 ), - array( 1 => 1), + array( 1 => 1 ), ), array( array(), diff --git a/tests/phpunit/includes/libs/CSSMinTest.php b/tests/phpunit/includes/libs/CSSMinTest.php index 6142f967..7841f30f 100644 --- a/tests/phpunit/includes/libs/CSSMinTest.php +++ b/tests/phpunit/includes/libs/CSSMinTest.php @@ -102,12 +102,12 @@ class CSSMinTest extends MediaWikiTestCase { 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); }', + 'foo { prop: url(http://example.org/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); }', + 'foo { prop: url(http://example.org/bar.png); }', ), array( 'Guard against stripping double slashes from query', @@ -133,12 +133,7 @@ class CSSMinTest extends MediaWikiTestCase { $remotePath = 'http://localhost/w/'; $realOutput = CSSMin::remap( $input, $localPath, $remotePath ); - - $this->assertEquals( - $expectedOutput, - preg_replace( '/\d+-\d+-\d+T\d+:\d+:\d+Z/', 'timestamp', $realOutput ), - "CSSMin::remap: $message" - ); + $this->assertEquals( $expectedOutput, $realOutput, "CSSMin::remap: $message" ); } public static function provideIsRemoteUrl() { @@ -197,7 +192,7 @@ class CSSMinTest extends MediaWikiTestCase { array( 'Regular file', 'foo { background: url(red.gif); }', - 'foo { background: url(http://localhost/w/red.gif?timestamp); }', + 'foo { background: url(http://localhost/w/red.gif?34ac6); }', ), array( 'Regular file (missing)', @@ -242,12 +237,12 @@ class CSSMinTest extends MediaWikiTestCase { array( 'Embedded file', 'foo { /* @embed */ background: url(red.gif); }', - "foo { background: url($red); background: url(http://localhost/w/red.gif?timestamp)!ie; }", + "foo { background: url($red); background: url(http://localhost/w/red.gif?34ac6)!ie; }", ), array( 'Embedded file, other comments before the rule', "foo { /* Bar. */ /* @embed */ background: url(red.gif); }", - "foo { /* Bar. */ background: url($red); /* Bar. */ background: url(http://localhost/w/red.gif?timestamp)!ie; }", + "foo { /* Bar. */ background: url($red); /* Bar. */ background: url(http://localhost/w/red.gif?34ac6)!ie; }", ), array( 'Can not re-embed data: URIs', @@ -268,12 +263,12 @@ class CSSMinTest extends MediaWikiTestCase { 'Embedded file (inline @embed)', 'foo { background: /* @embed */ url(red.gif); }', "foo { background: url($red); " - . "background: url(http://localhost/w/red.gif?timestamp)!ie; }", + . "background: url(http://localhost/w/red.gif?34ac6)!ie; }", ), array( 'Can not embed large files', 'foo { /* @embed */ background: url(large.png); }', - "foo { background: url(http://localhost/w/large.png?timestamp); }", + "foo { background: url(http://localhost/w/large.png?e3d1f); }", ), array( 'SVG files are embedded without base64 encoding and unnecessary IE 6 and 7 fallback', @@ -283,55 +278,55 @@ class CSSMinTest extends MediaWikiTestCase { array( 'Two regular files in one rule', 'foo { background: url(red.gif), url(green.gif); }', - 'foo { background: url(http://localhost/w/red.gif?timestamp), ' - . 'url(http://localhost/w/green.gif?timestamp); }', + 'foo { background: url(http://localhost/w/red.gif?34ac6), ' + . 'url(http://localhost/w/green.gif?13651); }', ), array( 'Two embedded files in one rule', 'foo { /* @embed */ background: url(red.gif), url(green.gif); }', "foo { background: url($red), url($green); " - . "background: url(http://localhost/w/red.gif?timestamp), " - . "url(http://localhost/w/green.gif?timestamp)!ie; }", + . "background: url(http://localhost/w/red.gif?34ac6), " + . "url(http://localhost/w/green.gif?13651)!ie; }", ), array( 'Two embedded files in one rule (inline @embed)', 'foo { background: /* @embed */ url(red.gif), /* @embed */ url(green.gif); }', "foo { background: url($red), url($green); " - . "background: url(http://localhost/w/red.gif?timestamp), " - . "url(http://localhost/w/green.gif?timestamp)!ie; }", + . "background: url(http://localhost/w/red.gif?34ac6), " + . "url(http://localhost/w/green.gif?13651)!ie; }", ), array( 'Two embedded files in one rule (inline @embed), one too large', 'foo { background: /* @embed */ url(red.gif), /* @embed */ url(large.png); }', - "foo { background: url($red), url(http://localhost/w/large.png?timestamp); " - . "background: url(http://localhost/w/red.gif?timestamp), " - . "url(http://localhost/w/large.png?timestamp)!ie; }", + "foo { background: url($red), url(http://localhost/w/large.png?e3d1f); " + . "background: url(http://localhost/w/red.gif?34ac6), " + . "url(http://localhost/w/large.png?e3d1f)!ie; }", ), array( 'Practical example with some noise', 'foo { /* @embed */ background: #f9f9f9 url(red.gif) 0 0 no-repeat; }', "foo { background: #f9f9f9 url($red) 0 0 no-repeat; " - . "background: #f9f9f9 url(http://localhost/w/red.gif?timestamp) 0 0 no-repeat!ie; }", + . "background: #f9f9f9 url(http://localhost/w/red.gif?34ac6) 0 0 no-repeat!ie; }", ), array( 'Does not mess with other properties', 'foo { color: red; background: url(red.gif); font-size: small; }', - 'foo { color: red; background: url(http://localhost/w/red.gif?timestamp); font-size: small; }', + 'foo { color: red; background: url(http://localhost/w/red.gif?34ac6); font-size: small; }', ), array( 'Spacing and miscellanea not changed (1)', 'foo { background: url(red.gif); }', - 'foo { background: url(http://localhost/w/red.gif?timestamp); }', + 'foo { background: url(http://localhost/w/red.gif?34ac6); }', ), array( 'Spacing and miscellanea not changed (2)', 'foo {background:url(red.gif)}', - 'foo {background:url(http://localhost/w/red.gif?timestamp)}', + 'foo {background:url(http://localhost/w/red.gif?34ac6)}', ), array( 'Spaces within url() parentheses are ignored', 'foo { background: url( red.gif ); }', - 'foo { background: url(http://localhost/w/red.gif?timestamp); }', + 'foo { background: url(http://localhost/w/red.gif?34ac6); }', ), array( '@import rule to local file (should we remap this?)', @@ -351,22 +346,22 @@ class CSSMinTest extends MediaWikiTestCase { array( 'Simple case with comments after url', 'foo { prop: url(red.gif)/* some {funny;} comment */ ; }', - 'foo { prop: url(http://localhost/w/red.gif?timestamp)/* some {funny;} comment */ ; }', + 'foo { prop: url(http://localhost/w/red.gif?34ac6)/* some {funny;} comment */ ; }', ), array( 'Embedded file with comment before url', 'foo { /* @embed */ background: /* some {funny;} comment */ url(red.gif); }', - "foo { background: /* some {funny;} comment */ url($red); background: /* some {funny;} comment */ url(http://localhost/w/red.gif?timestamp)!ie; }", + "foo { background: /* some {funny;} comment */ url($red); background: /* some {funny;} comment */ url(http://localhost/w/red.gif?34ac6)!ie; }", ), array( 'Embedded file with comments inside and outside the rule', 'foo { /* @embed */ background: url(red.gif) /* some {foo;} comment */; /* some {bar;} comment */ }', - "foo { background: url($red) /* some {foo;} comment */; background: url(http://localhost/w/red.gif?timestamp) /* some {foo;} comment */!ie; /* some {bar;} comment */ }", + "foo { background: url($red) /* some {foo;} comment */; background: url(http://localhost/w/red.gif?34ac6) /* some {foo;} comment */!ie; /* some {bar;} comment */ }", ), array( 'Embedded file with comment outside the rule', 'foo { /* @embed */ background: url(red.gif); /* some {funny;} comment */ }', - "foo { background: url($red); background: url(http://localhost/w/red.gif?timestamp)!ie; /* some {funny;} comment */ }", + "foo { background: url($red); background: url(http://localhost/w/red.gif?34ac6)!ie; /* some {funny;} comment */ }", ), array( 'Rule with two urls, each with comments', diff --git a/tests/phpunit/includes/libs/IEUrlExtensionTest.php b/tests/phpunit/includes/libs/IEUrlExtensionTest.php index e96953ee..57668e50 100644 --- a/tests/phpunit/includes/libs/IEUrlExtensionTest.php +++ b/tests/phpunit/includes/libs/IEUrlExtensionTest.php @@ -170,4 +170,37 @@ class IEUrlExtensionTest extends PHPUnit_Framework_TestCase { 'Two dots' ); } + + /** + * @covers IEUrlExtension::findIE6Extension + */ + public function testScriptQuery() { + $this->assertEquals( + 'php', + IEUrlExtension::findIE6Extension( 'example.php?foo=a&bar=b' ), + 'Script with query' + ); + } + + /** + * @covers IEUrlExtension::findIE6Extension + */ + public function testEscapedScriptQuery() { + $this->assertEquals( + '', + IEUrlExtension::findIE6Extension( 'example%2Ephp?foo=a&bar=b' ), + 'Script with urlencoded dot and query' + ); + } + + /** + * @covers IEUrlExtension::findIE6Extension + */ + public function testEscapedScriptQueryDot() { + $this->assertEquals( + 'y', + IEUrlExtension::findIE6Extension( 'example%2Ephp?foo=a.x&bar=b.y' ), + 'Script with urlencoded dot and query with dot' + ); + } } diff --git a/tests/phpunit/includes/libs/IPSetTest.php b/tests/phpunit/includes/libs/IPSetTest.php deleted file mode 100644 index 5bbacef4..00000000 --- a/tests/phpunit/includes/libs/IPSetTest.php +++ /dev/null @@ -1,252 +0,0 @@ -<?php - -/** - * @group IPSet - */ -class IPSetTest extends PHPUnit_Framework_TestCase { - /** - * Provides test cases for IPSetTest::testIPSet - * - * Returns an array of test cases. Each case is an array of (description, - * config, tests). Description is just text output for failure messages, - * config is an array constructor argument for IPSet, and the tests are - * an array of IP => expected (boolean) result against the config dataset. - */ - public static function provideIPSets() { - return array( - array( - 'old_list_subset', - array( - '208.80.152.162', - '10.64.0.123', - '10.64.0.124', - '10.64.0.125', - '10.64.0.126', - '10.64.0.127', - '10.64.0.128', - '10.64.0.129', - '10.64.32.104', - '10.64.32.105', - '10.64.32.106', - '10.64.32.107', - '91.198.174.45', - '91.198.174.46', - '91.198.174.47', - '91.198.174.57', - '2620:0:862:1:A6BA:DBFF:FE30:CFB3', - '91.198.174.58', - '2620:0:862:1:A6BA:DBFF:FE38:FFDA', - '208.80.152.16', - '208.80.152.17', - '208.80.152.18', - '208.80.152.19', - '91.198.174.102', - '91.198.174.103', - '91.198.174.104', - '91.198.174.105', - '91.198.174.106', - '91.198.174.107', - '91.198.174.81', - '2620:0:862:1:26B6:FDFF:FEF5:B2D4', - '91.198.174.82', - '2620:0:862:1:26B6:FDFF:FEF5:ABB4', - '10.20.0.113', - '2620:0:862:102:26B6:FDFF:FEF5:AD9C', - '10.20.0.114', - '2620:0:862:102:26B6:FDFF:FEF5:7C38', - ), - array( - '0.0.0.0' => false, - '255.255.255.255' => false, - '10.64.0.122' => false, - '10.64.0.123' => true, - '10.64.0.124' => true, - '10.64.0.129' => true, - '10.64.0.130' => false, - '91.198.174.81' => true, - '91.198.174.80' => false, - '0::0' => false, - 'ffff:ffff:ffff:ffff:FFFF:FFFF:FFFF:FFFF' => false, - '2001:db8::1234' => false, - '2620:0:862:1:26b6:fdff:fef5:abb3' => false, - '2620:0:862:1:26b6:fdff:fef5:abb4' => true, - '2620:0:862:1:26b6:fdff:fef5:abb5' => false, - ), - ), - array( - 'new_cidr_set', - array( - '208.80.154.0/26', - '2620:0:861:1::/64', - '208.80.154.128/26', - '2620:0:861:2::/64', - '208.80.154.64/26', - '2620:0:861:3::/64', - '208.80.155.96/27', - '2620:0:861:4::/64', - '10.64.0.0/22', - '2620:0:861:101::/64', - '10.64.16.0/22', - '2620:0:861:102::/64', - '10.64.32.0/22', - '2620:0:861:103::/64', - '10.64.48.0/22', - '2620:0:861:107::/64', - '91.198.174.0/25', - '2620:0:862:1::/64', - '10.20.0.0/24', - '2620:0:862:102::/64', - '10.128.0.0/24', - '2620:0:863:101::/64', - '10.2.4.26', - ), - array( - '0.0.0.0' => false, - '255.255.255.255' => false, - '10.2.4.25' => false, - '10.2.4.26' => true, - '10.2.4.27' => false, - '10.20.0.255' => true, - '10.128.0.0' => true, - '10.64.17.55' => true, - '10.64.20.0' => false, - '10.64.27.207' => false, - '10.64.31.255' => false, - '0::0' => false, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' => false, - '2001:DB8::1' => false, - '2620:0:861:106::45' => false, - '2620:0:862:103::' => false, - '2620:0:862:102:10:20:0:113' => true, - ), - ), - array( - 'empty_set', - array(), - array( - '0.0.0.0' => false, - '255.255.255.255' => false, - '10.2.4.25' => false, - '10.2.4.26' => false, - '10.2.4.27' => false, - '10.20.0.255' => false, - '10.128.0.0' => false, - '10.64.17.55' => false, - '10.64.20.0' => false, - '10.64.27.207' => false, - '10.64.31.255' => false, - '0::0' => false, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' => false, - '2001:DB8::1' => false, - '2620:0:861:106::45' => false, - '2620:0:862:103::' => false, - '2620:0:862:102:10:20:0:113' => false, - ), - ), - array( - 'edge_cases', - array( - '0.0.0.0', - '255.255.255.255', - '::', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', - '10.10.10.10/25', // host bits intentional - ), - array( - '0.0.0.0' => true, - '255.255.255.255' => true, - '10.2.4.25' => false, - '10.2.4.26' => false, - '10.2.4.27' => false, - '10.20.0.255' => false, - '10.128.0.0' => false, - '10.64.17.55' => false, - '10.64.20.0' => false, - '10.64.27.207' => false, - '10.64.31.255' => false, - '0::0' => true, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' => true, - '2001:DB8::1' => false, - '2620:0:861:106::45' => false, - '2620:0:862:103::' => false, - '2620:0:862:102:10:20:0:113' => false, - '10.10.9.255' => false, - '10.10.10.0' => true, - '10.10.10.1' => true, - '10.10.10.10' => true, - '10.10.10.126' => true, - '10.10.10.127' => true, - '10.10.10.128' => false, - '10.10.10.177' => false, - '10.10.10.255' => false, - '10.10.11.0' => false, - ), - ), - array( - 'exercise_optimizer', - array( - 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fffe:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fffd:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fffc:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fffb:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fffa:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff9:8000/113', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff9:0/113', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff8:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff7:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff6:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff5:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff4:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff3:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff2:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff1:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff0:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffef:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffee:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffec:0/111', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffeb:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffea:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe9:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe8:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe7:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe6:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe5:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe4:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe3:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe2:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe1:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe0:0/110', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffc0:0/107', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffa0:0/107', - ), - array( - '0.0.0.0' => false, - '255.255.255.255' => false, - '::' => false, - 'ffff:ffff:ffff:ffff:ffff:ffff:ff9f:ffff' => false, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffa0:0' => true, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffc0:1234' => true, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffed:ffff' => true, - 'ffff:ffff:ffff:ffff:ffff:ffff:fff4:4444' => true, - 'ffff:ffff:ffff:ffff:ffff:ffff:fff9:8080' => true, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' => true, - ), - ), - ); - } - - /** - * Validates IPSet loading and matching code - * - * @covers IPSet - * @dataProvider provideIPSets - */ - public function testIPSet( $desc, array $cfg, array $tests ) { - $ipset = new IPSet( $cfg ); - foreach ( $tests as $ip => $expected ) { - $result = $ipset->match( $ip ); - $this->assertEquals( $expected, $result, "Incorrect match() result for $ip in dataset $desc" ); - } - } -} diff --git a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php index 149a28c1..d23534ed 100644 --- a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php +++ b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php @@ -140,6 +140,13 @@ class JavaScriptMinifierTest extends PHPUnit_Framework_TestCase { array( "5..toString();", "5..toString();" ), array( "5...toString();", false ), array( "5.\n.toString();", '5..toString();' ), + + // Boolean minification (!0 / !1) + array( "var a = { b: true };", "var a={b:!0};" ), + array( "var a = { true: 12 };", "var a={true:12};", false ), + array( "a.true = 12;", "a.true=12;", false ), + array( "a.foo = true;", "a.foo=!0;" ), + array( "a.foo = false;", "a.foo=!1;" ), ); } @@ -147,15 +154,17 @@ class JavaScriptMinifierTest extends PHPUnit_Framework_TestCase { * @dataProvider provideCases * @covers JavaScriptMinifier::minify */ - public function testJavaScriptMinifierOutput( $code, $expectedOutput ) { + public function testJavaScriptMinifierOutput( $code, $expectedOutput, $expectedValid = true ) { $minified = JavaScriptMinifier::minify( $code ); // JSMin+'s parser will throw an exception if output is not valid JS. // suppression of warnings needed for stupid crap - wfSuppressWarnings(); - $parser = new JSParser(); - wfRestoreWarnings(); - $parser->parse( $minified, 'minify-test.js', 1 ); + if ( $expectedValid ) { + MediaWiki\suppressWarnings(); + $parser = new JSParser(); + MediaWiki\restoreWarnings(); + $parser->parse( $minified, 'minify-test.js', 1 ); + } $this->assertEquals( $expectedOutput, diff --git a/tests/phpunit/includes/libs/ObjectFactoryTest.php b/tests/phpunit/includes/libs/ObjectFactoryTest.php index 92207325..aea037e0 100644 --- a/tests/phpunit/includes/libs/ObjectFactoryTest.php +++ b/tests/phpunit/includes/libs/ObjectFactoryTest.php @@ -26,11 +26,20 @@ class ObjectFactoryTest extends PHPUnit_Framework_TestCase { public function testClosureExpansionDisabled() { $obj = ObjectFactory::getObjectFromSpec( array( 'class' => 'ObjectFactoryTest_Fixture', - 'args' => array( function (){ return 'unwrapped'; }, ), + 'args' => array( function() { + return 'unwrapped'; + }, ), + 'calls' => array( + 'setter' => array( function() { + return 'unwrapped'; + }, ), + ), 'closure_expansion' => false, ) ); $this->assertInstanceOf( 'Closure', $obj->args[0] ); $this->assertSame( 'unwrapped', $obj->args[0]() ); + $this->assertInstanceOf( 'Closure', $obj->setterArgs[0] ); + $this->assertSame( 'unwrapped', $obj->setterArgs[0]() ); } /** @@ -39,22 +48,46 @@ class ObjectFactoryTest extends PHPUnit_Framework_TestCase { public function testClosureExpansionEnabled() { $obj = ObjectFactory::getObjectFromSpec( array( 'class' => 'ObjectFactoryTest_Fixture', - 'args' => array( function (){ return 'unwrapped'; }, ), + 'args' => array( function() { + return 'unwrapped'; + }, ), + 'calls' => array( + 'setter' => array( function() { + return 'unwrapped'; + }, ), + ), 'closure_expansion' => true, ) ); $this->assertInternalType( 'string', $obj->args[0] ); $this->assertSame( 'unwrapped', $obj->args[0] ); + $this->assertInternalType( 'string', $obj->setterArgs[0] ); + $this->assertSame( 'unwrapped', $obj->setterArgs[0] ); $obj = ObjectFactory::getObjectFromSpec( array( 'class' => 'ObjectFactoryTest_Fixture', - 'args' => array( function (){ return 'unwrapped'; }, ), + 'args' => array( function() { + return 'unwrapped'; + }, ), + 'calls' => array( + 'setter' => array( function() { + return 'unwrapped'; + }, ), + ), ) ); $this->assertInternalType( 'string', $obj->args[0] ); $this->assertSame( 'unwrapped', $obj->args[0] ); + $this->assertInternalType( 'string', $obj->setterArgs[0] ); + $this->assertSame( 'unwrapped', $obj->setterArgs[0] ); } } class ObjectFactoryTest_Fixture { public $args; - public function __construct( /*...*/ ) { $this->args = func_get_args(); } + public $setterArgs; + public function __construct( /*...*/ ) { + $this->args = func_get_args(); + } + public function setter( /*...*/ ) { + $this->setterArgs = func_get_args(); + } } diff --git a/tests/phpunit/includes/libs/ProcessCacheLRUTest.php b/tests/phpunit/includes/libs/ProcessCacheLRUTest.php index 43001979..cec662a9 100644 --- a/tests/phpunit/includes/libs/ProcessCacheLRUTest.php +++ b/tests/phpunit/includes/libs/ProcessCacheLRUTest.php @@ -70,7 +70,7 @@ class ProcessCacheLRUTest extends PHPUnit_Framework_TestCase { /** * @dataProvider provideInvalidConstructorArg - * @expectedException UnexpectedValueException + * @expectedException Wikimedia\Assert\ParameterAssertionException * @covers ProcessCacheLRU::__construct */ public function testConstructorGivenInvalidValue( $maxSize ) { diff --git a/tests/phpunit/includes/libs/SamplingStatsdClientTest.php b/tests/phpunit/includes/libs/SamplingStatsdClientTest.php new file mode 100644 index 00000000..be6732d5 --- /dev/null +++ b/tests/phpunit/includes/libs/SamplingStatsdClientTest.php @@ -0,0 +1,43 @@ +<?php + +use Liuggio\StatsdClient\Entity\StatsdData; + +class SamplingStatsdClientTest extends PHPUnit_Framework_TestCase { + /** + * @dataProvider samplingDataProvider + */ + public function testSampling( $data, $sampleRate, $seed, $expectWrite ) { + $sender = $this->getMock( 'Liuggio\StatsdClient\Sender\SenderInterface' ); + $sender->expects( $this->any() )->method( 'open' )->will( $this->returnValue( true ) ); + if ( $expectWrite ) { + $sender->expects( $this->once() )->method( 'write' ) + ->with( $this->anything(), $this->equalTo( $data ) ); + } else { + $sender->expects( $this->never() )->method( 'write' ); + } + mt_srand( $seed ); + $client = new SamplingStatsdClient( $sender ); + $client->send( $data, $sampleRate ); + } + + public function samplingDataProvider() { + $unsampled = new StatsdData(); + $unsampled->setKey( 'foo' ); + $unsampled->setValue( 1 ); + + $sampled = new StatsdData(); + $sampled->setKey( 'foo' ); + $sampled->setValue( 1 ); + $sampled->setSampleRate( '0.1' ); + + return array( + // $data, $sampleRate, $seed, $expectWrite + array( $unsampled, 1, 0 /*0.44*/, $unsampled ), + array( $sampled, 1, 0 /*0.44*/, null ), + array( $sampled, 1, 4 /*0.03*/, $sampled ), + array( $unsampled, 0.1, 4 /*0.03*/, $sampled ), + array( $sampled, 0.5, 0 /*0.44*/, null ), + array( $sampled, 0.5, 4 /*0.03*/, $sampled ), + ); + } +} diff --git a/tests/phpunit/includes/libs/XhprofTest.php b/tests/phpunit/includes/libs/XhprofTest.php index 2440fc08..77b188cf 100644 --- a/tests/phpunit/includes/libs/XhprofTest.php +++ b/tests/phpunit/includes/libs/XhprofTest.php @@ -255,43 +255,43 @@ class XhprofTest extends PHPUnit_Framework_TestCase { */ protected function getXhprofFixture( array $opts = array() ) { $xhprof = new Xhprof( $opts ); - $xhprof->loadRawData( array ( - 'foo==>bar' => array ( + $xhprof->loadRawData( array( + 'foo==>bar' => array( 'ct' => 2, 'wt' => 57, 'cpu' => 92, 'mu' => 1896, 'pmu' => 0, ), - 'foo==>strlen' => array ( + 'foo==>strlen' => array( 'ct' => 2, 'wt' => 21, 'cpu' => 141, 'mu' => 752, 'pmu' => 0, ), - 'bar==>bar@1' => array ( + 'bar==>bar@1' => array( 'ct' => 1, 'wt' => 18, 'cpu' => 19, 'mu' => 752, 'pmu' => 0, ), - 'main()==>foo' => array ( + 'main()==>foo' => array( 'ct' => 1, 'wt' => 304, 'cpu' => 307, 'mu' => 4008, 'pmu' => 0, ), - 'main()==>xhprof_disable' => array ( + 'main()==>xhprof_disable' => array( 'ct' => 1, 'wt' => 8, 'cpu' => 10, 'mu' => 768, 'pmu' => 392, ), - 'main()' => array ( + 'main()' => array( 'ct' => 1, 'wt' => 353, 'cpu' => 351, @@ -311,7 +311,7 @@ class XhprofTest extends PHPUnit_Framework_TestCase { */ protected function assertArrayStructure( $struct, $actual, $label = null ) { $this->assertInternalType( 'array', $actual, $label ); - $this->assertCount( count($struct), $actual, $label ); + $this->assertCount( count( $struct ), $actual, $label ); foreach ( $struct as $key => $type ) { $this->assertArrayHasKey( $key, $actual ); $this->assertInternalType( $type, $actual[$key] ); diff --git a/tests/phpunit/includes/libs/composer/ComposerLockTest.php b/tests/phpunit/includes/libs/composer/ComposerLockTest.php index b5fd5f6e..cac3b101 100644 --- a/tests/phpunit/includes/libs/composer/ComposerLockTest.php +++ b/tests/phpunit/includes/libs/composer/ComposerLockTest.php @@ -27,34 +27,95 @@ class ComposerLockTest extends MediaWikiTestCase { 'wikimedia/cdb' => array( 'version' => '1.0.1', 'type' => 'library', + 'licenses' => array( 'GPL-2.0' ), + 'authors' => array( + array( + 'name' => 'Tim Starling', + 'email' => 'tstarling@wikimedia.org', + ), + array( + 'name' => 'Chad Horohoe', + 'email' => 'chad@wikimedia.org', + ), + ), + 'description' => 'Constant Database (CDB) wrapper library for PHP. Provides pure-PHP fallback when dba_* functions are absent.', ), 'cssjanus/cssjanus' => array( 'version' => '1.1.1', 'type' => 'library', + 'licenses' => array( 'Apache-2.0' ), + 'authors' => array(), + 'description' => 'Convert CSS stylesheets between left-to-right and right-to-left.', ), 'leafo/lessphp' => array( 'version' => '0.5.0', 'type' => 'library', + 'licenses' => array( 'MIT', 'GPL-3.0' ), + 'authors' => array( + array( + 'name' => 'Leaf Corcoran', + 'email' => 'leafot@gmail.com', + 'homepage' => 'http://leafo.net', + ), + ), + 'description' => 'lessphp is a compiler for LESS written in PHP.', ), 'psr/log' => array( 'version' => '1.0.0', 'type' => 'library', + 'licenses' => array( 'MIT' ), + 'authors' => array( + array( + 'name' => 'PHP-FIG', + 'homepage' => 'http://www.php-fig.org/', + ), + ), + 'description' => 'Common interface for logging libraries', ), 'oojs/oojs-ui' => array( 'version' => '0.6.0', 'type' => 'library', + 'licenses' => array( 'MIT' ), + 'authors' => array(), + 'description' => '', ), 'composer/installers' => array( 'version' => '1.0.19', 'type' => 'composer-installer', + 'licenses' => array( 'MIT' ), + 'authors' => array( + array( + 'name' => 'Kyle Robinson Young', + 'email' => 'kyle@dontkry.com', + 'homepage' => 'https://github.com/shama', + ), + ), + 'description' => 'A multi-framework Composer library installer', ), 'mediawiki/translate' => array( 'version' => '2014.12', 'type' => 'mediawiki-extension', + 'licenses' => array( 'GPL-2.0+' ), + 'authors' => array( + array( + 'name' => 'Niklas Laxström', + 'email' => 'niklas.laxstrom@gmail.com', + 'role' => 'Lead nitpicker', + ), + array( + 'name' => 'Siebrand Mazeland', + 'email' => 's.mazeland@xs4all.nl', + 'role' => 'Developer', + ), + ), + 'description' => 'The only standard solution to translate any kind of text with an avant-garde web interface within MediaWiki, including your documentation and software', ), 'mediawiki/universal-language-selector' => array( 'version' => '2014.12', 'type' => 'mediawiki-extension', + 'licenses' => array( 'GPL-2.0+', 'MIT' ), + 'authors' => array(), + 'description' => 'The primary aim is to allow users to select a language and configure its support in an easy way. Main features are language selection, input methods and web fonts.', ), ), $lock->getInstalledDependencies(), false, true ); } diff --git a/tests/phpunit/includes/logging/BlockLogFormatterTest.php b/tests/phpunit/includes/logging/BlockLogFormatterTest.php new file mode 100644 index 00000000..c7dc641d --- /dev/null +++ b/tests/phpunit/includes/logging/BlockLogFormatterTest.php @@ -0,0 +1,372 @@ +<?php + +class BlockLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideBlockLogDatabaseRows() { + return array( + // Current log format + array( + array( + 'type' => 'block', + 'action' => 'block', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + '5::duration' => 'infinite', + '6::flags' => 'anononly', + ), + ), + array( + 'text' => 'Sysop blocked Logtestuser with an expiry time of indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + + // Old legacy log + array( + array( + 'type' => 'block', + 'action' => 'block', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + 'infinite', + 'anononly', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop blocked Logtestuser with an expiry time of indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + + // Old legacy log without flag + array( + array( + 'type' => 'block', + 'action' => 'block', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + 'infinite', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop blocked Logtestuser with an expiry time of indefinite', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array(), + ), + ), + ), + + // Very old legacy log without duration + array( + array( + 'type' => 'block', + 'action' => 'block', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array(), + ), + array( + 'legacy' => true, + 'text' => 'Sysop blocked Logtestuser with an expiry time of indefinite', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array(), + ), + ), + ), + ); + } + + /** + * @dataProvider provideBlockLogDatabaseRows + */ + public function testBlockLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideReblockLogDatabaseRows() { + return array( + // Current log format + array( + array( + 'type' => 'block', + 'action' => 'reblock', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + '5::duration' => 'infinite', + '6::flags' => 'anononly', + ), + ), + array( + 'text' => 'Sysop changed block settings for Logtestuser with an expiry time of' + . ' indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + + // Old log + array( + array( + 'type' => 'block', + 'action' => 'reblock', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + 'infinite', + 'anononly', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop changed block settings for Logtestuser with an expiry time of' + . ' indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + + // Older log without flag + array( + array( + 'type' => 'block', + 'action' => 'reblock', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + 'infinite', + ) + ), + array( + 'legacy' => true, + 'text' => 'Sysop changed block settings for Logtestuser with an expiry time of indefinite', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array(), + ), + ), + ), + ); + } + + /** + * @dataProvider provideReblockLogDatabaseRows + */ + public function testReblockLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideUnblockLogDatabaseRows() { + return array( + // Current log format + array( + array( + 'type' => 'block', + 'action' => 'unblock', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array(), + ), + array( + 'text' => 'Sysop unblocked Logtestuser', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideUnblockLogDatabaseRows + */ + public function testUnblockLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideSuppressBlockLogDatabaseRows() { + return array( + // Current log format + array( + array( + 'type' => 'suppress', + 'action' => 'block', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + '5::duration' => 'infinite', + '6::flags' => 'anononly', + ), + ), + array( + 'text' => 'Sysop blocked Logtestuser with an expiry time of indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + + // legacy log + array( + array( + 'type' => 'suppress', + 'action' => 'block', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + 'infinite', + 'anononly', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop blocked Logtestuser with an expiry time of indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideSuppressBlockLogDatabaseRows + */ + public function testSuppressBlockLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideSuppressReblockLogDatabaseRows() { + return array( + // Current log format + array( + array( + 'type' => 'suppress', + 'action' => 'reblock', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + '5::duration' => 'infinite', + '6::flags' => 'anononly', + ), + ), + array( + 'text' => 'Sysop changed block settings for Logtestuser with an expiry time of' + . ' indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'suppress', + 'action' => 'reblock', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + 'infinite', + 'anononly', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop changed block settings for Logtestuser with an expiry time of' + . ' indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideSuppressReblockLogDatabaseRows + */ + public function testSuppressReblockLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/DeleteLogFormatterTest.php b/tests/phpunit/includes/logging/DeleteLogFormatterTest.php new file mode 100644 index 00000000..28e7efaf --- /dev/null +++ b/tests/phpunit/includes/logging/DeleteLogFormatterTest.php @@ -0,0 +1,527 @@ +<?php + +class DeleteLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideDeleteLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'delete', + 'action' => 'delete', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array(), + ), + array( + 'text' => 'User deleted page Page', + 'api' => array(), + ), + ), + + // Legacy format + array( + array( + 'type' => 'delete', + 'action' => 'delete', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array(), + ), + array( + 'legacy' => true, + 'text' => 'User deleted page Page', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideDeleteLogDatabaseRows + */ + public function testDeleteLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideRestoreLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'delete', + 'action' => 'restore', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array(), + ), + array( + 'text' => 'User restored page Page', + 'api' => array(), + ), + ), + + // Legacy format + array( + array( + 'type' => 'delete', + 'action' => 'restore', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array(), + ), + array( + 'legacy' => true, + 'text' => 'User restored page Page', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideRestoreLogDatabaseRows + */ + public function testRestoreLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideRevisionLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'delete', + 'action' => 'revision', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::type' => 'archive', + '5::ids' => array( '1', '3', '4' ), + '6::ofield' => '1', + '7::nfield' => '2', + ), + ), + array( + 'text' => 'User changed visibility of 3 revisions on page Page: edit summary ' + . 'hidden and content unhidden', + 'api' => array( + 'type' => 'archive', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 2, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => false, + ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'delete', + 'action' => 'revision', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + 'archive', + '1,3,4', + 'ofield=1', + 'nfield=2', + ), + ), + array( + 'legacy' => true, + 'text' => 'User changed visibility of 3 revisions on page Page: edit summary ' + . 'hidden and content unhidden', + 'api' => array( + 'type' => 'archive', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 2, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => false, + ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideRevisionLogDatabaseRows + */ + public function testRevisionLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideEventLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'delete', + 'action' => 'event', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::ids' => array( '1', '3', '4' ), + '5::ofield' => '1', + '6::nfield' => '2', + ), + ), + array( + 'text' => 'User changed visibility of 3 log events on Page: edit summary hidden ' + . 'and content unhidden', + 'api' => array( + 'type' => 'logging', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 2, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => false, + ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'delete', + 'action' => 'event', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '1,3,4', + 'ofield=1', + 'nfield=2', + ), + ), + array( + 'legacy' => true, + 'text' => 'User changed visibility of 3 log events on Page: edit summary hidden ' + . 'and content unhidden', + 'api' => array( + 'type' => 'logging', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 2, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => false, + ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideEventLogDatabaseRows + */ + public function testEventLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideSuppressRevisionLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'suppress', + 'action' => 'revision', + 'comment' => 'Suppress comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::type' => 'archive', + '5::ids' => array( '1', '3', '4' ), + '6::ofield' => '1', + '7::nfield' => '10', + ), + ), + array( + 'text' => 'User secretly changed visibility of 3 revisions on page Page: edit ' + . 'summary hidden, content unhidden and applied restrictions to administrators', + 'api' => array( + 'type' => 'archive', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 10, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => true, + ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'suppress', + 'action' => 'revision', + 'comment' => 'Suppress comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + 'archive', + '1,3,4', + 'ofield=1', + 'nfield=10', + ), + ), + array( + 'legacy' => true, + 'text' => 'User secretly changed visibility of 3 revisions on page Page: edit ' + . 'summary hidden, content unhidden and applied restrictions to administrators', + 'api' => array( + 'type' => 'archive', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 10, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => true, + ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideSuppressRevisionLogDatabaseRows + */ + public function testSuppressRevisionLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideSuppressEventLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'suppress', + 'action' => 'event', + 'comment' => 'Suppress comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::ids' => array( '1', '3', '4' ), + '5::ofield' => '1', + '6::nfield' => '10', + ), + ), + array( + 'text' => 'User secretly changed visibility of 3 log events on Page: edit ' + . 'summary hidden, content unhidden and applied restrictions to administrators', + 'api' => array( + 'type' => 'logging', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 10, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => true, + ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'suppress', + 'action' => 'event', + 'comment' => 'Suppress comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '1,3,4', + 'ofield=1', + 'nfield=10', + ), + ), + array( + 'legacy' => true, + 'text' => 'User secretly changed visibility of 3 log events on Page: edit ' + . 'summary hidden, content unhidden and applied restrictions to administrators', + 'api' => array( + 'type' => 'logging', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 10, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => true, + ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideSuppressEventLogDatabaseRows + */ + public function testSuppressEventLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideSuppressDeleteLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'suppress', + 'action' => 'delete', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array(), + ), + array( + 'text' => 'User suppressed page Page', + 'api' => array(), + ), + ), + + // Legacy format + array( + array( + 'type' => 'suppress', + 'action' => 'delete', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array(), + ), + array( + 'legacy' => true, + 'text' => 'User suppressed page Page', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideSuppressDeleteLogDatabaseRows + */ + public function testSuppressDeleteLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/LogFormatterTest.php b/tests/phpunit/includes/logging/LogFormatterTest.php index 515990e6..844c9afb 100644 --- a/tests/phpunit/includes/logging/LogFormatterTest.php +++ b/tests/phpunit/includes/logging/LogFormatterTest.php @@ -1,4 +1,5 @@ <?php + /** * @group Database */ @@ -19,6 +20,16 @@ class LogFormatterTest extends MediaWikiLangTestCase { */ protected $context; + /** + * @var Title + */ + protected $target; + + /** + * @var string + */ + protected $user_comment; + protected function setUp() { parent::setUp(); @@ -35,12 +46,15 @@ class LogFormatterTest extends MediaWikiLangTestCase { Language::getLocalisationCache()->recache( $wgLang->getCode() ); $this->user = User::newFromName( 'Testuser' ); - $this->title = Title::newMainPage(); + $this->title = Title::newFromText( 'SomeTitle' ); + $this->target = Title::newFromText( 'TestTarget' ); $this->context = new RequestContext(); $this->context->setUser( $this->user ); $this->context->setTitle( $this->title ); $this->context->setLanguage( $wgLang ); + + $this->user_comment = '<User comment about action>'; } protected function tearDown() { @@ -292,4 +306,309 @@ class LogFormatterTest extends MediaWikiLangTestCase { array( '4:user-link:key', 'foo', array( 'key' => 'Foo' ) ), ); } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeBlock() { + $sep = $this->context->msg( 'colon-separator' )->text(); + + # block/block + $this->assertIRCComment( + $this->context->msg( 'blocklogentry', 'SomeTitle', 'duration', '(flags)' )->plain() + . $sep . $this->user_comment, + 'block', 'block', + array( + '5::duration' => 'duration', + '6::flags' => 'flags', + ), + $this->user_comment + ); + # block/block - legacy + $this->assertIRCComment( + $this->context->msg( 'blocklogentry', 'SomeTitle', 'duration', '(flags)' )->plain() + . $sep . $this->user_comment, + 'block', 'block', + array( + 'duration', + 'flags', + ), + $this->user_comment, + '', + true + ); + # block/unblock + $this->assertIRCComment( + $this->context->msg( 'unblocklogentry', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'block', 'unblock', + array(), + $this->user_comment + ); + # block/reblock + $this->assertIRCComment( + $this->context->msg( 'reblock-logentry', 'SomeTitle', 'duration', '(flags)' )->plain() + . $sep . $this->user_comment, + 'block', 'reblock', + array( + '5::duration' => 'duration', + '6::flags' => 'flags', + ), + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeDelete() { + $sep = $this->context->msg( 'colon-separator' )->text(); + + # delete/delete + $this->assertIRCComment( + $this->context->msg( 'deletedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'delete', 'delete', + array(), + $this->user_comment + ); + + # delete/restore + $this->assertIRCComment( + $this->context->msg( 'undeletedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'delete', 'restore', + array(), + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public 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::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeMove() { + $move_params = array( + '4::target' => $this->target->getPrefixedText(), + '5::noredir' => 0, + ); + $sep = $this->context->msg( 'colon-separator' )->text(); + + # move/move + $this->assertIRCComment( + $this->context->msg( '1movedto2', 'SomeTitle', 'TestTarget' ) + ->plain() . $sep . $this->user_comment, + 'move', 'move', + $move_params, + $this->user_comment + ); + + # move/move_redir + $this->assertIRCComment( + $this->context->msg( '1movedto2_redir', 'SomeTitle', 'TestTarget' ) + ->plain() . $sep . $this->user_comment, + 'move', 'move_redir', + $move_params, + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public 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::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeProtect() { + $protectParams = array( + '[edit=sysop] (indefinite) [move=sysop] (indefinite)' + ); + $sep = $this->context->msg( 'colon-separator' )->text(); + + # protect/protect + $this->assertIRCComment( + $this->context->msg( 'protectedarticle', 'SomeTitle ' . $protectParams[0] ) + ->plain() . $sep . $this->user_comment, + 'protect', 'protect', + $protectParams, + $this->user_comment + ); + + # protect/unprotect + $this->assertIRCComment( + $this->context->msg( 'unprotectedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'protect', 'unprotect', + array(), + $this->user_comment + ); + + # protect/modify + $this->assertIRCComment( + $this->context->msg( 'modifiedarticleprotection', 'SomeTitle ' . $protectParams[0] ) + ->plain() . $sep . $this->user_comment, + 'protect', 'modify', + $protectParams, + $this->user_comment + ); + + # protect/move_prot + $this->assertIRCComment( + $this->context->msg( 'movedarticleprotection', 'SomeTitle', 'OldTitle' ) + ->plain() . $sep . $this->user_comment, + 'protect', 'move_prot', + array( + '4::oldtitle' => 'OldTitle' + ), + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeUpload() { + $sep = $this->context->msg( 'colon-separator' )->text(); + + # upload/upload + $this->assertIRCComment( + $this->context->msg( 'uploadedimage', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'upload', 'upload', + array(), + $this->user_comment + ); + + # upload/overwrite + $this->assertIRCComment( + $this->context->msg( 'overwroteimage', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'upload', 'overwrite', + array(), + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeMerge() { + $sep = $this->context->msg( 'colon-separator' )->text(); + + # merge/merge + $this->assertIRCComment( + $this->context->msg( 'pagemerge-logentry', 'SomeTitle', 'Dest', 'timestamp' )->plain() + . $sep . $this->user_comment, + 'merge', 'merge', + array( + '4::dest' => 'Dest', + '5::mergepoint' => 'timestamp', + ), + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeImport() { + $sep = $this->context->msg( 'colon-separator' )->text(); + + # import/upload + $msg = $this->context->msg( 'import-logentry-upload', 'SomeTitle' )->plain() . + $sep . + $this->user_comment; + $this->assertIRCComment( + $msg, + 'import', 'upload', + array(), + $this->user_comment + ); + + # import/interwiki + $msg = $this->context->msg( 'import-logentry-interwiki', 'SomeTitle' )->plain() . + $sep . + $this->user_comment; + $this->assertIRCComment( + $msg, + 'import', 'interwiki', + array(), + $this->user_comment + ); + } + + /** + * @param string $expected Expected IRC text without colors codes + * @param string $type Log type (move, delete, suppress, patrol ...) + * @param string $action A log type action + * @param array $params + * @param string $comment (optional) A comment for the log action + * @param string $msg (optional) A message for PHPUnit :-) + */ + protected function assertIRCComment( $expected, $type, $action, $params, + $comment = null, $msg = '', $legacy = false + ) { + $logEntry = new ManualLogEntry( $type, $action ); + $logEntry->setPerformer( $this->user ); + $logEntry->setTarget( $this->title ); + if ( $comment !== null ) { + $logEntry->setComment( $comment ); + } + $logEntry->setParameters( $params ); + $logEntry->setLegacy( $legacy ); + + $formatter = LogFormatter::newFromEntry( $logEntry ); + $formatter->setContext( $this->context ); + + // Apply the same transformation as done in IRCColourfulRCFeedFormatter::getLine for rc_comment + $ircRcComment = IRCColourfulRCFeedFormatter::cleanupForIRC( $formatter->getIRCActionComment() ); + + $this->assertEquals( + $expected, + $ircRcComment, + $msg + ); + } + } diff --git a/tests/phpunit/includes/logging/LogFormatterTestCase.php b/tests/phpunit/includes/logging/LogFormatterTestCase.php new file mode 100644 index 00000000..e88452b7 --- /dev/null +++ b/tests/phpunit/includes/logging/LogFormatterTestCase.php @@ -0,0 +1,65 @@ +<?php + +/** + * @since 1.26 + */ +abstract class LogFormatterTestCase extends MediaWikiLangTestCase { + + public function doTestLogFormatter( $row, $extra ) { + RequestContext::resetMain(); + $row = $this->expandDatabaseRow( $row, $this->isLegacy( $extra ) ); + + $formatter = LogFormatter::newFromRow( $row ); + + $this->assertEquals( + $extra['text'], + self::removeSomeHtml( $formatter->getActionText() ), + 'Action text is equal to expected text' + ); + + $this->assertSame( // ensure types and array key order + $extra['api'], + self::removeApiMetaData( $formatter->formatParametersForApi() ), + 'Api log params is equal to expected array' + ); + } + + protected function isLegacy( $extra ) { + return isset( $extra['legacy'] ) && $extra['legacy']; + } + + protected function expandDatabaseRow( $data, $legacy ) { + return array( + // no log_id because no insert in database + 'log_type' => $data['type'], + 'log_action' => $data['action'], + 'log_timestamp' => isset( $data['timestamp'] ) ? $data['timestamp'] : wfTimestampNow(), + 'log_user' => isset( $data['user'] ) ? $data['user'] : 0, + 'log_user_text' => isset( $data['user_text'] ) ? $data['user_text'] : 'User', + 'log_namespace' => isset( $data['namespace'] ) ? $data['namespace'] : NS_MAIN, + 'log_title' => isset( $data['title'] ) ? $data['title'] : 'Main_Page', + 'log_page' => isset( $data['page'] ) ? $data['page'] : 0, + 'log_comment' => isset( $data['comment'] ) ? $data['comment'] : '', + 'log_params' => $legacy + ? LogPage::makeParamBlob( $data['params'] ) + : LogEntryBase::makeParamBlob( $data['params'] ), + 'log_deleted' => isset( $data['deleted'] ) ? $data['deleted'] : 0, + ); + } + + private static function removeSomeHtml( $html ) { + $html = str_replace( '"', '"', $html ); + return trim( preg_replace( '/<(a|span)[^>]*>([^<]*)<\/\1>/', '$2', $html ) ); + } + + private static function removeApiMetaData( $val ) { + if ( is_array( $val ) ) { + unset( $val['_element'] ); + unset( $val['_type'] ); + foreach ( $val as $key => $value ) { + $val[$key] = self::removeApiMetaData( $value ); + } + } + return $val; + } +} diff --git a/tests/phpunit/includes/logging/MergeLogFormatterTest.php b/tests/phpunit/includes/logging/MergeLogFormatterTest.php new file mode 100644 index 00000000..2ff0ddf5 --- /dev/null +++ b/tests/phpunit/includes/logging/MergeLogFormatterTest.php @@ -0,0 +1,67 @@ +<?php + +class MergeLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideMergeLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'merge', + 'action' => 'merge', + 'comment' => 'Merge comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + '4::dest' => 'NewPage', + '5::mergepoint' => '20140804160710', + ), + ), + array( + 'text' => 'User merged OldPage into NewPage (revisions up to 16:07, 4 August 2014)', + 'api' => array( + 'dest_ns' => 0, + 'dest_title' => 'NewPage', + 'mergepoint' => '2014-08-04T16:07:10Z', + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'merge', + 'action' => 'merge', + 'comment' => 'merge comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + '20140804160710', + ), + ), + array( + 'legacy' => true, + 'text' => 'User merged OldPage into NewPage (revisions up to 16:07, 4 August 2014)', + 'api' => array( + 'dest_ns' => 0, + 'dest_title' => 'NewPage', + 'mergepoint' => '2014-08-04T16:07:10Z', + ), + ), + ), + ); + } + + /** + * @dataProvider provideMergeLogDatabaseRows + */ + public function testMergeLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/MoveLogFormatterTest.php b/tests/phpunit/includes/logging/MoveLogFormatterTest.php new file mode 100644 index 00000000..fdc4b7e1 --- /dev/null +++ b/tests/phpunit/includes/logging/MoveLogFormatterTest.php @@ -0,0 +1,270 @@ +<?php + +class MoveLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideMoveLogDatabaseRows() { + return array( + // Current format - with redirect + array( + array( + 'type' => 'move', + 'action' => 'move', + 'comment' => 'move comment with redirect', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + '4::target' => 'NewPage', + '5::noredir' => '0', + ), + ), + array( + 'text' => 'User moved page OldPage to NewPage', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => false, + ), + ), + ), + + // Current format - without redirect + array( + array( + 'type' => 'move', + 'action' => 'move', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + '4::target' => 'NewPage', + '5::noredir' => '1', + ), + ), + array( + 'text' => 'User moved page OldPage to NewPage without leaving a redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => true, + ), + ), + ), + + // legacy format - with redirect + array( + array( + 'type' => 'move', + 'action' => 'move', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + '', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved page OldPage to NewPage', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => false, + ), + ), + ), + + // legacy format - without redirect + array( + array( + 'type' => 'move', + 'action' => 'move', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + '1', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved page OldPage to NewPage without leaving a redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => true, + ), + ), + ), + + // old format without flag for redirect suppression + array( + array( + 'type' => 'move', + 'action' => 'move', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved page OldPage to NewPage', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => false, + ), + ), + ), + ); + } + + /** + * @dataProvider provideMoveLogDatabaseRows + */ + public function testMoveLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideMoveRedirLogDatabaseRows() { + return array( + // Current format - with redirect + array( + array( + 'type' => 'move', + 'action' => 'move_redir', + 'comment' => 'move comment with redirect', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + '4::target' => 'NewPage', + '5::noredir' => '0', + ), + ), + array( + 'text' => 'User moved page OldPage to NewPage over redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => false, + ), + ), + ), + + // Current format - without redirect + array( + array( + 'type' => 'move', + 'action' => 'move_redir', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + '4::target' => 'NewPage', + '5::noredir' => '1', + ), + ), + array( + 'text' => 'User moved page OldPage to NewPage over a redirect without leaving a redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => true, + ), + ), + ), + + // legacy format - with redirect + array( + array( + 'type' => 'move', + 'action' => 'move_redir', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + '', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved page OldPage to NewPage over redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => false, + ), + ), + ), + + // legacy format - without redirect + array( + array( + 'type' => 'move', + 'action' => 'move_redir', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + '1', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved page OldPage to NewPage over a redirect without leaving a redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => true, + ), + ), + ), + + // old format without flag for redirect suppression + array( + array( + 'type' => 'move', + 'action' => 'move_redir', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved page OldPage to NewPage over redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => false, + ), + ), + ), + ); + } + + /** + * @dataProvider provideMoveRedirLogDatabaseRows + */ + public function testMoveRedirLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/NewUsersLogFormatterTest.php b/tests/phpunit/includes/logging/NewUsersLogFormatterTest.php new file mode 100644 index 00000000..5b03370d --- /dev/null +++ b/tests/phpunit/includes/logging/NewUsersLogFormatterTest.php @@ -0,0 +1,207 @@ +<?php + +/** + * @group Database + */ +class NewUsersLogFormatterTest extends LogFormatterTestCase { + + protected function setUp() { + parent::setUp(); + + // Register LogHandler, see $wgNewUserLog in Setup.php + $this->mergeMwGlobalArrayValue( 'wgLogActionsHandlers', array( + 'newusers/newusers' => 'NewUsersLogFormatter', + 'newusers/create' => 'NewUsersLogFormatter', + 'newusers/create2' => 'NewUsersLogFormatter', + 'newusers/byemail' => 'NewUsersLogFormatter', + 'newusers/autocreate' => 'NewUsersLogFormatter', + ) ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideNewUsersLogDatabaseRows() { + return array( + // Only old logs + array( + array( + 'type' => 'newusers', + 'action' => 'newusers', + 'comment' => 'newusers comment', + 'user' => 0, + 'user_text' => 'New user', + 'namespace' => NS_USER, + 'title' => 'New user', + 'params' => array(), + ), + array( + 'legacy' => true, + 'text' => 'User account New user was created', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideNewUsersLogDatabaseRows + */ + public function testNewUsersLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideCreateLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'newusers', + 'action' => 'create', + 'comment' => 'newusers comment', + 'user' => 0, + 'user_text' => 'New user', + 'namespace' => NS_USER, + 'title' => 'New user', + 'params' => array( + '4::userid' => 1, + ), + ), + array( + 'text' => 'User account New user was created', + 'api' => array( + 'userid' => 1, + ), + ), + ), + ); + } + + /** + * @dataProvider provideCreateLogDatabaseRows + */ + public function testCreateLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideCreate2LogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'newusers', + 'action' => 'create2', + 'comment' => 'newusers comment', + 'user' => 0, + 'user_text' => 'User', + 'namespace' => NS_USER, + 'title' => 'UTSysop', + 'params' => array( + '4::userid' => 1, + ), + ), + array( + 'text' => 'User account UTSysop was created by User', + 'api' => array( + 'userid' => 1, + ), + ), + ), + ); + } + + /** + * @dataProvider provideCreate2LogDatabaseRows + */ + public function testCreate2LogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideByemailLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'newusers', + 'action' => 'byemail', + 'comment' => 'newusers comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'UTSysop', + 'params' => array( + '4::userid' => 1, + ), + ), + array( + 'text' => 'User account UTSysop was created by Sysop and password was sent by email', + 'api' => array( + 'userid' => 1, + ), + ), + ), + ); + } + + /** + * @dataProvider provideByemailLogDatabaseRows + */ + public function testByemailLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideAutocreateLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'newusers', + 'action' => 'autocreate', + 'comment' => 'newusers comment', + 'user' => 0, + 'user_text' => 'New user', + 'namespace' => NS_USER, + 'title' => 'New user', + 'params' => array( + '4::userid' => 1, + ), + ), + array( + 'text' => 'User account New user was created automatically', + 'api' => array( + 'userid' => 1, + ), + ), + ), + ); + } + + /** + * @dataProvider provideAutocreateLogDatabaseRows + */ + public function testAutocreateLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/PageLangLogFormatterTest.php b/tests/phpunit/includes/logging/PageLangLogFormatterTest.php new file mode 100644 index 00000000..226e492b --- /dev/null +++ b/tests/phpunit/includes/logging/PageLangLogFormatterTest.php @@ -0,0 +1,53 @@ +<?php + +class PageLangLogFormatterTest extends LogFormatterTestCase { + + protected function setUp() { + parent::setUp(); + + // Disable cldr extension + $this->setMwGlobals( 'wgHooks', array() ); + // Register LogHandler, see $wgPageLanguageUseDB in Setup.php + $this->mergeMwGlobalArrayValue( 'wgLogActionsHandlers', array( + 'pagelang/pagelang' => 'PageLangLogFormatter', + ) ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function providePageLangLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'pagelang', + 'action' => 'pagelang', + 'comment' => 'page lang comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::oldlanguage' => 'en', + '5::newlanguage' => 'de[def]', + ), + ), + array( + 'text' => 'User changed page language for Page from English (en) to Deutsch (de) [default].', + 'api' => array( + 'oldlanguage' => 'en', + 'newlanguage' => 'de[def]' + ), + ), + ), + ); + } + + /** + * @dataProvider providePageLangLogDatabaseRows + */ + public function testPageLangLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/PatrolLogFormatterTest.php b/tests/phpunit/includes/logging/PatrolLogFormatterTest.php new file mode 100644 index 00000000..6e1c5efc --- /dev/null +++ b/tests/phpunit/includes/logging/PatrolLogFormatterTest.php @@ -0,0 +1,118 @@ +<?php + +class PatrolLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function providePatrolLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'patrol', + 'action' => 'patrol', + 'comment' => 'patrol comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::curid' => 2, + '5::previd' => 1, + '6::auto' => 0, + ), + ), + array( + 'text' => 'User marked revision 2 of page Page patrolled', + 'api' => array( + 'curid' => 2, + 'previd' => 1, + 'auto' => false, + ), + ), + ), + + // Current format - autopatrol + array( + array( + 'type' => 'patrol', + 'action' => 'patrol', + 'comment' => 'patrol comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::curid' => 2, + '5::previd' => 1, + '6::auto' => 1, + ), + ), + array( + 'text' => 'User automatically marked revision 2 of page Page patrolled', + 'api' => array( + 'curid' => 2, + 'previd' => 1, + 'auto' => true, + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'patrol', + 'action' => 'patrol', + 'comment' => 'patrol comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '2', + '1', + '0', + ), + ), + array( + 'legacy' => true, + 'text' => 'User marked revision 2 of page Page patrolled', + 'api' => array( + 'curid' => 2, + 'previd' => 1, + 'auto' => false, + ), + ), + ), + + // Legacy format - autopatrol + array( + array( + 'type' => 'patrol', + 'action' => 'patrol', + 'comment' => 'patrol comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '2', + '1', + '1', + ), + ), + array( + 'legacy' => true, + 'text' => 'User automatically marked revision 2 of page Page patrolled', + 'api' => array( + 'curid' => 2, + 'previd' => 1, + 'auto' => true, + ), + ), + ), + ); + } + + /** + * @dataProvider providePatrolLogDatabaseRows + */ + public function testPatrolLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/ProtectLogFormatterTest.php b/tests/phpunit/includes/logging/ProtectLogFormatterTest.php new file mode 100644 index 00000000..611b2dfc --- /dev/null +++ b/tests/phpunit/includes/logging/ProtectLogFormatterTest.php @@ -0,0 +1,63 @@ +<?php + +class ProtectLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideMoveProtLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'protect', + 'action' => 'move_prot', + 'comment' => 'Move comment', + 'namespace' => NS_MAIN, + 'title' => 'NewPage', + 'params' => array( + '4::oldtitle' => 'OldPage', + ), + ), + array( + 'text' => 'User moved protection settings from OldPage to NewPage', + 'api' => array( + 'oldtitle_ns' => 0, + 'oldtitle_title' => 'OldPage', + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'protect', + 'action' => 'move_prot', + 'comment' => 'Move comment', + 'namespace' => NS_MAIN, + 'title' => 'NewPage', + 'params' => array( + 'OldPage', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved protection settings from OldPage to NewPage', + 'api' => array( + 'oldtitle_ns' => 0, + 'oldtitle_title' => 'OldPage', + ), + ), + ), + ); + } + + /** + * @dataProvider provideMoveProtLogDatabaseRows + */ + public function testMoveProtLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/RightsLogFormatterTest.php b/tests/phpunit/includes/logging/RightsLogFormatterTest.php new file mode 100644 index 00000000..e9577f11 --- /dev/null +++ b/tests/phpunit/includes/logging/RightsLogFormatterTest.php @@ -0,0 +1,157 @@ +<?php + +class RightsLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideRightsLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'rights', + 'action' => 'rights', + 'comment' => 'rights comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'User', + 'params' => array( + '4::oldgroups' => array(), + '5::newgroups' => array( 'sysop', 'bureaucrat' ), + ), + ), + array( + 'text' => 'Sysop changed group membership for User:User from (none) to ' + . 'administrator and bureaucrat', + 'api' => array( + 'oldgroups' => array(), + 'newgroups' => array( 'sysop', 'bureaucrat' ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'rights', + 'action' => 'rights', + 'comment' => 'rights comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'User', + 'params' => array( + '', + 'sysop, bureaucrat', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop changed group membership for User:User from (none) to ' + . 'administrator and bureaucrat', + 'api' => array( + 'oldgroups' => array(), + 'newgroups' => array( 'sysop', 'bureaucrat' ), + ), + ), + ), + + // Really old entry + array( + array( + 'type' => 'rights', + 'action' => 'rights', + 'comment' => 'rights comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'User', + 'params' => array(), + ), + array( + 'legacy' => true, + 'text' => 'Sysop changed group membership for User:User', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideRightsLogDatabaseRows + */ + public function testRightsLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideAutopromoteLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'rights', + 'action' => 'autopromote', + 'comment' => 'rights comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Sysop', + 'params' => array( + '4::oldgroups' => array( 'sysop' ), + '5::newgroups' => array( 'sysop', 'bureaucrat' ), + ), + ), + array( + 'text' => 'Sysop was automatically promoted from administrator to ' + . 'administrator and bureaucrat', + 'api' => array( + 'oldgroups' => array( 'sysop' ), + 'newgroups' => array( 'sysop', 'bureaucrat' ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'rights', + 'action' => 'autopromote', + 'comment' => 'rights comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Sysop', + 'params' => array( + 'sysop', + 'sysop, bureaucrat', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop was automatically promoted from administrator to ' + . 'administrator and bureaucrat', + 'api' => array( + 'oldgroups' => array( 'sysop' ), + 'newgroups' => array( 'sysop', 'bureaucrat' ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideAutopromoteLogDatabaseRows + */ + public function testAutopromoteLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/UploadLogFormatterTest.php b/tests/phpunit/includes/logging/UploadLogFormatterTest.php new file mode 100644 index 00000000..12f51613 --- /dev/null +++ b/tests/phpunit/includes/logging/UploadLogFormatterTest.php @@ -0,0 +1,166 @@ +<?php + +class UploadLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideUploadLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'upload', + 'action' => 'upload', + 'comment' => 'upload comment', + 'namespace' => NS_FILE, + 'title' => 'File.png', + 'params' => array( + 'img_sha1' => 'hash', + 'img_timestamp' => '20150101000000', + ), + ), + array( + 'text' => 'User uploaded File:File.png', + 'api' => array( + 'img_sha1' => 'hash', + 'img_timestamp' => '2015-01-01T00:00:00Z', + ), + ), + ), + + // Old format without params + array( + array( + 'type' => 'upload', + 'action' => 'upload', + 'comment' => 'upload comment', + 'namespace' => NS_FILE, + 'title' => 'File.png', + 'params' => array(), + ), + array( + 'text' => 'User uploaded File:File.png', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideUploadLogDatabaseRows + */ + public function testUploadLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideOverwriteLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'upload', + 'action' => 'overwrite', + 'comment' => 'upload comment', + 'namespace' => NS_FILE, + 'title' => 'File.png', + 'params' => array( + 'img_sha1' => 'hash', + 'img_timestamp' => '20150101000000', + ), + ), + array( + 'text' => 'User uploaded a new version of File:File.png', + 'api' => array( + 'img_sha1' => 'hash', + 'img_timestamp' => '2015-01-01T00:00:00Z', + ), + ), + ), + + // Old format without params + array( + array( + 'type' => 'upload', + 'action' => 'overwrite', + 'comment' => 'upload comment', + 'namespace' => NS_FILE, + 'title' => 'File.png', + 'params' => array(), + ), + array( + 'text' => 'User uploaded a new version of File:File.png', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideOverwriteLogDatabaseRows + */ + public function testOverwriteLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideRevertLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'upload', + 'action' => 'revert', + 'comment' => 'upload comment', + 'namespace' => NS_FILE, + 'title' => 'File.png', + 'params' => array( + 'img_sha1' => 'hash', + 'img_timestamp' => '20150101000000', + ), + ), + array( + 'text' => 'User uploaded File:File.png', + 'api' => array( + 'img_sha1' => 'hash', + 'img_timestamp' => '2015-01-01T00:00:00Z', + ), + ), + ), + + // Old format without params + array( + array( + 'type' => 'upload', + 'action' => 'revert', + 'comment' => 'upload comment', + 'namespace' => NS_FILE, + 'title' => 'File.png', + 'params' => array(), + ), + array( + 'text' => 'User uploaded File:File.png', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideRevertLogDatabaseRows + */ + public function testRevertLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/media/ExifBitmapTest.php b/tests/phpunit/includes/media/ExifBitmapTest.php index 41330f41..adbc9775 100644 --- a/tests/phpunit/includes/media/ExifBitmapTest.php +++ b/tests/phpunit/includes/media/ExifBitmapTest.php @@ -3,7 +3,7 @@ /** * @group Media */ -class ExifBitmapTest extends MediaWikiTestCase { +class ExifBitmapTest extends MediaWikiMediaTestCase { /** * @var ExifBitmapHandler @@ -143,4 +143,41 @@ class ExifBitmapTest extends MediaWikiTestCase { $res = $this->handler->convertMetadataVersion( $metadata, 1 ); $this->assertEquals( $expected, $res ); } + + /** + * @dataProvider provideSwappingICCProfile + * @covers BitmapHandler::swapICCProfile + */ + public function testSwappingICCProfile( $sourceFilename, $controlFilename, $newProfileFilename, $oldProfileName ) { + global $wgExiftool; + + if ( !$wgExiftool || !is_file( $wgExiftool ) ) { + $this->markTestSkipped( "Exiftool not installed, cannot test ICC profile swapping" ); + } + + $this->setMwGlobals( 'wgUseTinyRGBForJPGThumbnails', true ); + + $sourceFilepath = $this->filePath . $sourceFilename; + $controlFilepath = $this->filePath . $controlFilename; + $profileFilepath = $this->filePath . $newProfileFilename; + $filepath = $this->getNewTempFile(); + + copy( $sourceFilepath, $filepath ); + + $file = $this->dataFile( $sourceFilename, 'image/jpeg' ); + $this->handler->swapICCProfile( $filepath, $oldProfileName, $profileFilepath ); + + $this->assertEquals( sha1( file_get_contents( $filepath ) ), sha1( file_get_contents( $controlFilepath ) ) ); + } + + public function provideSwappingICCProfile() { + return array( + // File with sRGB should end up with TinyRGB + array( 'srgb.jpg', 'tinyrgb.jpg', 'tinyrgb.icc', 'IEC 61966-2.1 Default RGB colour space - sRGB' ), + // File with TinyRGB should be left unchanged + array( 'tinyrgb.jpg', 'tinyrgb.jpg', 'tinyrgb.icc', 'IEC 61966-2.1 Default RGB colour space - sRGB' ), + // File with no profile should be left unchanged + array( 'test.jpg', 'test.jpg', 'tinyrgb.icc', 'IEC 61966-2.1 Default RGB colour space - sRGB' ) + ); + } } diff --git a/tests/phpunit/includes/media/FormatMetadataTest.php b/tests/phpunit/includes/media/FormatMetadataTest.php index 54758f94..b666c83c 100644 --- a/tests/phpunit/includes/media/FormatMetadataTest.php +++ b/tests/phpunit/includes/media/FormatMetadataTest.php @@ -37,39 +37,6 @@ class FormatMetadataTest extends MediaWikiMediaTestCase { } /** - * @param string $filename - * @param int $expected Total image area - * @dataProvider provideFlattenArray - * @covers FormatMetadata::flattenArray - */ - public function testFlattenArray( $vals, $type, $noHtml, $ctx, $expected ) { - $actual = FormatMetadata::flattenArray( $vals, $type, $noHtml, $ctx ); - $this->assertEquals( $expected, $actual ); - } - - public static function provideFlattenArray() { - return array( - array( - array( 1, 2, 3 ), 'ul', false, false, - "<ul><li>1</li>\n<li>2</li>\n<li>3</li></ul>", - ), - array( - array( 1, 2, 3 ), 'ol', false, false, - "<ol><li>1</li>\n<li>2</li>\n<li>3</li></ol>", - ), - array( - array( 1, 2, 3 ), 'ul', true, false, - "\n*1\n*2\n*3", - ), - array( - array( 1, 2, 3 ), 'ol', true, false, - "\n#1\n#2\n#3", - ), - // TODO: more test cases - ); - } - - /** * @param mixed $input * @param mixed $output * @dataProvider provideResolveMultivalueValue diff --git a/tests/phpunit/includes/media/WebPTest.php b/tests/phpunit/includes/media/WebPTest.php new file mode 100644 index 00000000..d36710a3 --- /dev/null +++ b/tests/phpunit/includes/media/WebPTest.php @@ -0,0 +1,127 @@ +<?php +class WebPHandlerTest extends MediaWikiTestCase { + public function setUp() { + parent::setUp(); + // Allocated file for testing + $this->tempFileName = tempnam( wfTempDir(), 'WEBP' ); + } + public function tearDown() { + parent::tearDown(); + unlink( $this->tempFileName ); + } + /** + * @dataProvider provideTestExtractMetaData + */ + public function testExtractMetaData( $header, $expectedResult ) { + // Put header into file + file_put_contents( $this->tempFileName, $header ); + + $this->assertEquals( $expectedResult, WebPHandler::extractMetadata( $this->tempFileName ) ); + } + public function provideTestExtractMetaData() { + return array( + // Files from https://developers.google.com/speed/webp/gallery2 + array( "\x52\x49\x46\x46\x90\x68\x01\x00\x57\x45\x42\x50\x56\x50\x38\x4C\x83\x68\x01\x00\x2F\x8F\x01\x4B\x10\x8D\x38\x6C\xDB\x46\x92\xE0\xE0\x82\x7B\x6C", + array( 'compression' => 'lossless', 'width' => 400, 'height' => 301 ) ), + array( "\x52\x49\x46\x46\x64\x5B\x00\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x8F\x01\x00\x2C\x01\x00\x41\x4C\x50\x48\xE5\x0E", + array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 400, 'height' => 301) ), + array( "\x52\x49\x46\x46\xA8\x72\x00\x00\x57\x45\x42\x50\x56\x50\x38\x4C\x9B\x72\x00\x00\x2F\x81\x81\x62\x10\x8D\x40\x8C\x24\x39\x6E\x73\x73\x38\x01\x96", + array( 'compression' => 'lossless', 'width' => 386, 'height' => 395 ) ), + array( "\x52\x49\x46\x46\xE0\x42\x00\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x81\x01\x00\x8A\x01\x00\x41\x4C\x50\x48\x56\x10", + array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 386, 'height' => 395 ) ), + array( "\x52\x49\x46\x46\x70\x61\x02\x00\x57\x45\x42\x50\x56\x50\x38\x4C\x63\x61\x02\x00\x2F\x1F\xC3\x95\x10\x8D\xC8\x72\xDB\xC8\x92\x24\xD8\x91\xD9\x91", + array( 'compression' => 'lossless', 'width' => 800, 'height' => 600 ) ), + array( "\x52\x49\x46\x46\x1C\x1D\x01\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x1F\x03\x00\x57\x02\x00\x41\x4C\x50\x48\x25\x8B", + array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 800, 'height' => 600 ) ), + array( "\x52\x49\x46\x46\xFA\xC5\x00\x00\x57\x45\x42\x50\x56\x50\x38\x4C\xEE\xC5\x00\x00\x2F\xA4\x81\x28\x10\x8D\x40\x68\x24\xC9\x91\xA4\xAE\xF3\x97\x75", + array( 'compression' => 'lossless', 'width' => 421, 'height' => 163 ) ), + array( "\x52\x49\x46\x46\xF6\x5D\x00\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\xA4\x01\x00\xA2\x00\x00\x41\x4C\x50\x48\x38\x1A", + array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 421, 'height' => 163 ) ), + array( "\x52\x49\x46\x46\xC4\x96\x01\x00\x57\x45\x42\x50\x56\x50\x38\x4C\xB8\x96\x01\x00\x2F\x2B\xC1\x4A\x10\x11\x87\x6D\xDB\x48\x12\xFC\x60\xB0\x83\x24", + array( 'compression' => 'lossless', 'width' => 300, 'height' => 300 ) ), + array( "\x52\x49\x46\x46\x0A\x11\x01\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x2B\x01\x00\x2B\x01\x00\x41\x4C\x50\x48\x67\x6E", + array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 300, 'height' => 300 ) ), + + // Lossy files from https://developers.google.com/speed/webp/gallery1 + array( "\x52\x49\x46\x46\x68\x76\x00\x00\x57\x45\x42\x50\x56\x50\x38\x20\x5C\x76\x00\x00\xD2\xBE\x01\x9D\x01\x2A\x26\x02\x70\x01\x3E\xD5\x4E\x97\x43\xA2", + array( 'compression' => 'lossy', 'width' => 550, 'height' => 368 ) ), + array( "\x52\x49\x46\x46\xB0\xEC\x00\x00\x57\x45\x42\x50\x56\x50\x38\x20\xA4\xEC\x00\x00\xB2\x4B\x02\x9D\x01\x2A\x26\x02\x94\x01\x3E\xD1\x50\x96\x46\x26", + array( 'compression' => 'lossy', 'width' => 550, 'height' => 404 ) ), + array( "\x52\x49\x46\x46\x7A\x19\x03\x00\x57\x45\x42\x50\x56\x50\x38\x20\x6E\x19\x03\x00\xB2\xF8\x09\x9D\x01\x2A\x00\x05\xD0\x02\x3E\xAD\x46\x99\x4A\xA5", + array( 'compression' => 'lossy', 'width' => 1280, 'height' => 720 ) ), + array( "\x52\x49\x46\x46\x44\xB3\x02\x00\x57\x45\x42\x50\x56\x50\x38\x20\x38\xB3\x02\x00\x52\x57\x06\x9D\x01\x2A\x00\x04\x04\x03\x3E\xA5\x44\x96\x49\x26", + array( 'compression' => 'lossy', 'width' => 1024, 'height' => 772) ), + array( "\x52\x49\x46\x46\x02\x43\x01\x00\x57\x45\x42\x50\x56\x50\x38\x20\xF6\x42\x01\x00\x12\xC0\x05\x9D\x01\x2A\x00\x04\xF0\x02\x3E\x79\x34\x93\x47\xA4", + array( 'compression' => 'lossy', 'width' => 1024, 'height' => 752) ), + + // Animated file from https://groups.google.com/a/chromium.org/d/topic/blink-dev/Y8tRC4mdQz8/discussion + array( "\x52\x49\x46\x46\xD0\x0B\x02\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x12\x00\x00\x00\x3F\x01\x00\x3F\x01\x00\x41\x4E", + array( 'compression' => 'unknown', 'animated' => true, 'transparency' => true, 'width' => 320, 'height' => 320 ) ), + + // Error cases + array( '', false ), + array( ' ', false ), + array( 'RIFF ', false ), + array( 'RIFF1234WEBP ', false ), + array( 'RIFF1234WEBPVP8 ', false ), + array( 'RIFF1234WEBPVP8L ', false ), + ); + } + + /** + * @dataProvider provideTestWithFileExtractMetaData + */ + public function testWithFileExtractMetaData( $filename, $expectedResult ) { + $this->assertEquals( $expectedResult, WebPHandler::extractMetadata( $filename ) ); + } + public function provideTestWithFileExtractMetaData() { + return array( + array( __DIR__ . '/../../data/media/2_webp_ll.webp', + array( 'compression' => 'lossless', 'width' => 386, 'height' => 395 ) ), + array( __DIR__ . '/../../data/media/2_webp_a.webp', + array( 'compression' => 'lossy', 'animated' => false, 'transparency' => true, 'width' => 386, 'height' => 395 ) ), + ); + } + + /** + * @dataProvider provideTestGetImageSize + */ + public function testGetImageSize( $path, $expectedResult ) { + $handler = new WebPHandler(); + $this->assertEquals( $expectedResult, $handler->getImageSize( null, $path ) ); + } + public function provideTestGetImageSize() { + return array( + // Public domain files from https://developers.google.com/speed/webp/gallery2 + array( __DIR__ . '/../../data/media/2_webp_a.webp', array( 386, 395 ) ), + array( __DIR__ . '/../../data/media/2_webp_ll.webp', array( 386, 395 ) ), + array( __DIR__ . '/../../data/media/webp_animated.webp', array( 300, 225 ) ), + + // Error cases + array( __FILE__, false ), + ); + } + + /** + * Tests the WebP MIME detection. This should really be a separate test, but sticking it + * here for now. + * + * @dataProvider provideTestGetMimeType + */ + public function testGuessMimeType( $path ) { + $mime = MimeMagic::singleton(); + $this->assertEquals( 'image/webp', $mime->guessMimeType( $path, false ) ); + } + public function provideTestGetMimeType() { + return array( + // Public domain files from https://developers.google.com/speed/webp/gallery2 + array( __DIR__ . '/../../data/media/2_webp_a.webp' ), + array( __DIR__ . '/../../data/media/2_webp_ll.webp' ), + array( __DIR__ . '/../../data/media/webp_animated.webp' ), + ); + } +} + +/* Python code to extract a header and convert to PHP format: + * print '"%s"' % ''.join( '\\x%02X' % ord(c) for c in urllib.urlopen(url).read(36) ) + */ diff --git a/tests/phpunit/includes/media/XMPValidateTest.php b/tests/phpunit/includes/media/XMPValidateTest.php index ebec8f6c..53671d42 100644 --- a/tests/phpunit/includes/media/XMPValidateTest.php +++ b/tests/phpunit/includes/media/XMPValidateTest.php @@ -1,5 +1,7 @@ <?php +use Psr\Log\NullLogger; + /** * @group Media */ @@ -11,7 +13,8 @@ class XMPValidateTest extends MediaWikiTestCase { */ public function testValidateDate( $value, $expected ) { // The method should modify $value. - XMPValidate::validateDate( array(), $value, true ); + $validate = new XMPValidate( new NullLogger() ); + $validate->validateDate( array(), $value, true ); $this->assertEquals( $expected, $value ); } diff --git a/tests/phpunit/includes/objectcache/BagOStuffTest.php b/tests/phpunit/includes/objectcache/BagOStuffTest.php index 4516bb4e..b6840062 100644 --- a/tests/phpunit/includes/objectcache/BagOStuffTest.php +++ b/tests/phpunit/includes/objectcache/BagOStuffTest.php @@ -1,8 +1,10 @@ <?php /** * @author Matthias Mullie <mmullie@wikimedia.org> + * @group BagOStuff */ class BagOStuffTest extends MediaWikiTestCase { + /** @var BagOStuff */ private $cache; protected function setUp() { @@ -136,20 +138,48 @@ class BagOStuffTest extends MediaWikiTestCase { public function testGetMulti() { $value1 = array( 'this' => 'is', 'a' => 'test' ); $value2 = array( 'this' => 'is', 'another' => 'test' ); + $value3 = array( 'testing a key that may be encoded when sent to cache backend' ); $key1 = wfMemcKey( 'test1' ); $key2 = wfMemcKey( 'test2' ); + $key3 = wfMemcKey( 'will-%-encode' ); // internally, MemcachedBagOStuffs will encode to will-%25-encode $this->cache->add( $key1, $value1 ); $this->cache->add( $key2, $value2 ); + $this->cache->add( $key3, $value3 ); $this->assertEquals( - $this->cache->getMulti( array( $key1, $key2 ) ), - array( $key1 => $value1, $key2 => $value2 ) + array( $key1 => $value1, $key2 => $value2, $key3 => $value3 ), + $this->cache->getMulti( array( $key1, $key2, $key3 ) ) ); // cleanup $this->cache->delete( $key1 ); $this->cache->delete( $key2 ); + $this->cache->delete( $key3 ); + } + + /** + * @covers BagOStuff::getScopedLock + */ + public function testGetScopedLock() { + $key = wfMemcKey( 'test' ); + $value1 = $this->cache->getScopedLock( $key, 0 ); + $value2 = $this->cache->getScopedLock( $key, 0 ); + + $this->assertType( 'ScopedCallback', $value1, 'First call returned lock' ); + $this->assertNull( $value2, 'Duplicate call returned no lock' ); + + unset( $value1 ); + + $value3 = $this->cache->getScopedLock( $key, 0 ); + $this->assertType( 'ScopedCallback', $value3, 'Lock returned callback after release' ); + unset( $value3 ); + + $value1 = $this->cache->getScopedLock( $key, 0, 5, 'reentry' ); + $value2 = $this->cache->getScopedLock( $key, 0, 5, 'reentry' ); + + $this->assertType( 'ScopedCallback', $value1, 'First reentrant call returned lock' ); + $this->assertType( 'ScopedCallback', $value1, 'Second reentrant call returned lock' ); } } diff --git a/tests/phpunit/includes/objectcache/MultiWriteBagOStuffTest.php b/tests/phpunit/includes/objectcache/MultiWriteBagOStuffTest.php new file mode 100644 index 00000000..2b66181c --- /dev/null +++ b/tests/phpunit/includes/objectcache/MultiWriteBagOStuffTest.php @@ -0,0 +1,55 @@ +<?php + +/** + * @group Database + */ +class MultiWriteBagOStuffTest extends MediaWikiTestCase { + /** @var HashBagOStuff */ + private $cache1; + /** @var HashBagOStuff */ + private $cache2; + /** @var MultiWriteBagOStuff */ + private $cache; + + protected function setUp() { + parent::setUp(); + + $this->cache1 = new HashBagOStuff(); + $this->cache2 = new HashBagOStuff(); + $this->cache = new MultiWriteBagOStuff( array( + 'caches' => array( $this->cache1, $this->cache2 ), + 'replication' => 'async' + ) ); + } + + public function testSetImmediate() { + $key = wfRandomString(); + $value = wfRandomString(); + $this->cache->set( $key, $value ); + + // Set in tier 1 + $this->assertEquals( $value, $this->cache1->get( $key ), 'Written to tier 1' ); + // Set in tier 2 + $this->assertEquals( $value, $this->cache2->get( $key ), 'Written to tier 2' ); + } + + public function testSetDelayed() { + $key = wfRandomString(); + $value = wfRandomString(); + + // XXX: DeferredUpdates bound to transactions in CLI mode + $dbw = wfGetDB( DB_MASTER ); + $dbw->begin(); + $this->cache->set( $key, $value ); + + // Set in tier 1 + $this->assertEquals( $value, $this->cache1->get( $key ), 'Written to tier 1' ); + // Not yet set in tier 2 + $this->assertEquals( false, $this->cache2->get( $key ), 'Not written to tier 2' ); + + $dbw->commit(); + + // Set in tier 2 + $this->assertEquals( $value, $this->cache2->get( $key ), 'Written to tier 2' ); + } +} diff --git a/tests/phpunit/includes/objectcache/ReplicatedBagOStuffTest.php b/tests/phpunit/includes/objectcache/ReplicatedBagOStuffTest.php new file mode 100644 index 00000000..a419f5b6 --- /dev/null +++ b/tests/phpunit/includes/objectcache/ReplicatedBagOStuffTest.php @@ -0,0 +1,62 @@ +<?php + +class ReplicatedBagOStuffTest extends MediaWikiTestCase { + /** @var HashBagOStuff */ + private $writeCache; + /** @var HashBagOStuff */ + private $readCache; + /** @var ReplicatedBagOStuff */ + private $cache; + + protected function setUp() { + parent::setUp(); + + $this->writeCache = new HashBagOStuff(); + $this->readCache = new HashBagOStuff(); + $this->cache = new ReplicatedBagOStuff( array( + 'writeFactory' => $this->writeCache, + 'readFactory' => $this->readCache, + ) ); + } + + /** + * @covers ReplicatedBagOStuff::set + */ + public function testSet() { + $key = wfRandomString(); + $value = wfRandomString(); + $this->cache->set( $key, $value ); + + // Write to master. + $this->assertEquals( $this->writeCache->get( $key ), $value ); + // Don't write to slave. Replication is deferred to backend. + $this->assertEquals( $this->readCache->get( $key ), false ); + } + + /** + * @covers ReplicatedBagOStuff::get + */ + public function testGet() { + $key = wfRandomString(); + + $write = wfRandomString(); + $this->writeCache->set( $key, $write ); + $read = wfRandomString(); + $this->readCache->set( $key, $read ); + + // Read from slave. + $this->assertEquals( $this->cache->get( $key ), $read ); + } + + /** + * @covers ReplicatedBagOStuff::get + */ + public function testGetAbsent() { + $key = wfRandomString(); + $value = wfRandomString(); + $this->writeCache->set( $key, $value ); + + // Don't read from master. No failover if value is absent. + $this->assertEquals( $this->cache->get( $key ), false ); + } +} diff --git a/tests/phpunit/includes/objectcache/WANObjectCacheTest.php b/tests/phpunit/includes/objectcache/WANObjectCacheTest.php new file mode 100644 index 00000000..40ae4613 --- /dev/null +++ b/tests/phpunit/includes/objectcache/WANObjectCacheTest.php @@ -0,0 +1,292 @@ +<?php + +class WANObjectCacheTest extends MediaWikiTestCase { + /** @var WANObjectCache */ + private $cache; + + protected function setUp() { + parent::setUp(); + + if ( $this->getCliArg( 'use-wanobjectcache' ) ) { + $name = $this->getCliArg( 'use-wanobjectcache' ); + + $this->cache = ObjectCache::getWANInstance( $name ); + } else { + $this->cache = new WANObjectCache( array( + 'cache' => new HashBagOStuff(), + 'pool' => 'testcache-hash', + 'relayer' => new EventRelayerNull( array() ) + ) ); + } + + $wanCache = TestingAccessWrapper::newFromObject( $this->cache ); + $this->internalCache = $wanCache->cache; + } + + /** + * @dataProvider provider_testSetAndGet + * @covers WANObjectCache::set() + * @covers WANObjectCache::get() + * @param mixed $value + * @param integer $ttl + */ + public function testSetAndGet( $value, $ttl ) { + $key = wfRandomString(); + $this->cache->set( $key, $value, $ttl ); + + $curTTL = null; + $this->assertEquals( $value, $this->cache->get( $key, $curTTL ) ); + if ( is_infinite( $ttl ) || $ttl == 0 ) { + $this->assertTrue( is_infinite( $curTTL ), "Current TTL is infinite" ); + } else { + $this->assertGreaterThan( 0, $curTTL, "Current TTL > 0" ); + $this->assertLessThanOrEqual( $ttl, $curTTL, "Current TTL < nominal TTL" ); + } + } + + public static function provider_testSetAndGet() { + return array( + array( 14141, 3 ), + array( 3535.666, 3 ), + array( array(), 3 ), + array( null, 3 ), + array( '0', 3 ), + array( (object)array( 'meow' ), 3 ), + array( INF, 3 ), + array( '', 3 ), + array( 'pizzacat', INF ), + ); + } + + public function testGetNotExists() { + $key = wfRandomString(); + $curTTL = null; + $value = $this->cache->get( $key, $curTTL ); + + $this->assertFalse( $value, "Non-existing key has false value" ); + $this->assertNull( $curTTL, "Non-existing key has null current TTL" ); + } + + public function testSetOver() { + $key = wfRandomString(); + for ( $i = 0; $i < 3; ++$i ) { + $value = wfRandomString(); + $this->cache->set( $key, $value, 3 ); + + $this->assertEquals( $this->cache->get( $key ), $value ); + } + } + + /** + * @covers WANObjectCache::getWithSetCallback() + */ + public function testGetWithSetCallback() { + $cache = $this->cache; + + $key = wfRandomString(); + $value = wfRandomString(); + $cKey1 = wfRandomString(); + $cKey2 = wfRandomString(); + + $wasSet = 0; + $func = function( $old, &$ttl ) use ( &$wasSet, $value ) { + ++$wasSet; + $ttl = 20; // override with another value + return $value; + }; + + $wasSet = 0; + $v = $cache->getWithSetCallback( $key, $func, 30, array(), array( 'lockTSE' => 5 ) ); + $this->assertEquals( $v, $value ); + $this->assertEquals( 1, $wasSet, "Value regenerated" ); + + $curTTL = null; + $v = $cache->get( $key, $curTTL ); + $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' ); + $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' ); + + $wasSet = 0; + $v = $cache->getWithSetCallback( $key, $func, 30, array(), array( 'lockTSE' => 5 ) ); + $this->assertEquals( $v, $value ); + $this->assertEquals( 0, $wasSet, "Value not regenerated" ); + + $priorTime = microtime( true ); + usleep( 1 ); + $wasSet = 0; + $v = $cache->getWithSetCallback( $key, $func, 30, array( $cKey1, $cKey2 ) ); + $this->assertEquals( $v, $value ); + $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" ); + $t1 = $cache->getCheckKeyTime( $cKey1 ); + $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' ); + $t2 = $cache->getCheckKeyTime( $cKey2 ); + $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' ); + + $priorTime = microtime( true ); + $wasSet = 0; + $v = $cache->getWithSetCallback( $key, $func, 30, array( $cKey1, $cKey2 ) ); + $this->assertEquals( $v, $value ); + $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" ); + $t1 = $cache->getCheckKeyTime( $cKey1 ); + $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' ); + $t2 = $cache->getCheckKeyTime( $cKey2 ); + $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' ); + + $curTTL = null; + $v = $cache->get( $key, $curTTL, array( $cKey1, $cKey2 ) ); + $this->assertEquals( $v, $value ); + $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" ); + } + + /** + * @covers WANObjectCache::getWithSetCallback() + */ + public function testLockTSE() { + $cache = $this->cache; + $key = wfRandomString(); + $value = wfRandomString(); + + $calls = 0; + $func = function() use ( &$calls, $value ) { + ++$calls; + return $value; + }; + + $cache->delete( $key ); + $ret = $cache->getWithSetCallback( $key, 30, $func, array(), array( 'lockTSE' => 5 ) ); + $this->assertEquals( $value, $ret ); + $this->assertEquals( 1, $calls, 'Value was populated' ); + + // Acquire a lock to verify that getWithSetCallback uses lockTSE properly + $this->internalCache->lock( $key, 0 ); + $ret = $cache->getWithSetCallback( $key, 30, $func, array(), array( 'lockTSE' => 5 ) ); + $this->assertEquals( $value, $ret ); + $this->assertEquals( 1, $calls, 'Callback was not used' ); + } + + /** + * @covers WANObjectCache::getMulti() + */ + public function testGetMulti() { + $cache = $this->cache; + + $value1 = array( 'this' => 'is', 'a' => 'test' ); + $value2 = array( 'this' => 'is', 'another' => 'test' ); + + $key1 = wfRandomString(); + $key2 = wfRandomString(); + $key3 = wfRandomString(); + + $cache->set( $key1, $value1, 5 ); + $cache->set( $key2, $value2, 10 ); + + $curTTLs = array(); + $this->assertEquals( + array( $key1 => $value1, $key2 => $value2 ), + $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs ) + ); + + $this->assertEquals( 2, count( $curTTLs ), "Two current TTLs in array" ); + $this->assertGreaterThan( 0, $curTTLs[$key1], "Key 1 has current TTL > 0" ); + $this->assertGreaterThan( 0, $curTTLs[$key2], "Key 2 has current TTL > 0" ); + + $cKey1 = wfRandomString(); + $cKey2 = wfRandomString(); + $curTTLs = array(); + $this->assertEquals( + array( $key1 => $value1, $key2 => $value2 ), + $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs ), + 'Result array populated' + ); + + $priorTime = microtime( true ); + usleep( 1 ); + $curTTLs = array(); + $this->assertEquals( + array( $key1 => $value1, $key2 => $value2 ), + $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs, array( $cKey1, $cKey2 ) ), + "Result array populated even with new check keys" + ); + $t1 = $cache->getCheckKeyTime( $cKey1 ); + $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key 1 generated on miss' ); + $t2 = $cache->getCheckKeyTime( $cKey2 ); + $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check key 2 generated on miss' ); + $this->assertEquals( 2, count( $curTTLs ), "Current TTLs array set" ); + $this->assertLessThanOrEqual( 0, $curTTLs[$key1], 'Key 1 has current TTL <= 0' ); + $this->assertLessThanOrEqual( 0, $curTTLs[$key2], 'Key 2 has current TTL <= 0' ); + + usleep( 1 ); + $curTTLs = array(); + $this->assertEquals( + array( $key1 => $value1, $key2 => $value2 ), + $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs, array( $cKey1, $cKey2 ) ), + "Result array still populated even with new check keys" + ); + $this->assertEquals( 2, count( $curTTLs ), "Current TTLs still array set" ); + $this->assertLessThan( 0, $curTTLs[$key1], 'Key 1 has negative current TTL' ); + $this->assertLessThan( 0, $curTTLs[$key2], 'Key 2 has negative current TTL' ); + } + + /** + * @covers WANObjectCache::delete() + */ + public function testDelete() { + $key = wfRandomString(); + $value = wfRandomString(); + $this->cache->set( $key, $value ); + + $curTTL = null; + $v = $this->cache->get( $key, $curTTL ); + $this->assertEquals( $value, $v, "Key was created with value" ); + $this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" ); + + $this->cache->delete( $key ); + + $curTTL = null; + $v = $this->cache->get( $key, $curTTL ); + $this->assertFalse( $v, "Deleted key has false value" ); + $this->assertLessThan( 0, $curTTL, "Deleted key has current TTL < 0" ); + + $this->cache->set( $key, $value . 'more' ); + $this->assertFalse( $v, "Deleted key is tombstoned and has false value" ); + $this->assertLessThan( 0, $curTTL, "Deleted key is tombstoned and has current TTL < 0" ); + } + + /** + * @covers WANObjectCache::touchCheckKey() + * @covers WANObjectCache::resetCheckKey() + * @covers WANObjectCache::getCheckKeyTime() + */ + public function testTouchKeys() { + $key = wfRandomString(); + + $priorTime = microtime( true ); + usleep( 1 ); + $t0 = $this->cache->getCheckKeyTime( $key ); + $this->assertGreaterThanOrEqual( $priorTime, $t0, 'Check key auto-created' ); + + $priorTime = microtime( true ); + usleep( 1 ); + $this->cache->touchCheckKey( $key ); + $t1 = $this->cache->getCheckKeyTime( $key ); + $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key created' ); + + $t2 = $this->cache->getCheckKeyTime( $key ); + $this->assertEquals( $t1, $t2, 'Check key time did not change' ); + + usleep( 1 ); + $this->cache->touchCheckKey( $key ); + $t3 = $this->cache->getCheckKeyTime( $key ); + $this->assertGreaterThan( $t2, $t3, 'Check key time increased' ); + + $t4 = $this->cache->getCheckKeyTime( $key ); + $this->assertEquals( $t3, $t4, 'Check key time did not change' ); + + usleep( 1 ); + $this->cache->resetCheckKey( $key ); + $t5 = $this->cache->getCheckKeyTime( $key ); + $this->assertGreaterThan( $t4, $t5, 'Check key time increased' ); + + $t6 = $this->cache->getCheckKeyTime( $key ); + $this->assertEquals( $t5, $t6, 'Check key time did not change' ); + } +} diff --git a/tests/phpunit/includes/parser/MagicVariableTest.php b/tests/phpunit/includes/parser/MagicVariableTest.php index 17226113..cd54a9e3 100644 --- a/tests/phpunit/includes/parser/MagicVariableTest.php +++ b/tests/phpunit/includes/parser/MagicVariableTest.php @@ -10,6 +10,8 @@ * @copyright Copyright © 2011, Antoine Musso * @file * @todo covers tags + * + * @group Database */ class MagicVariableTest extends MediaWikiTestCase { diff --git a/tests/phpunit/includes/parser/MediaWikiParserTest.php b/tests/phpunit/includes/parser/MediaWikiParserTest.php index df891f5a..96ae3bec 100644 --- a/tests/phpunit/includes/parser/MediaWikiParserTest.php +++ b/tests/phpunit/includes/parser/MediaWikiParserTest.php @@ -91,7 +91,7 @@ class MediaWikiParserTest { // enough to cause there to be separate names for different // things, which is good enough for our purposes. $extensionName = basename( dirname( $fileName ) ); - $testsName = $extensionName . '⁄' . basename( $fileName, '.txt' ); + $testsName = $extensionName . '__' . basename( $fileName, '.txt' ); $escapedFileName = strtr( $fileName, array( "'" => "\\'", '\\' => '\\\\' ) ); $parserTestClassName = ucfirst( $testsName ); // Official spec for class names: http://php.net/manual/en/language.oop5.basic.php diff --git a/tests/phpunit/includes/parser/NewParserTest.php b/tests/phpunit/includes/parser/NewParserTest.php index 91aad10c..df7da98c 100644 --- a/tests/phpunit/includes/parser/NewParserTest.php +++ b/tests/phpunit/includes/parser/NewParserTest.php @@ -91,7 +91,7 @@ class NewParserTest extends MediaWikiTestCase { ); $tmpGlobals['wgForeignFileRepos'] = array(); $tmpGlobals['wgDefaultExternalStore'] = array(); - $tmpGlobals['wgEnableParserCache'] = false; + $tmpGlobals['wgParserCacheType'] = CACHE_NONE; $tmpGlobals['wgCapitalLinks'] = true; $tmpGlobals['wgNoFollowLinks'] = true; $tmpGlobals['wgNoFollowDomainExceptions'] = array(); @@ -106,7 +106,6 @@ class NewParserTest extends MediaWikiTestCase { $tmpGlobals['wgAdaptiveMessageCache'] = true; $tmpGlobals['wgUseDatabaseMessages'] = true; $tmpGlobals['wgLocaltimezone'] = 'UTC'; - $tmpGlobals['wgDeferredUpdateList'] = array(); $tmpGlobals['wgGroupPermissions'] = array( '*' => array( 'createaccount' => true, @@ -160,10 +159,10 @@ class NewParserTest extends MediaWikiTestCase { $this->djVuSupport = new DjVuSupport(); // Tidy support $this->tidySupport = new TidySupport(); + $tmpGlobals['wgTidyConfig'] = null; $tmpGlobals['wgUseTidy'] = false; - $tmpGlobals['wgAlwaysUseTidy'] = false; $tmpGlobals['wgDebugTidy'] = false; - $tmpGlobals['wgTidyConf'] = $IP . '/includes/tidy.conf'; + $tmpGlobals['wgTidyConf'] = $IP . '/includes/tidy/tidy.conf'; $tmpGlobals['wgTidyOpts'] = ''; $tmpGlobals['wgTidyInternal'] = $this->tidySupport->isInternal(); @@ -185,6 +184,8 @@ class NewParserTest extends MediaWikiTestCase { $wgNamespaceAliases['Image'] = $this->savedWeirdGlobals['image_alias']; $wgNamespaceAliases['Image_talk'] = $this->savedWeirdGlobals['image_talk_alias']; + MWTidy::destroySingleton(); + // Restore backends RepoGroup::destroySingleton(); FileBackendGroup::destroySingleton(); @@ -454,6 +455,7 @@ class NewParserTest extends MediaWikiTestCase { $GLOBALS[$var] = $val; } + MWTidy::destroySingleton(); MagicWord::clearCache(); # The entries saved into RepoGroup cache with previous globals will be wrong. diff --git a/tests/phpunit/includes/parser/ParserMethodsTest.php b/tests/phpunit/includes/parser/ParserMethodsTest.php index 1790086a..af143caa 100644 --- a/tests/phpunit/includes/parser/ParserMethodsTest.php +++ b/tests/phpunit/includes/parser/ParserMethodsTest.php @@ -1,5 +1,9 @@ <?php +/** + * @group Database + */ + class ParserMethodsTest extends MediaWikiLangTestCase { public static function providePreSaveTransform() { diff --git a/tests/phpunit/includes/parser/TagHooksTest.php b/tests/phpunit/includes/parser/TagHooksTest.php index 251da471..4af38985 100644 --- a/tests/phpunit/includes/parser/TagHooksTest.php +++ b/tests/phpunit/includes/parser/TagHooksTest.php @@ -1,6 +1,7 @@ <?php /** + * @group Database * @group Parser */ class TagHookTest extends MediaWikiTestCase { @@ -18,12 +19,6 @@ class TagHookTest extends MediaWikiTestCase { return array( array( "foo<bar" ), array( "foo>bar" ), array( "foo\nbar" ), array( "foo\rbar" ) ); } - protected function setUp() { - parent::setUp(); - - $this->setMwGlobals( 'wgAlwaysUseTidy', false ); - } - /** * @dataProvider provideValidNames * @covers Parser::setHook diff --git a/tests/phpunit/includes/parser/TidyTest.php b/tests/phpunit/includes/parser/TidyTest.php index f656a74d..5db29080 100644 --- a/tests/phpunit/includes/parser/TidyTest.php +++ b/tests/phpunit/includes/parser/TidyTest.php @@ -7,8 +7,7 @@ class TidyTest extends MediaWikiTestCase { protected function setUp() { parent::setUp(); - $check = MWTidy::tidy( '' ); - if ( strpos( $check, '<!--' ) !== false ) { + if ( !MWTidy::isEnabled() ) { $this->markTestSkipped( 'Tidy not found' ); } } diff --git a/tests/phpunit/includes/password/PasswordPolicyChecksTest.php b/tests/phpunit/includes/password/PasswordPolicyChecksTest.php new file mode 100644 index 00000000..af34282f --- /dev/null +++ b/tests/phpunit/includes/password/PasswordPolicyChecksTest.php @@ -0,0 +1,136 @@ +<?php +/** + * Testing password-policy check functions + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +class PasswordPolicyChecksTest extends MediaWikiTestCase { + + /** + * @covers PasswordPolicyChecks::checkMinimalPasswordLength + */ + public function testCheckMinimalPasswordLength() { + $statusOK = PasswordPolicyChecks::checkMinimalPasswordLength( + 3, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertTrue( $statusOK->isGood(), 'Password is longer than minimal policy' ); + $statusShort = PasswordPolicyChecks::checkMinimalPasswordLength( + 10, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertFalse( + $statusShort->isGood(), + 'Password is shorter than minimal policy' + ); + $this->assertTrue( + $statusShort->isOk(), + 'Password is shorter than minimal policy, not fatal' + ); + } + + /** + * @covers PasswordPolicyChecks::checkMinimumPasswordLengthToLogin + */ + public function testCheckMinimumPasswordLengthToLogin() { + $statusOK = PasswordPolicyChecks::checkMinimumPasswordLengthToLogin( + 3, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertTrue( $statusOK->isGood(), 'Password is longer than minimal policy' ); + $statusShort = PasswordPolicyChecks::checkMinimumPasswordLengthToLogin( + 10, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertFalse( + $statusShort->isGood(), + 'Password is shorter than minimum login policy' + ); + $this->assertFalse( + $statusShort->isOk(), + 'Password is shorter than minimum login policy, fatal' + ); + } + + /** + * @covers PasswordPolicyChecks::checkMaximalPasswordLength + */ + public function testCheckMaximalPasswordLength() { + $statusOK = PasswordPolicyChecks::checkMaximalPasswordLength( + 100, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertTrue( $statusOK->isGood(), 'Password is shorter than maximal policy' ); + $statusLong = PasswordPolicyChecks::checkMaximalPasswordLength( + 4, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertFalse( $statusLong->isGood(), + 'Password is longer than maximal policy' + ); + $this->assertFalse( $statusLong->isOk(), + 'Password is longer than maximal policy, fatal' + ); + } + + /** + * @covers PasswordPolicyChecks::checkPasswordCannotMatchUsername + */ + public function testCheckPasswordCannotMatchUsername() { + $statusOK = PasswordPolicyChecks::checkPasswordCannotMatchUsername( + 1, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertTrue( $statusOK->isGood(), 'Password does not match username' ); + $statusLong = PasswordPolicyChecks::checkPasswordCannotMatchUsername( + 1, // policy value + User::newFromName( 'user' ), // User + 'user' // password + ); + $this->assertFalse( $statusLong->isGood(), 'Password matches username' ); + $this->assertTrue( $statusLong->isOk(), 'Password matches username, not fatal' ); + } + + /** + * @covers PasswordPolicyChecks::checkPasswordCannotMatchBlacklist + */ + public function testCheckPasswordCannotMatchBlacklist() { + $statusOK = PasswordPolicyChecks::checkPasswordCannotMatchBlacklist( + true, // policy value + User::newFromName( 'Username' ), // User + 'AUniquePassword' // password + ); + $this->assertTrue( $statusOK->isGood(), 'Password is not on blacklist' ); + $statusLong = PasswordPolicyChecks::checkPasswordCannotMatchBlacklist( + true, // policy value + User::newFromName( 'Useruser1' ), // User + 'Passpass1' // password + ); + $this->assertFalse( $statusLong->isGood(), 'Password matches blacklist' ); + $this->assertTrue( $statusLong->isOk(), 'Password matches blacklist, not fatal' ); + } + +} diff --git a/tests/phpunit/includes/password/UserPasswordPolicyTest.php b/tests/phpunit/includes/password/UserPasswordPolicyTest.php new file mode 100644 index 00000000..ce4e30ab --- /dev/null +++ b/tests/phpunit/includes/password/UserPasswordPolicyTest.php @@ -0,0 +1,234 @@ +<?php +/** + * Testing for password-policy enforcement, based on a user's groups. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +class UserPasswordPolicyTest extends MediaWikiTestCase { + + protected $policies = array( + 'checkuser' => array( + 'MinimalPasswordLength' => 10, + 'MinimumPasswordLengthToLogin' => 6, + 'PasswordCannotMatchUsername' => true, + ), + 'sysop' => array( + 'MinimalPasswordLength' => 8, + 'MinimumPasswordLengthToLogin' => 1, + 'PasswordCannotMatchUsername' => true, + ), + 'default' => array( + 'MinimalPasswordLength' => 4, + 'MinimumPasswordLengthToLogin' => 1, + 'PasswordCannotMatchBlacklist' => true, + 'MaximalPasswordLength' => 4096, + ), + ); + + protected $checks = array( + 'MinimalPasswordLength' => 'PasswordPolicyChecks::checkMinimalPasswordLength', + 'MinimumPasswordLengthToLogin' => 'PasswordPolicyChecks::checkMinimumPasswordLengthToLogin', + 'PasswordCannotMatchUsername' => 'PasswordPolicyChecks::checkPasswordCannotMatchUsername', + 'PasswordCannotMatchBlacklist' => 'PasswordPolicyChecks::checkPasswordCannotMatchBlacklist', + 'MaximalPasswordLength' => 'PasswordPolicyChecks::checkMaximalPasswordLength', + ); + + private function getUserPasswordPolicy() { + return new UserPasswordPolicy( $this->policies, $this->checks ); + } + + /** + * @covers UserPasswordPolicy::getPoliciesForUser + */ + public function testGetPoliciesForUser() { + + $upp = $this->getUserPasswordPolicy(); + + $user = User::newFromName( 'TestUserPolicy' ); + $user->addGroup( 'sysop' ); + + $this->assertArrayEquals( + array( + 'MinimalPasswordLength' => 8, + 'MinimumPasswordLengthToLogin' => 1, + 'PasswordCannotMatchUsername' => 1, + 'PasswordCannotMatchBlacklist' => true, + 'MaximalPasswordLength' => 4096, + ), + $upp->getPoliciesForUser( $user ) + ); + } + + /** + * @covers UserPasswordPolicy::getPoliciesForGroups + */ + public function testGetPoliciesForGroups() { + $effective = UserPasswordPolicy::getPoliciesForGroups( + $this->policies, + array( 'user', 'checkuser' ), + $this->policies['default'] + ); + + $this->assertArrayEquals( + array( + 'MinimalPasswordLength' => 10, + 'MinimumPasswordLengthToLogin' => 6, + 'PasswordCannotMatchUsername' => true, + 'PasswordCannotMatchBlacklist' => true, + 'MaximalPasswordLength' => 4096, + ), + $effective + ); + } + + /** + * @dataProvider provideCheckUserPassword + * @covers UserPasswordPolicy::checkUserPassword + */ + public function testCheckUserPassword( $username, $groups, $password, $valid, $ok, $msg ) { + + $upp = $this->getUserPasswordPolicy(); + + $user = User::newFromName( $username ); + foreach ( $groups as $group ) { + $user->addGroup( $group ); + } + + $status = $upp->checkUserPassword( $user, $password ); + $this->assertSame( $valid, $status->isGood(), $msg . ' - password valid' ); + $this->assertSame( $ok, $status->isOk(), $msg . ' - can login' ); + } + + public function provideCheckUserPassword() { + return array( + array( + 'PassPolicyUser', + array(), + '', + false, + false, + 'No groups, default policy, password too short to login' + ), + array( + 'PassPolicyUser', + array( 'user' ), + 'aaa', + false, + true, + 'Default policy, short password' + ), + array( + 'PassPolicyUser', + array( 'sysop' ), + 'abcdabcdabcd', + true, + true, + 'Sysop with good password' + ), + array( + 'PassPolicyUser', + array( 'sysop' ), + 'abcd', + false, + true, + 'Sysop with short password' + ), + array( + 'PassPolicyUser', + array( 'sysop', 'checkuser' ), + 'abcdabcd', + false, + true, + 'Checkuser with short password' + ), + array( + 'PassPolicyUser', + array( 'sysop', 'checkuser' ), + 'abcd', + false, + false, + 'Checkuser with too short password to login' + ), + array( + 'Useruser', + array( 'user' ), + 'Passpass', + false, + true, + 'Username & password on blacklist' + ), + ); + } + + /** + * @dataProvider provideMaxOfPolicies + * @covers UserPasswordPolicy::maxOfPolicies + */ + public function testMaxOfPolicies( $p1, $p2, $max, $msg ) { + $this->assertArrayEquals( + $max, + UserPasswordPolicy::maxOfPolicies( $p1, $p2 ), + $msg + ); + } + + public function provideMaxOfPolicies() { + return array( + array( + array( 'MinimalPasswordLength' => 8 ), //p1 + array( 'MinimalPasswordLength' => 2 ), //p2 + array( 'MinimalPasswordLength' => 8 ), //max + 'Basic max in p1' + ), + array( + array( 'MinimalPasswordLength' => 2 ), //p1 + array( 'MinimalPasswordLength' => 8 ), //p2 + array( 'MinimalPasswordLength' => 8 ), //max + 'Basic max in p2' + ), + array( + array( 'MinimalPasswordLength' => 8 ), //p1 + array( + 'MinimalPasswordLength' => 2, + 'PasswordCannotMatchUsername' => 1, + ), //p2 + array( + 'MinimalPasswordLength' => 8, + 'PasswordCannotMatchUsername' => 1, + ), //max + 'Missing items in p1' + ), + array( + array( + 'MinimalPasswordLength' => 8, + 'PasswordCannotMatchUsername' => 1, + ), //p1 + array( + 'MinimalPasswordLength' => 2, + ), //p2 + array( + 'MinimalPasswordLength' => 8, + 'PasswordCannotMatchUsername' => 1, + ), //max + 'Missing items in p2' + ), + ); + } + +} diff --git a/tests/phpunit/includes/registration/CoreVersionCheckerTest.php b/tests/phpunit/includes/registration/CoreVersionCheckerTest.php new file mode 100644 index 00000000..bc154b37 --- /dev/null +++ b/tests/phpunit/includes/registration/CoreVersionCheckerTest.php @@ -0,0 +1,38 @@ +<?php + +/** + * @covers CoreVersionChecker + */ +class CoreVersionCheckerTest extends PHPUnit_Framework_TestCase { + /** + * @dataProvider provideCheck + */ + public function testCheck( $coreVersion, $constraint, $expected ) { + $checker = new CoreVersionChecker( $coreVersion ); + $this->assertEquals( $expected, $checker->check( $constraint ) ); + } + + public static function provideCheck() { + return array( + // array( $wgVersion, constraint, expected ) + array( '1.25alpha', '>= 1.26', false ), + array( '1.25.0', '>= 1.26', false ), + array( '1.26alpha', '>= 1.26', true ), + array( '1.26alpha', '>= 1.26.0', true ), + array( '1.26alpha', '>= 1.26.0-stable', false ), + array( '1.26.0', '>= 1.26.0-stable', true ), + array( '1.26.1', '>= 1.26.0-stable', true ), + array( '1.27.1', '>= 1.26.0-stable', true ), + array( '1.26alpha', '>= 1.26.1', false ), + array( '1.26alpha', '>= 1.26alpha', true ), + array( '1.26alpha', '>= 1.25', true ), + array( '1.26.0-alpha.14', '>= 1.26.0-alpha.15', false ), + array( '1.26.0-alpha.14', '>= 1.26.0-alpha.10', true ), + array( '1.26.1', '>= 1.26.2, <=1.26.0', false ), + array( '1.26.1', '^1.26.2', false ), + // Accept anything for un-parsable version strings + array( '1.26mwf14', '== 1.25alpha', true ), + array( 'totallyinvalid', '== 1.0', true ), + ); + } +} diff --git a/tests/phpunit/includes/registration/ExtensionProcessorTest.php b/tests/phpunit/includes/registration/ExtensionProcessorTest.php index ff6be6c2..1cb8a5d9 100644 --- a/tests/phpunit/includes/registration/ExtensionProcessorTest.php +++ b/tests/phpunit/includes/registration/ExtensionProcessorTest.php @@ -14,7 +14,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { * * @var array */ - static $default = array( + public static $default = array( 'name' => 'FooBar', ); @@ -28,7 +28,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { '@metadata' => array( 'foobarbaz' ), 'AnAttribute' => array( 'omg' ), 'AutoloadClasses' => array( 'FooBar' => 'includes/FooBar.php' ), - ) ); + ), 1 ); $extracted = $processor->getExtractedInfo(); $attributes = $extracted['attributes']; @@ -96,7 +96,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { */ public function testRegisterHooks( $pre, $info, $expected ) { $processor = new MockExtensionProcessor( array( 'wgHooks' => $pre ) ); - $processor->extractInfo( $this->dir, $info ); + $processor->extractInfo( $this->dir, $info, 1 ); $extracted = $processor->getExtractedInfo(); $this->assertEquals( $expected, $extracted['globals']['wgHooks'] ); } @@ -119,8 +119,8 @@ class ExtensionProcessorTest extends MediaWikiTestCase { 'Bar' => 'somevalue' ), ) + self::$default; - $processor->extractInfo( $this->dir, $info ); - $processor->extractInfo( $this->dir, $info2 ); + $processor->extractInfo( $this->dir, $info, 1 ); + $processor->extractInfo( $this->dir, $info2, 1 ); $extracted = $processor->getExtractedInfo(); $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] ); $this->assertEquals( 10, $extracted['globals']['wgFoo'] ); @@ -159,7 +159,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { */ public function testExtracttExtensionMessagesFiles( $input, $expected ) { $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, $input + self::$default ); + $processor->extractInfo( $this->dir, $input + self::$default, 1 ); $out = $processor->getExtractedInfo(); foreach ( $expected as $key => $value ) { $this->assertEquals( $value, $out['globals'][$key] ); @@ -187,7 +187,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { */ public function testExtractMessagesDirs( $input, $expected ) { $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, $input + self::$default ); + $processor->extractInfo( $this->dir, $input + self::$default, 1 ); $out = $processor->getExtractedInfo(); foreach ( $expected as $key => $value ) { $this->assertEquals( $value, $out['globals'][$key] ); @@ -200,7 +200,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { */ public function testExtractResourceLoaderModules( $input, $expected ) { $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, $input + self::$default ); + $processor->extractInfo( $this->dir, $input + self::$default, 1 ); $out = $processor->getExtractedInfo(); foreach ( $expected as $key => $value ) { $this->assertEquals( $value, $out['globals'][$key] ); diff --git a/tests/phpunit/includes/registration/ExtensionRegistryTest.php b/tests/phpunit/includes/registration/ExtensionRegistryTest.php index c3a0c8d4..201cbfcd 100644 --- a/tests/phpunit/includes/registration/ExtensionRegistryTest.php +++ b/tests/phpunit/includes/registration/ExtensionRegistryTest.php @@ -218,6 +218,7 @@ class ExtensionRegistryTest extends MediaWikiTestCase { 'user' => array( 'right' => true, 'somethingtwo' => false, + 'nonduplicated' => true, ), ExtensionRegistry::MERGE_STRATEGY => 'array_plus_2d', ), @@ -233,6 +234,7 @@ class ExtensionRegistryTest extends MediaWikiTestCase { 'user' => array( 'somethingtwo' => true, 'right' => true, + 'nonduplicated' => true, ) ), ), diff --git a/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php b/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php new file mode 100644 index 00000000..0d11f621 --- /dev/null +++ b/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php @@ -0,0 +1,78 @@ +<?php + +/** + * @group ResourceLoader + */ +class DerivativeResourceLoaderContextTest extends PHPUnit_Framework_TestCase { + + protected static function getResourceLoaderContext() { + $resourceLoader = new ResourceLoader(); + $request = new FauxRequest( array( + 'lang' => 'zh', + 'modules' => 'test.context', + 'only' => 'scripts', + 'skin' => 'fallback', + 'target' => 'test', + ) ); + return new ResourceLoaderContext( $resourceLoader, $request ); + } + + public function testGet() { + $context = self::getResourceLoaderContext(); + $derived = new DerivativeResourceLoaderContext( $context ); + + $this->assertEquals( $derived->getLanguage(), 'zh' ); + $this->assertEquals( $derived->getModules(), array( 'test.context' ) ); + $this->assertEquals( $derived->getOnly(), 'scripts' ); + $this->assertEquals( $derived->getSkin(), 'fallback' ); + $this->assertEquals( $derived->getHash(), 'zh|ltr|fallback||||||scripts|' ); + } + + public function testSetLanguage() { + $context = self::getResourceLoaderContext(); + $derived = new DerivativeResourceLoaderContext( $context ); + + $derived->setLanguage( 'nl' ); + $this->assertEquals( $derived->getLanguage(), 'nl' ); + + $derived->setLanguage( 'he' ); + $this->assertEquals( $derived->getDirection(), 'rtl' ); + } + + public function testSetModules() { + $context = self::getResourceLoaderContext(); + $derived = new DerivativeResourceLoaderContext( $context ); + + $derived->setModules( array( 'test.override' ) ); + $this->assertEquals( $derived->getModules(), array( 'test.override' ) ); + } + + public function testSetOnly() { + $context = self::getResourceLoaderContext(); + $derived = new DerivativeResourceLoaderContext( $context ); + + $derived->setOnly( 'styles' ); + $this->assertEquals( $derived->getOnly(), 'styles' ); + + $derived->setOnly( null ); + $this->assertEquals( $derived->getOnly(), null ); + } + + public function testSetSkin() { + $context = self::getResourceLoaderContext(); + $derived = new DerivativeResourceLoaderContext( $context ); + + $derived->setSkin( 'override' ); + $this->assertEquals( $derived->getSkin(), 'override' ); + } + + public function testGetHash() { + $context = self::getResourceLoaderContext(); + $derived = new DerivativeResourceLoaderContext( $context ); + + $derived->setLanguage( 'nl' ); + // Assert that subclass is able to clear parent class "hash" member + $this->assertEquals( $derived->getHash(), 'nl|ltr|fallback||||||scripts|' ); + } + +} diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php index 122995a5..9d97b282 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php @@ -1,6 +1,7 @@ <?php /** + * @group Database * @group ResourceLoader */ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase { @@ -157,7 +158,7 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase { * @covers ResourceLoaderFileModule::getStyles * @covers ResourceLoaderFileModule::getStyleFiles */ - public function testMixedCssAnnotations( ) { + public function testMixedCssAnnotations() { $basePath = __DIR__ . '/../../data/css'; $testModule = new ResourceLoaderFileModule( array( 'localBasePath' => $basePath, @@ -225,23 +226,4 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase { $this->assertEquals( $rl->getTemplates(), $expected ); } - - public static function providerGetModifiedTime() { - $modules = self::getModules(); - - return array( - // Check the default value when no templates present in module is 1 - array( $modules['noTemplateModule'], 1 ), - ); - } - - /** - * @dataProvider providerGetModifiedTime - * @covers ResourceLoaderFileModule::getModifiedTime - */ - public function testGetModifiedTime( $module, $expected ) { - $rl = new ResourceLoaderFileModule( $module ); - $ts = $rl->getModifiedTime( $this->getResourceLoaderContext() ); - $this->assertEquals( $ts, $expected ); - } } diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php index 758cfe19..cc121ba3 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php @@ -109,9 +109,6 @@ class ResourceLoaderImageTest extends ResourceLoaderTestCase { class ResourceLoaderImageTestable extends ResourceLoaderImage { // Make some protected methods public - public function getPath( ResourceLoaderContext $context ) { - return parent::getPath( $context ); - } public function massageSvgPathdata( $svg ) { return parent::massageSvgPathdata( $svg ); } diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php index 6d1ed4e0..41653fb0 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php @@ -3,10 +3,9 @@ class ResourceLoaderModuleTest extends ResourceLoaderTestCase { /** - * @covers ResourceLoaderModule::getDefinitionSummary - * @covers ResourceLoaderFileModule::getDefinitionSummary + * @covers ResourceLoaderModule::getVersionHash */ - public function testDefinitionSummary() { + public function testGetVersionHash() { $context = $this->getResourceLoaderContext(); $baseParams = array( @@ -16,15 +15,13 @@ class ResourceLoaderModuleTest extends ResourceLoaderTestCase { ); $module = new ResourceLoaderFileModule( $baseParams ); - - $jsonSummary = json_encode( $module->getDefinitionSummary( $context ) ); + $version = json_encode( $module->getVersionHash( $context ) ); // Exactly the same $module = new ResourceLoaderFileModule( $baseParams ); - $this->assertEquals( - $jsonSummary, - json_encode( $module->getDefinitionSummary( $context ) ), + $version, + json_encode( $module->getVersionHash( $context ) ), 'Instance is insignificant' ); @@ -32,10 +29,9 @@ class ResourceLoaderModuleTest extends ResourceLoaderTestCase { $module = new ResourceLoaderFileModule( array( 'dependencies' => array( 'mediawiki', 'jquery' ), ) + $baseParams ); - $this->assertEquals( - $jsonSummary, - json_encode( $module->getDefinitionSummary( $context ) ), + $version, + json_encode( $module->getVersionHash( $context ) ), 'Order of dependencies is insignificant' ); @@ -43,10 +39,9 @@ class ResourceLoaderModuleTest extends ResourceLoaderTestCase { $module = new ResourceLoaderFileModule( array( 'messages' => array( 'world', 'hello' ), ) + $baseParams ); - $this->assertEquals( - $jsonSummary, - json_encode( $module->getDefinitionSummary( $context ) ), + $version, + json_encode( $module->getVersionHash( $context ) ), 'Order of messages is insignificant' ); @@ -54,20 +49,43 @@ class ResourceLoaderModuleTest extends ResourceLoaderTestCase { $module = new ResourceLoaderFileModule( array( 'scripts' => array( 'bar.js', 'foo.js' ), ) + $baseParams ); - $this->assertNotEquals( - $jsonSummary, - json_encode( $module->getDefinitionSummary( $context ) ), + $version, + json_encode( $module->getVersionHash( $context ) ), 'Order of scripts is significant' ); // Subclass $module = new ResourceLoaderFileModuleTestModule( $baseParams ); - $this->assertNotEquals( - $jsonSummary, - json_encode( $module->getDefinitionSummary( $context ) ), + $version, + json_encode( $module->getVersionHash( $context ) ), 'Class is significant' ); } + + /** + * @covers ResourceLoaderModule::validateScriptFile + */ + public function testValidateScriptFile() { + $context = $this->getResourceLoaderContext(); + + $module = new ResourceLoaderTestModule( array( + 'script' => "var a = 'this is';\n {\ninvalid" + ) ); + $this->assertEquals( + $module->getScript( $context ), + 'mw.log.error("JavaScript parse error: Parse error: Unexpected token; token } expected in file \'input\' on line 3");', + 'Replace invalid syntax with error logging' + ); + + $module = new ResourceLoaderTestModule( array( + 'script' => "\n'valid';" + ) ); + $this->assertEquals( + $module->getScript( $context ), + "\n'valid';", + 'Leave valid scripts as-is' + ); + } } diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php index 7f3506cc..cb916142 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php @@ -10,7 +10,8 @@ class ResourceLoaderStartUpModuleTest extends ResourceLoaderTestCase { 'out' => ' mw.loader.addSource( { "local": "/w/load.php" -} );mw.loader.register( [] );' +} ); +mw.loader.register( [] );' ) ), array( array( 'msg' => 'Basic registry', @@ -20,10 +21,11 @@ mw.loader.addSource( { 'out' => ' mw.loader.addSource( { "local": "/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.blank", - 1388534400 + "wvTifjse" ] ] );', ) ), @@ -37,20 +39,21 @@ mw.loader.addSource( { 'out' => ' mw.loader.addSource( { "local": "/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.blank", - 1388534400 + "wvTifjse" ], [ "test.group.foo", - 1388534400, + "wvTifjse", [], "x-foo" ], [ "test.group.bar", - 1388534400, + "wvTifjse", [], "x-bar" ] @@ -65,10 +68,11 @@ mw.loader.addSource( { 'out' => ' mw.loader.addSource( { "local": "/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.blank", - 1388534400 + "wvTifjse" ] ] );' ) ), @@ -87,10 +91,11 @@ mw.loader.addSource( { mw.loader.addSource( { "local": "/w/load.php", "example": "http://example.org/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.blank", - 1388534400, + "wvTifjse", [], null, "example" @@ -123,14 +128,15 @@ mw.loader.addSource( { 'out' => ' mw.loader.addSource( { "local": "/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.x.core", - 1388534400 + "wvTifjse" ], [ "test.x.polyfill", - 1388534400, + "wvTifjse", [], null, null, @@ -138,7 +144,7 @@ mw.loader.addSource( { ], [ "test.y.polyfill", - 1388534400, + "wvTifjse", [], null, null, @@ -146,7 +152,7 @@ mw.loader.addSource( { ], [ "test.z.foo", - 1388534400, + "wvTifjse", [ 0, 1, @@ -219,39 +225,40 @@ mw.loader.addSource( { mw.loader.addSource( { "local": "/w/load.php", "example": "http://example.org/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.blank", - 1388534400 + "wvTifjse" ], [ "test.x.core", - 1388534400 + "wvTifjse" ], [ "test.x.util", - 1388534400, + "wvTifjse", [ 1 ] ], [ "test.x.foo", - 1388534400, + "wvTifjse", [ 1 ] ], [ "test.x.bar", - 1388534400, + "wvTifjse", [ 2 ] ], [ "test.x.quux", - 1388534400, + "wvTifjse", [ 3, 4, @@ -260,25 +267,25 @@ mw.loader.addSource( { ], [ "test.group.foo.1", - 1388534400, + "wvTifjse", [], "x-foo" ], [ "test.group.foo.2", - 1388534400, + "wvTifjse", [], "x-foo" ], [ "test.group.bar.1", - 1388534400, + "wvTifjse", [], "x-bar" ], [ "test.group.bar.2", - 1388534400, + "wvTifjse", [], "x-bar", "example" @@ -342,10 +349,10 @@ mw.loader.addSource( { $rl->register( $modules ); $module = new ResourceLoaderStartUpModule(); $this->assertEquals( -'mw.loader.addSource({"local":"/w/load.php"});' +'mw.loader.addSource({"local":"/w/load.php"});' . "\n" . 'mw.loader.register([' -. '["test.blank",1388534400],' -. '["test.min",1388534400,[0],null,null,' +. '["test.blank","wvTifjse"],' +. '["test.min","wvTifjse",[0],null,null,' . '"return!!(window.JSON\u0026\u0026JSON.parse\u0026\u0026JSON.stringify);"' . ']]);', $module->getModuleRegistrations( $context ), @@ -364,14 +371,15 @@ mw.loader.addSource( { $this->assertEquals( 'mw.loader.addSource( { "local": "/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.blank", - 1388534400 + "wvTifjse" ], [ "test.min", - 1388534400, + "wvTifjse", [ 0 ], diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php index ca7307ec..b6838859 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php @@ -6,17 +6,8 @@ class ResourceLoaderTest extends ResourceLoaderTestCase { parent::setUp(); $this->setMwGlobals( array( - 'wgResourceLoaderLESSFunctions' => array( - 'test-sum' => function ( $frame, $less ) { - $sum = 0; - foreach ( $frame[2] as $arg ) { - $sum += (int)$arg[1]; - } - return $sum; - }, - ), 'wgResourceLoaderLESSImportPaths' => array( - dirname( dirname( __DIR__ ) ) . '/data/less/common', + dirname( dirname( __DIR__ ) ) . '/data/less/common', ), 'wgResourceLoaderLESSVars' => array( 'foo' => '2px', diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php index 93a3ebba..8cefec75 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php @@ -31,16 +31,15 @@ class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase { $module = new ResourceLoaderWikiModule( $params ); $module->setConfig( $config ); - // Use getDefinitionSummary because getPages is protected - $summary = $module->getDefinitionSummary( ResourceLoaderContext::newDummyContext() ); - $this->assertEquals( - $expected, - $summary['pages'] - ); + // Because getPages is protected.. + $getPages = new ReflectionMethod( $module, 'getPages' ); + $getPages->setAccessible( true ); + $out = $getPages->invoke( $module, ResourceLoaderContext::newDummyContext() ); + $this->assertEquals( $expected, $out ); } public static function provideGetPages() { - $settings = array( + $settings = self::getSettings() + array( 'UseSiteJs' => true, 'UseSiteCss' => true, ); @@ -110,39 +109,27 @@ class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase { array( array(), 'test1', true ), // 'site' module with a non-empty page array( - array( - 'MediaWiki:Common.js' => array( - 'timestamp' => 123456789, - 'length' => 1234 - ) - ), 'site', false, + array( 'MediaWiki:Common.js' => array( 'rev_sha1' => 'dmh6qn', 'rev_len' => 1234 ) ), + 'site', + false, ), // 'site' module with an empty page array( - array( - 'MediaWiki:Monobook.js' => array( - 'timestamp' => 987654321, - 'length' => 0, - ), - ), 'site', false, + array( 'MediaWiki:Foo.js' => array( 'rev_sha1' => 'phoi', 'rev_len' => 0 ) ), + 'site', + false, ), // 'user' module with a non-empty page array( - array( - 'User:FooBar/common.js' => array( - 'timestamp' => 246813579, - 'length' => 25, - ), - ), 'user', false, + array( 'User:Example/common.js' => array( 'rev_sha1' => 'j7ssba', 'rev_len' => 25 ) ), + 'user', + false, ), // 'user' module with an empty page array( - array( - 'User:FooBar/monobook.js' => array( - 'timestamp' => 1357924680, - 'length' => 0, - ), - ), 'user', true, + array( 'User:Example/foo.js' => array( 'rev_sha1' => 'phoi', 'rev_len' => 0 ) ), + 'user', + true, ), ); } diff --git a/tests/phpunit/includes/site/CachingSiteStoreTest.php b/tests/phpunit/includes/site/CachingSiteStoreTest.php index d0a79803..4305ceb9 100644 --- a/tests/phpunit/includes/site/CachingSiteStoreTest.php +++ b/tests/phpunit/includes/site/CachingSiteStoreTest.php @@ -96,17 +96,17 @@ class CachingSiteStoreTest extends MediaWikiTestCase { ->getMock(); // php 5.3 compatibility! - $self = $this; + $that = $this; $dbSiteStore->expects( $this->any() ) ->method( 'getSite' ) - ->will( $this->returnValue( $self->getTestSite() ) ); + ->will( $this->returnValue( $that->getTestSite() ) ); $dbSiteStore->expects( $this->any() ) ->method( 'getSites' ) - ->will( $this->returnCallback( function() use( $self ) { + ->will( $this->returnCallback( function() use ( $that ) { $siteList = new SiteList(); - $siteList->setSite( $self->getTestSite() ); + $siteList->setSite( $that->getTestSite() ); return $siteList; } ) ); diff --git a/tests/phpunit/includes/site/DBSiteStoreTest.php b/tests/phpunit/includes/site/DBSiteStoreTest.php index 673ba54d..48ef5243 100644 --- a/tests/phpunit/includes/site/DBSiteStoreTest.php +++ b/tests/phpunit/includes/site/DBSiteStoreTest.php @@ -130,4 +130,28 @@ class DBSiteStoreTest extends MediaWikiTestCase { $sites = $store->getSites(); $this->assertEquals( 0, $sites->count() ); } + + /** + * @covers DBSiteStore::getSites + */ + public function testGetSitesDefaultOrder() { + $store = new DBSiteStore(); + $siteB = new Site(); + $siteB->setGlobalId( 'B' ); + $siteA = new Site(); + $siteA->setGlobalId( 'A' ); + $store->saveSites( array( $siteB, $siteA ) ); + + $sites = $store->getSites(); + $siteIdentifiers = array(); + /** @var Site $site */ + foreach ( $sites as $site ) { + $siteIdentifiers[] = $site->getGlobalId(); + } + $this->assertSame( array( 'A', 'B' ), $siteIdentifiers ); + + // Note: SiteList::getGlobalIdentifiers uses an other internal state. Iteration must be + // tested separately. + $this->assertSame( array( 'A', 'B' ), $sites->getGlobalIdentifiers() ); + } } diff --git a/tests/phpunit/includes/site/HashSiteStoreTest.php b/tests/phpunit/includes/site/HashSiteStoreTest.php index 49a96338..bebc0936 100644 --- a/tests/phpunit/includes/site/HashSiteStoreTest.php +++ b/tests/phpunit/includes/site/HashSiteStoreTest.php @@ -32,7 +32,7 @@ class HashSiteStoreTest extends MediaWikiTestCase { public function testGetSites() { $expectedSites = array(); - foreach( TestSites::getSites() as $testSite ) { + foreach ( TestSites::getSites() as $testSite ) { $siteId = $testSite->getGlobalId(); $expectedSites[$siteId] = $testSite; } diff --git a/tests/phpunit/includes/site/SiteExporterTest.php b/tests/phpunit/includes/site/SiteExporterTest.php index 19dd0aa1..7be19ef9 100644 --- a/tests/phpunit/includes/site/SiteExporterTest.php +++ b/tests/phpunit/includes/site/SiteExporterTest.php @@ -53,7 +53,7 @@ class SiteExporterTest extends PHPUnit_Framework_TestCase { $exporter->exportSites( array( $foo, $acme ) ); fseek( $tmp, 0 ); - $xml = fread( $tmp, 16*1024 ); + $xml = fread( $tmp, 16 * 1024 ); $this->assertContains( '<sites ', $xml ); $this->assertContains( '<site>', $xml ); @@ -133,7 +133,7 @@ class SiteExporterTest extends PHPUnit_Framework_TestCase { $exporter->exportSites( $sites ); fseek( $tmp, 0 ); - $xml = fread( $tmp, 16*1024 ); + $xml = fread( $tmp, 16 * 1024 ); $actualSites = new SiteList(); $store = $this->newSiteStore( $actualSites ); diff --git a/tests/phpunit/includes/site/SiteImporterTest.php b/tests/phpunit/includes/site/SiteImporterTest.php index cb0316ab..b11b1a9f 100644 --- a/tests/phpunit/includes/site/SiteImporterTest.php +++ b/tests/phpunit/includes/site/SiteImporterTest.php @@ -34,11 +34,11 @@ class SiteImporterTest extends PHPUnit_Framework_TestCase { private function newSiteImporter( array $expectedSites, $errorCount ) { $store = $this->getMock( 'SiteStore' ); - $self = $this; + $that = $this; $store->expects( $this->once() ) ->method( 'saveSites' ) - ->will( $this->returnCallback( function ( $sites ) use ( $expectedSites, $self ) { - $self->assertSitesEqual( $expectedSites, $sites ); + ->will( $this->returnCallback( function ( $sites ) use ( $expectedSites, $that ) { + $that->assertSitesEqual( $expectedSites, $sites ); } ) ); $store->expects( $this->any() ) @@ -141,12 +141,12 @@ class SiteImporterTest extends PHPUnit_Framework_TestCase { /** * @dataProvider provideImportFromXML */ - public function testImportFromXML( $xml, array $expectedSites, $errorCount = 0 ) { + public function testImportFromXML( $xml, array $expectedSites, $errorCount = 0 ) { $importer = $this->newSiteImporter( $expectedSites, $errorCount ); $importer->importFromXML( $xml ); } - public function testImportFromXML_malformed() { + public function testImportFromXML_malformed() { $this->setExpectedException( 'Exception' ); $store = $this->getMock( 'SiteStore' ); @@ -154,7 +154,7 @@ class SiteImporterTest extends PHPUnit_Framework_TestCase { $importer->importFromXML( 'THIS IS NOT XML' ); } - public function testImportFromFile() { + public function testImportFromFile() { $foo = Site::newForType( Site::TYPE_UNKNOWN ); $foo->setGlobalId( 'Foo' ); diff --git a/tests/phpunit/includes/specials/SpecialBlankPageTest.php b/tests/phpunit/includes/specials/SpecialBlankPageTest.php new file mode 100644 index 00000000..1d4f5e51 --- /dev/null +++ b/tests/phpunit/includes/specials/SpecialBlankPageTest.php @@ -0,0 +1,25 @@ +<?php + +/** + * @licence GNU GPL v2+ + * @author Adam Shorland + * + * @covers SpecialBlankpage + */ +class SpecialBlankPageTest extends SpecialPageTestBase { + + /** + * Returns a new instance of the special page under test. + * + * @return SpecialPage + */ + protected function newSpecialPage() { + return new SpecialBlankpage(); + } + + public function testHasWikiMsg() { + list( $html, ) = $this->executeSpecialPage(); + $this->assertContains( wfMessage( 'intentionallyblankpage' )->text(), $html ); + } + +} diff --git a/tests/phpunit/includes/specials/SpecialPageTestBase.php b/tests/phpunit/includes/specials/SpecialPageTestBase.php new file mode 100644 index 00000000..9c7b0f00 --- /dev/null +++ b/tests/phpunit/includes/specials/SpecialPageTestBase.php @@ -0,0 +1,165 @@ +<?php + +/** + * Base class for testing special pages. + * + * @since 1.26 + * + * @licence GNU GPL v2+ + * @author Jeroen De Dauw < jeroendedauw@gmail.com > + * @author Daniel Kinzler + * @author Adam Shorland + * @author Thiemo Mättig + */ +abstract class SpecialPageTestBase extends MediaWikiTestCase { + + private $obLevel; + + protected function setUp() { + parent::setUp(); + + $this->obLevel = ob_get_level(); + } + + protected function tearDown() { + $obLevel = ob_get_level(); + + while ( ob_get_level() > $this->obLevel ) { + ob_end_clean(); + } + + if ( $obLevel !== $this->obLevel ) { + $this->fail( + "Test changed output buffer level: was {$this->obLevel} before test, but $obLevel after test." + ); + } + + parent::tearDown(); + } + + /** + * Returns a new instance of the special page under test. + * + * @return SpecialPage + */ + abstract protected function newSpecialPage(); + + /** + * @param string $subPage The subpage parameter to call the page with + * @param WebRequest|null $request Web request that may contain URL parameters, etc + * @param Language|string|null $language The language which should be used in the context + * @param User|null $user The user which should be used in the context of this special page + * + * @throws Exception + * @return array( string, WebResponse ) A two-elements array containing the HTML output + * generated by the special page as well as the response object. + */ + protected function executeSpecialPage( + $subPage = '', + WebRequest $request = null, + $language = null, + User $user = null + ) { + $context = $this->newContext( $request, $language, $user ); + + $output = new OutputPage( $context ); + $context->setOutput( $output ); + + $page = $this->newSpecialPage(); + $page->setContext( $context ); + $output->setTitle( $page->getPageTitle() ); + + $html = $this->getHTMLFromSpecialPage( $page, $subPage ); + $response = $context->getRequest()->response(); + + if ( $response instanceof FauxResponse ) { + $code = $response->getStatusCode(); + + if ( $code > 0 ) { + $response->header( 'Status: ' . $code . ' ' . HttpStatus::getMessage( $code ) ); + } + } + + return array( $html, $response ); + } + + /** + * @param WebRequest|null $request + * @param Language|string|null $language + * @param User|null $user + * + * @return DerivativeContext + */ + private function newContext( + WebRequest $request = null, + $language = null, + User $user = null + ) { + $context = new DerivativeContext( RequestContext::getMain() ); + + $context->setRequest( $request ?: new FauxRequest() ); + + if ( $language !== null ) { + $context->setLanguage( $language ); + } + + if ( $user !== null ) { + $context->setUser( $user ); + } + + $this->setEditTokenFromUser( $context ); + + return $context; + } + + /** + * If we are trying to edit and no token is set, supply one. + * + * @param DerivativeContext $context + */ + private function setEditTokenFromUser( DerivativeContext $context ) { + $request = $context->getRequest(); + + // Edits via GET are a security issue and should not succeed. On the other hand, not all + // POST requests are edits, but should ignore unused parameters. + if ( !$request->getCheck( 'wpEditToken' ) && $request->wasPosted() ) { + $request->setVal( 'wpEditToken', $context->getUser()->getEditToken() ); + } + } + + /** + * @param SpecialPage $page + * @param string $subPage + * + * @throws Exception + * @return string HTML + */ + private function getHTMLFromSpecialPage( SpecialPage $page, $subPage ) { + ob_start(); + + try { + $page->execute( $subPage ); + + $output = $page->getOutput(); + + if ( $output->getRedirect() !== '' ) { + $output->output(); + $html = ob_get_contents(); + } elseif ( $output->isDisabled() ) { + $html = ob_get_contents(); + } else { + $html = $output->getHTML(); + } + } catch ( Exception $ex ) { + ob_end_clean(); + + // Re-throw exception after "finally" handling because PHP 5.3 doesn't have "finally". + throw $ex; + } + + ob_end_clean(); + + return $html; + } + +} diff --git a/tests/phpunit/includes/specials/SpecialPreferencesTest.php b/tests/phpunit/includes/specials/SpecialPreferencesTest.php index 4f6c4116..1545d7ec 100644 --- a/tests/phpunit/includes/specials/SpecialPreferencesTest.php +++ b/tests/phpunit/includes/specials/SpecialPreferencesTest.php @@ -4,10 +4,11 @@ * * Copyright © 2013, Antoine Musso * Copyright © 2013, Wikimedia Foundation Inc. - * */ /** + * @group Database + * * @covers SpecialPreferences */ class SpecialPreferencesTest extends MediaWikiTestCase { diff --git a/tests/phpunit/includes/specials/SpecialSearchTest.php b/tests/phpunit/includes/specials/SpecialSearchTest.php index 83489c65..13c28381 100644 --- a/tests/phpunit/includes/specials/SpecialSearchTest.php +++ b/tests/phpunit/includes/specials/SpecialSearchTest.php @@ -136,9 +136,113 @@ class SpecialSearchTest extends MediaWikiTestCase { # Compare :-] $this->assertRegExp( - '/' . preg_quote( $term ) . '/', + '/' . preg_quote( $term, '/' ) . '/', $pageTitle, "Search term '{$term}' should not be expanded in Special:Search <title>" ); } + + public function provideRewriteQueryWithSuggestion() { + return array( + array( + 'With suggestion and no rewritten query shows did you mean', + '/Did you mean: <a[^>]+>first suggestion/', + new SpecialSearchTestMockResultSet( 'first suggestion', null, array( + SearchResult::newFromTitle( Title::newMainPage() ), + ) ), + ), + + array( + 'With rewritten query informs user of change', + '/Showing results for <a[^>]+>first suggestion/', + new SpecialSearchTestMockResultSet( 'asdf', 'first suggestion', array( + SearchResult::newFromTitle( Title::newMainPage() ), + ) ), + ), + + array( + 'When both queries have no results user gets no results', + '/There were no results matching the query/', + new SpecialSearchTestMockResultSet( 'first suggestion', 'first suggestion', array() ), + ), + ); + } + + /** + * @dataProvider provideRewriteQueryWithSuggestion + */ + public function testRewriteQueryWithSuggestion( $message, $expectRegex, $results ) { + $mockSearchEngine = $this->mockSearchEngine( $results ); + $search = $this->getMockBuilder( 'SpecialSearch' ) + ->setMethods( array( 'getSearchEngine' ) ) + ->getMock(); + $search->expects( $this->any() ) + ->method( 'getSearchEngine' ) + ->will( $this->returnValue( $mockSearchEngine ) ); + + $search->getContext()->setTitle( Title::makeTitle( NS_SPECIAL, 'Search' ) ); + $search->load(); + $search->showResults( 'this is a fake search' ); + + $html = $search->getContext()->getOutput()->getHTML(); + foreach ( (array)$expectRegex as $regex ) { + $this->assertRegExp( $regex, $html, $message ); + } + } + + protected function mockSearchEngine( $results ) { + $mock = $this->getMockBuilder( 'SearchEngine' ) + ->setMethods( array( 'searchText', 'searchTitle' ) ) + ->getMock(); + + $mock->expects( $this->any() ) + ->method( 'searchText' ) + ->will( $this->returnValue( $results ) ); + + return $mock; + } +} + +class SpecialSearchTestMockResultSet extends SearchResultSet { + protected $results; + protected $suggestion; + + public function __construct( $suggestion = null, $rewrittenQuery = null, array $results = array(), $containedSyntax = false) { + $this->suggestion = $suggestion; + $this->rewrittenQuery = $rewrittenQuery; + $this->results = $results; + $this->containedSyntax = $containedSyntax; + } + + public function numRows() { + return count( $this->results ); + } + + public function getTotalHits() { + return $this->numRows(); + } + + public function hasSuggestion() { + return $this->suggestion !== null; + } + + public function getSuggestionQuery() { + return $this->suggestion; + } + + public function getSuggestionSnippet() { + return $this->suggestion; + } + + public function hasRewrittenQuery() { + return $this->rewrittenQuery !== null; + } + + public function getQueryAfterRewrite() { + return $this->rewrittenQuery; + } + + public function getQueryAfterRewriteSnippet() { + return htmlspecialchars( $this->rewrittenQuery ); + } } diff --git a/tests/phpunit/includes/title/ForeignTitleTest.php b/tests/phpunit/includes/title/ForeignTitleTest.php index 599d2a33..10b7e28f 100644 --- a/tests/phpunit/includes/title/ForeignTitleTest.php +++ b/tests/phpunit/includes/title/ForeignTitleTest.php @@ -59,7 +59,7 @@ class ForeignTitleTest extends MediaWikiTestCase { $this->assertEquals( $expectedText, $title->getText() ); } - public function testUnknownNamespaceCheck( ) { + public function testUnknownNamespaceCheck() { $title = new ForeignTitle( null, 'this', 'that' ); $this->assertEquals( false, $title->isNamespaceIdKnown() ); @@ -67,7 +67,7 @@ class ForeignTitleTest extends MediaWikiTestCase { $this->assertEquals( 'that', $title->getText() ); } - public function testUnknownNamespaceError( ) { + public function testUnknownNamespaceError() { $this->setExpectedException( 'MWException' ); $title = new ForeignTitle( null, 'this', 'that' ); $title->getNamespaceId(); diff --git a/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php b/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php index cd0d0b1c..1e5f9d01 100644 --- a/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php +++ b/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php @@ -158,7 +158,7 @@ class MediaWikiPageLinkRendererTest extends MediaWikiTestCase { function ( TitleValue $title ) { return str_replace( '_', ' ', "$title" ); } - )); + ) ); $renderer = new MediaWikiPageLinkRenderer( $formatter, '/' ); $actual = $renderer->renderWikitextLink( $title, $text ); diff --git a/tests/phpunit/includes/upload/UploadStashTest.php b/tests/phpunit/includes/upload/UploadStashTest.php index d5d1188e..8f9eabe9 100644 --- a/tests/phpunit/includes/upload/UploadStashTest.php +++ b/tests/phpunit/includes/upload/UploadStashTest.php @@ -20,7 +20,7 @@ class UploadStashTest extends MediaWikiTestCase { parent::setUp(); // Setup a file for bug 29408 - $this->bug29408File = __DIR__ . '/bug29408'; + $this->bug29408File = wfTempDir() . '/bug29408'; file_put_contents( $this->bug29408File, "\x00" ); self::$users = array( diff --git a/tests/phpunit/includes/utils/AvroValidatorTest.php b/tests/phpunit/includes/utils/AvroValidatorTest.php new file mode 100644 index 00000000..52c242c1 --- /dev/null +++ b/tests/phpunit/includes/utils/AvroValidatorTest.php @@ -0,0 +1,96 @@ +<?php +/** + * Tests for IP validity functions. + * + * Ported from /t/inc/IP.t by avar. + * + * @group IP + * @todo Test methods in this call should be split into a method and a + * dataprovider. + */ + +class AvroValidatorTest extends PHPUnit_Framework_TestCase { + public function setUp() { + if ( !class_exists( 'AvroSchema' ) ) { + $this->markTestSkipped( 'Avro is required to run the AvroValidatorTest' ); + } + parent::setUp(); + } + + public function getErrorsProvider() { + $stringSchema = AvroSchema::parse( json_encode( array( 'type' => 'string' ) ) ); + $recordSchema = AvroSchema::parse( json_encode( array( + 'type' => 'record', + 'name' => 'ut', + 'fields' => array( + array( 'name' => 'id', 'type' => 'int', 'required' => true ), + ), + ) ) ); + $enumSchema = AvroSchema::parse( json_encode( array( + 'type' => 'record', + 'name' => 'ut', + 'fields' => array( + array( 'name' => 'count', 'type' => array( 'int', 'null' ) ), + ), + ) ) ); + + return array( + array( + 'No errors with a simple string serialization', + $stringSchema, 'foobar', array(), + ), + + array( + 'Cannot serialize integer into string', + $stringSchema, 5, 'Expected string, but recieved integer', + ), + + array( + 'Cannot serialize array into string', + $stringSchema, array(), 'Expected string, but recieved array', + ), + + array( + 'allows and ignores extra fields', + $recordSchema, array( 'id' => 4, 'foo' => 'bar' ), array(), + ), + + array( + 'detects missing fields', + $recordSchema, array(), array( 'id' => 'Missing expected field' ), + ), + + array( + 'handles first element in enum', + $enumSchema, array( 'count' => 4 ), array(), + ), + + array( + 'handles second element in enum', + $enumSchema, array( 'count' => null ), array(), + ), + + array( + 'rejects element not in union', + $enumSchema, array( 'count' => 'invalid' ), array( 'count' => array( + 'Expected any one of these to be true', + array( + 'Expected integer, but recieved string', + 'Expected null, but recieved string', + ) + ) ) + ), + ); + } + + /** + * @dataProvider getErrorsProvider + */ + public function testGetErrors( $message, $schema, $datum, $expected ) { + $this->assertEquals( + $expected, + AvroValidator::getErrors( $schema, $datum ), + $message + ); + } +} diff --git a/tests/phpunit/includes/utils/BatchRowUpdateTest.php b/tests/phpunit/includes/utils/BatchRowUpdateTest.php new file mode 100644 index 00000000..a2b35f39 --- /dev/null +++ b/tests/phpunit/includes/utils/BatchRowUpdateTest.php @@ -0,0 +1,243 @@ +<?php + +/** + * Tests for BatchRowUpdate and its components + * + * @group db + */ +class BatchRowUpdateTest extends MediaWikiTestCase { + + public function testWriterBasicFunctionality() { + $db = $this->mockDb(); + $writer = new BatchRowWriter( $db, 'echo_event' ); + + $updates = array( + self::mockUpdate( array( 'something' => 'changed' ) ), + self::mockUpdate( array( 'otherthing' => 'changed' ) ), + self::mockUpdate( array( 'and' => 'something', 'else' => 'changed' ) ), + ); + + $db->expects( $this->exactly( count( $updates ) ) ) + ->method( 'update' ); + + $writer->write( $updates ); + } + + static protected function mockUpdate( array $changes ) { + static $i = 0; + return array( + 'primaryKey' => array( 'event_id' => $i++ ), + 'changes' => $changes, + ); + } + + public function testReaderBasicIterate() { + $db = $this->mockDb(); + $batchSize = 2; + $reader = new BatchRowIterator( $db, 'some_table', 'id_field', $batchSize ); + + $response = $this->genSelectResult( $batchSize, /*numRows*/ 5, function() { + static $i = 0; + return array( 'id_field' => ++$i ); + } ); + $db->expects( $this->exactly( count( $response ) ) ) + ->method( 'select' ) + ->will( $this->consecutivelyReturnFromSelect( $response ) ); + + $pos = 0; + foreach ( $reader as $rows ) { + $this->assertEquals( $response[$pos], $rows, "Testing row in position $pos" ); + $pos++; + } + // -1 is because the final array() marks the end and isnt included + $this->assertEquals( count( $response ) - 1, $pos ); + } + + static public function provider_readerGetPrimaryKey() { + $row = array( + 'id_field' => 42, + 'some_col' => 'dvorak', + 'other_col' => 'samurai', + ); + return array( + + array( + 'Must return single column pk when requested', + array( 'id_field' => 42 ), + $row + ), + + array( + 'Must return multiple column pks when requested', + array( 'id_field' => 42, 'other_col' => 'samurai' ), + $row + ), + + ); + } + + /** + * @dataProvider provider_readerGetPrimaryKey + */ + public function testReaderGetPrimaryKey( $message, array $expected, array $row ) { + $reader = new BatchRowIterator( $this->mockDb(), 'some_table', array_keys( $expected ), 8675309 ); + $this->assertEquals( $expected, $reader->extractPrimaryKeys( (object) $row ), $message ); + } + + static public function provider_readerSetFetchColumns() { + return array( + + array( + 'Must merge primary keys into select conditions', + // Expected column select + array( 'foo', 'bar' ), + // primary keys + array( 'foo' ), + // setFetchColumn + array( 'bar' ) + ), + + array( + 'Must not merge primary keys into the all columns selector', + // Expected column select + array( '*' ), + // primary keys + array( 'foo' ), + // setFetchColumn + array( '*' ), + ), + + array( + 'Must not duplicate primary keys into column selector', + // Expected column select. + // TODO: figure out how to only assert the array_values portion and not the keys + array( 0 => 'foo', 1 => 'bar', 3 => 'baz' ), + // primary keys + array( 'foo', 'bar', ), + // setFetchColumn + array( 'bar', 'baz' ), + ), + ); + } + + /** + * @dataProvider provider_readerSetFetchColumns + */ + public function testReaderSetFetchColumns( $message, array $columns, array $primaryKeys, array $fetchColumns ) { + $db = $this->mockDb(); + $db->expects( $this->once() ) + ->method( 'select' ) + ->with( 'some_table', $columns ) // only testing second parameter of DatabaseBase::select + ->will( $this->returnValue( new ArrayIterator( array() ) ) ); + + $reader = new BatchRowIterator( $db, 'some_table', $primaryKeys, 22 ); + $reader->setFetchColumns( $fetchColumns ); + // triggers first database select + $reader->rewind(); + } + + static public function provider_readerSelectConditions() { + return array( + + array( + "With single primary key must generate id > 'value'", + // Expected second iteration + array( "( id_field > '3' )" ), + // Primary key(s) + 'id_field', + ), + + array( + 'With multiple primary keys the first conditions must use >= and the final condition must use >', + // Expected second iteration + array( "( id_field = '3' AND foo > '103' ) OR ( id_field > '3' )" ), + // Primary key(s) + array( 'id_field', 'foo' ), + ), + + ); + } + + /** + * Slightly hackish to use reflection, but asserting different parameters + * to consecutive calls of DatabaseBase::select in phpunit is error prone + * + * @dataProvider provider_readerSelectConditions + */ + public function testReaderSelectConditionsMultiplePrimaryKeys( $message, $expectedSecondIteration, $primaryKeys, $batchSize = 3 ) { + $results = $this->genSelectResult( $batchSize, $batchSize * 3, function() { + static $i = 0, $j = 100, $k = 1000; + return array( 'id_field' => ++$i, 'foo' => ++$j, 'bar' => ++$k ); + } ); + $db = $this->mockDbConsecutiveSelect( $results ); + + $conditions = array( 'bar' => 42, 'baz' => 'hai' ); + $reader = new BatchRowIterator( $db, 'some_table', $primaryKeys, $batchSize ); + $reader->addConditions( $conditions ); + + $buildConditions = new ReflectionMethod( $reader, 'buildConditions' ); + $buildConditions->setAccessible( true ); + + // On first iteration only the passed conditions must be used + $this->assertEquals( $conditions, $buildConditions->invoke( $reader ), + 'First iteration must return only the conditions passed in addConditions' ); + $reader->rewind(); + + // Second iteration must use the maximum primary key of last set + $this->assertEquals( + $conditions + $expectedSecondIteration, + $buildConditions->invoke( $reader ), + $message + ); + } + + protected function mockDbConsecutiveSelect( array $retvals ) { + $db = $this->mockDb(); + $db->expects( $this->any() ) + ->method( 'select' ) + ->will( $this->consecutivelyReturnFromSelect( $retvals ) ); + $db->expects( $this->any() ) + ->method( 'addQuotes' ) + ->will( $this->returnCallback( function( $value ) { + return "'$value'"; // not real quoting: doesn't matter in test + } ) ); + + return $db; + } + + protected function consecutivelyReturnFromSelect( array $results ) { + $retvals = array(); + foreach ( $results as $rows ) { + // The DatabaseBase::select method returns iterators, so we do too. + $retvals[] = $this->returnValue( new ArrayIterator( $rows ) ); + } + + return call_user_func_array( array( $this, 'onConsecutiveCalls' ), $retvals ); + } + + + protected function genSelectResult( $batchSize, $numRows, $rowGenerator ) { + $res = array(); + for ( $i = 0; $i < $numRows; $i += $batchSize ) { + $rows = array(); + for ( $j = 0; $j < $batchSize && $i + $j < $numRows; $j++ ) { + $rows [] = (object) call_user_func( $rowGenerator ); + } + $res[] = $rows; + } + $res[] = array(); // termination condition requires empty result for last row + return $res; + } + + protected function mockDb() { + // Cant mock from DatabaseType or DatabaseBase, they dont + // have the full gamut of methods + $databaseMysql = $this->getMockBuilder( 'DatabaseMysql' ) + ->disableOriginalConstructor() + ->getMock(); + $databaseMysql->expects( $this->any() ) + ->method( 'isOpen' ) + ->will( $this->returnValue( true ) ); + return $databaseMysql; + } +} diff --git a/tests/phpunit/includes/utils/IPTest.php b/tests/phpunit/includes/utils/IPTest.php index 09c1587d..04b8f486 100644 --- a/tests/phpunit/includes/utils/IPTest.php +++ b/tests/phpunit/includes/utils/IPTest.php @@ -11,29 +11,37 @@ class IPTest extends PHPUnit_Framework_TestCase { /** - * not sure it should be tested with boolean false. hashar 20100924 * @covers IP::isIPAddress + * @dataProvider provideInvalidIPs */ - public function testisIPAddress() { - $this->assertFalse( IP::isIPAddress( false ), 'Boolean false is not an IP' ); - $this->assertFalse( IP::isIPAddress( true ), 'Boolean true is not an IP' ); - $this->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 :: 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' + public function isNotIPAddress( $val, $desc ) { + $this->assertFalse( IP::isIPAddress( $val ), $desc ); + } + + /** + * Provide a list of things that aren't IP addresses + */ + public function provideInvalidIPs() { + return array( + array( false, 'Boolean false is not an IP' ), + array( true, 'Boolean true is not an IP' ), + array( '', 'Empty string is not an IP' ), + array( 'abc', 'Garbage IP string' ), + array( ':', 'Single ":" is not an IP' ), + array( '2001:0DB8::A:1::1', 'IPv6 with a double :: occurrence' ), + array( '2001:0DB8::A:1::', 'IPv6 with a double :: occurrence, last at end' ), + array( '::2001:0DB8::5:1', 'IPv6 with a double :: occurrence, firt at beginning' ), + array( '124.24.52', 'IPv4 not enough quads' ), + array( '24.324.52.13', 'IPv4 out of range' ), + array( '.24.52.13', 'IPv4 starts with period' ), + array( 'fc:100:300', 'IPv6 with only 3 words' ), ); - $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' ); - $this->assertFalse( IP::isIPAddress( 'fc:100:300' ), 'IPv6 with only 3 words' ); + } + /** + * @covers IP::isIPAddress + */ + public function testisIPAddress() { $this->assertTrue( IP::isIPAddress( '::' ), 'RFC 4291 IPv6 Unspecified Address' ); $this->assertTrue( IP::isIPAddress( '::1' ), 'RFC 4291 IPv6 Loopback Address' ); $this->assertTrue( IP::isIPAddress( '74.24.52.13/20', 'IPv4 range' ) ); @@ -107,20 +115,42 @@ class IPTest extends PHPUnit_Framework_TestCase { /** * @covers IP::isIPv4 + * @dataProvider provideInvalidIPv4Addresses + */ + public function testisNotIPv4( $bogusIP, $desc ) { + $this->assertFalse( IP::isIPv4( $bogusIP ), $desc ); + } + + public function provideInvalidIPv4Addresses() { + return array( + array( false, 'Boolean false is not an IP' ), + array( true, 'Boolean true is not an IP' ), + array( '', 'Empty string is not an IP' ), + array( 'abc', 'Letters are not an IP' ), + array( ':', 'A colon is not an IP' ), + array( '124.24.52', 'IPv4 not enough quads' ), + array( '24.324.52.13', 'IPv4 out of range' ), + array( '.24.52.13', 'IPv4 starts with period' ), + ); + } + + /** + * @covers IP::isIPv4 + * @dataProvider provideValidIPv4Address */ - public function testisIPv4() { - $this->assertFalse( IP::isIPv4( false ), 'Boolean false is not an IP' ); - $this->assertFalse( IP::isIPv4( true ), 'Boolean true is not an IP' ); - $this->assertFalse( IP::isIPv4( "" ), 'Empty string is not an IP' ); - $this->assertFalse( IP::isIPv4( 'abc' ) ); - $this->assertFalse( IP::isIPv4( ':' ) ); - $this->assertFalse( IP::isIPv4( '124.24.52' ), 'IPv4 not enough quads' ); - $this->assertFalse( IP::isIPv4( '24.324.52.13' ), 'IPv4 out of range' ); - $this->assertFalse( IP::isIPv4( '.24.52.13' ), 'IPv4 starts with period' ); + public function testIsIPv4( $ip, $desc ) { + $this->assertTrue( IP::isIPv4( $ip ), $desc ); + } - $this->assertTrue( IP::isIPv4( '124.24.52.13' ) ); - $this->assertTrue( IP::isIPv4( '1.24.52.13' ) ); - $this->assertTrue( IP::isIPv4( '74.24.52.13/20', 'IPv4 range' ) ); + /** + * Provide some IPv4 addresses and ranges + */ + public function provideValidIPv4Address() { + return array( + array( '124.24.52.13', 'Valid IPv4 address' ), + array( '1.24.52.13', 'Another valid IPv4 address' ), + array( '74.24.52.13/20', 'An IPv4 range' ), + ); } /** @@ -224,49 +254,56 @@ class IPTest extends PHPUnit_Framework_TestCase { } /** - * @covers IP::isValidBlock + * Provide some valid IP blocks */ - public function testValidBlocks() { - $valid = array( - '116.17.184.5/32', - '0.17.184.5/30', - '16.17.184.1/24', - '30.242.52.14/1', - '10.232.52.13/8', - '30.242.52.14/0', - '::e:f:2001/96', - '::c:f:2001/128', - '::10:f:2001/70', - '::fe:f:2001/1', - '::6d:f:2001/8', - '::fe:f:2001/0', + public function provideValidBlocks() { + return array( + array( '116.17.184.5/32' ), + array( '0.17.184.5/30' ), + array( '16.17.184.1/24' ), + array( '30.242.52.14/1' ), + array( '10.232.52.13/8' ), + array( '30.242.52.14/0' ), + array( '::e:f:2001/96' ), + array( '::c:f:2001/128' ), + array( '::10:f:2001/70' ), + array( '::fe:f:2001/1' ), + array( '::6d:f:2001/8' ), + array( '::fe:f:2001/0' ), ); - foreach ( $valid as $i ) { - $this->assertTrue( IP::isValidBlock( $i ), "$i is a valid IP block" ); - } } /** * @covers IP::isValidBlock + * @dataProvider provideValidBlocks */ - public function testInvalidBlocks() { - $invalid = array( - '116.17.184.5/33', - '0.17.184.5/130', - '16.17.184.1/-1', - '10.232.52.13/*', - '7.232.52.13/ab', - '11.232.52.13/', - '::e:f:2001/129', - '::c:f:2001/228', - '::10:f:2001/-1', - '::6d:f:2001/*', - '::86:f:2001/ab', - '::23:f:2001/', + public function testValidBlocks( $block ) { + $this->assertTrue( IP::isValidBlock( $block ), "$block is a valid IP block" ); + } + + /** + * @covers IP::isValidBlock + * @dataProvider provideInvalidBlocks + */ + public function testInvalidBlocks( $invalid ) { + $this->assertFalse( IP::isValidBlock( $invalid ), "$invalid is not a valid IP block" ); + } + + public function provideInvalidBlocks() { + return array( + array( '116.17.184.5/33' ), + array( '0.17.184.5/130' ), + array( '16.17.184.1/-1' ), + array( '10.232.52.13/*' ), + array( '7.232.52.13/ab' ), + array( '11.232.52.13/' ), + array( '::e:f:2001/129' ), + array( '::c:f:2001/228' ), + array( '::10:f:2001/-1' ), + array( '::6d:f:2001/*' ), + array( '::86:f:2001/ab' ), + array( '::23:f:2001/' ), ); - foreach ( $invalid as $i ) { - $this->assertFalse( IP::isValidBlock( $i ), "$i is not a valid IP block" ); - } } /** @@ -310,16 +347,31 @@ class IPTest extends PHPUnit_Framework_TestCase { /** * @covers IP::isPublic + * @dataProvider provideIsPublic */ - public function testPrivateIPs() { - $private = array( 'fc00::3', 'fc00::ff', '::1', '10.0.0.1', '172.16.0.1', '192.168.0.1' ); - foreach ( $private as $p ) { - $this->assertFalse( IP::isPublic( $p ), "$p is not a public IP address" ); - } - $public = array( '2001:5c0:1000:a::133', 'fc::3', '00FC::' ); - foreach ( $public as $p ) { - $this->assertTrue( IP::isPublic( $p ), "$p is a public IP address" ); - } + public function testIsPublic( $expected, $input ) { + $result = IP::isPublic( $input ); + $this->assertEquals( $expected, $result ); + } + + /** + * Provider for IP::testIsPublic() + */ + public static function provideIsPublic() { + return array( + array( false, 'fc00::3' ), # RFC 4193 (local) + array( false, 'fc00::ff' ), # RFC 4193 (local) + array( false, '127.1.2.3' ), # loopback + array( false, '::1' ), # loopback + array( false, 'fe80::1' ), # link-local + array( false, '169.254.1.1' ), # link-local + array( false, '10.0.0.1' ), # RFC 1918 (private) + array( false, '172.16.0.1' ), # RFC 1918 (private) + array( false, '192.168.0.1' ), # RFC 1918 (private) + array( true, '2001:5c0:1000:a::133' ), # public + array( true, 'fc::3' ), # public + array( true, '00FC::' ) # public + ); } // Private wrapper used to test CIDR Parsing. @@ -336,40 +388,55 @@ class IPTest extends PHPUnit_Framework_TestCase { /** * @covers IP::hexToQuad + * @dataProvider provideIPsAndHexes */ - public function testHexToQuad() { - $this->assertEquals( '0.0.0.1', IP::hexToQuad( '00000001' ) ); - $this->assertEquals( '255.0.0.0', IP::hexToQuad( 'FF000000' ) ); - $this->assertEquals( '255.255.255.255', IP::hexToQuad( 'FFFFFFFF' ) ); - $this->assertEquals( '10.188.222.255', IP::hexToQuad( '0ABCDEFF' ) ); - // hex not left-padded... - $this->assertEquals( '0.0.0.0', IP::hexToQuad( '0' ) ); - $this->assertEquals( '0.0.0.1', IP::hexToQuad( '1' ) ); - $this->assertEquals( '0.0.0.255', IP::hexToQuad( 'FF' ) ); - $this->assertEquals( '0.0.255.0', IP::hexToQuad( 'FF00' ) ); + public function testHexToQuad( $ip, $hex ) { + $this->assertEquals( $ip, IP::hexToQuad( $hex ) ); + } + + /** + * Provide some IP addresses and their equivalent hex representations + */ + public function provideIPsandHexes() { + return array( + array( '0.0.0.1', '00000001' ), + array( '255.0.0.0', 'FF000000' ), + array( '255.255.255.255', 'FFFFFFFF' ), + array( '10.188.222.255', '0ABCDEFF' ), + // hex not left-padded... + array( '0.0.0.0', '0' ), + array( '0.0.0.1', '1' ), + array( '0.0.0.255', 'FF' ), + array( '0.0.255.0', 'FF00' ), + ); } /** * @covers IP::hexToOctet + * @dataProvider provideOctetsAndHexes */ - public function testHexToOctet() { - $this->assertEquals( '0:0:0:0:0:0:0:1', - IP::hexToOctet( '00000000000000000000000000000001' ) ); - $this->assertEquals( '0:0:0:0:0:0:FF:3', - IP::hexToOctet( '00000000000000000000000000FF0003' ) ); - $this->assertEquals( '0:0:0:0:0:0:FF00:6', - IP::hexToOctet( '000000000000000000000000FF000006' ) ); - $this->assertEquals( '0:0:0:0:0:0:FCCF:FAFF', - IP::hexToOctet( '000000000000000000000000FCCFFAFF' ) ); - $this->assertEquals( 'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', - IP::hexToOctet( 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ) ); - // hex not left-padded... - $this->assertEquals( '0:0:0:0:0:0:0:0', IP::hexToOctet( '0' ) ); - $this->assertEquals( '0:0:0:0:0:0:0:1', IP::hexToOctet( '1' ) ); - $this->assertEquals( '0:0:0:0:0:0:0:FF', IP::hexToOctet( 'FF' ) ); - $this->assertEquals( '0:0:0:0:0:0:0:FFD0', IP::hexToOctet( 'FFD0' ) ); - $this->assertEquals( '0:0:0:0:0:0:FA00:0', IP::hexToOctet( 'FA000000' ) ); - $this->assertEquals( '0:0:0:0:0:0:FCCF:FAFF', IP::hexToOctet( 'FCCFFAFF' ) ); + public function testHexToOctet( $octet, $hex ) { + $this->assertEquals( $octet, IP::hexToOctet( $hex ) ); + } + + /** + * Provide some hex and octet representations of the same IPs + */ + public function provideOctetsAndHexes() { + return array( + array( '0:0:0:0:0:0:0:1', '00000000000000000000000000000001' ), + array( '0:0:0:0:0:0:FF:3', '00000000000000000000000000FF0003' ), + array( '0:0:0:0:0:0:FF00:6', '000000000000000000000000FF000006' ), + array( '0:0:0:0:0:0:FCCF:FAFF', '000000000000000000000000FCCFFAFF' ), + array( 'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ), + // hex not left-padded... + array( '0:0:0:0:0:0:0:0', '0' ), + array( '0:0:0:0:0:0:0:1', '1' ), + array( '0:0:0:0:0:0:0:FF', 'FF' ), + array( '0:0:0:0:0:0:0:FFD0', 'FFD0' ), + array( '0:0:0:0:0:0:FA00:0', 'FA000000' ), + array( '0:0:0:0:0:0:FCCF:FAFF', 'FCCFFAFF' ), + ); } /** diff --git a/tests/phpunit/includes/utils/MWFunctionTest.php b/tests/phpunit/includes/utils/MWFunctionTest.php deleted file mode 100644 index f4d17999..00000000 --- a/tests/phpunit/includes/utils/MWFunctionTest.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php - -/** - * @covers MWFunction - */ -class MWFunctionTest extends MediaWikiTestCase { - public function testNewObjFunction() { - $arg1 = 'Foo'; - $arg2 = 'Bar'; - $arg3 = array( 'Baz' ); - $arg4 = new ExampleObject; - - $args = array( $arg1, $arg2, $arg3, $arg4 ); - - $newObject = new MWBlankClass( $arg1, $arg2, $arg3, $arg4 ); - $this->hideDeprecated( 'MWFunction::newObj' ); - $this->assertEquals( - MWFunction::newObj( 'MWBlankClass', $args )->args, - $newObject->args - ); - } -} - -class MWBlankClass { - - public $args = array(); - - function __construct( $arg1, $arg2, $arg3, $arg4 ) { - $this->args = array( $arg1, $arg2, $arg3, $arg4 ); - } -} - -class ExampleObject { -} diff --git a/tests/phpunit/includes/utils/UIDGeneratorTest.php b/tests/phpunit/includes/utils/UIDGeneratorTest.php index 0e11ccad..fedcc762 100644 --- a/tests/phpunit/includes/utils/UIDGeneratorTest.php +++ b/tests/phpunit/includes/utils/UIDGeneratorTest.php @@ -1,6 +1,6 @@ <?php -class UIDGeneratorTest extends MediaWikiTestCase { +class UIDGeneratorTest extends PHPUnit_Framework_TestCase { protected function tearDown() { // Bug: 44850 @@ -28,7 +28,7 @@ class UIDGeneratorTest extends MediaWikiTestCase { $lastId = array_shift( $ids ); - $this->assertArrayEquals( array_unique( $ids ), $ids, "All generated IDs are unique." ); + $this->assertSame( array_unique( $ids ), $ids, "All generated IDs are unique." ); foreach ( $ids as $id ) { $id_bin = wfBaseConvert( $id, 10, 2 ); @@ -105,8 +105,8 @@ class UIDGeneratorTest extends MediaWikiTestCase { $id1 = UIDGenerator::newSequentialPerNodeID( 'test', 32 ); $id2 = UIDGenerator::newSequentialPerNodeID( 'test', 32 ); - $this->assertType( 'float', $id1, "ID returned as float" ); - $this->assertType( 'float', $id2, "ID returned as float" ); + $this->assertInternalType( 'float', $id1, "ID returned as float" ); + $this->assertInternalType( 'float', $id2, "ID returned as float" ); $this->assertGreaterThan( 0, $id1, "ID greater than 1" ); $this->assertGreaterThan( $id1, $id2, "IDs increasing in value" ); } @@ -118,7 +118,7 @@ class UIDGeneratorTest extends MediaWikiTestCase { $ids = UIDGenerator::newSequentialPerNodeIDs( 'test', 32, 5 ); $lastId = null; foreach ( $ids as $id ) { - $this->assertType( 'float', $id, "ID returned as float" ); + $this->assertInternalType( 'float', $id, "ID returned as float" ); $this->assertGreaterThan( 0, $id, "ID greater than 1" ); if ( $lastId ) { $this->assertGreaterThan( $lastId, $id, "IDs increasing in value" ); diff --git a/tests/phpunit/languages/LanguageTest.php b/tests/phpunit/languages/LanguageTest.php index cff2e8fd..4fca0023 100644 --- a/tests/phpunit/languages/LanguageTest.php +++ b/tests/phpunit/languages/LanguageTest.php @@ -1030,7 +1030,7 @@ class LanguageTest extends LanguageClassesTestCase { return array( array( 0, - "0 B", + "0 bytes", "Zero bytes" ), array( @@ -1046,7 +1046,7 @@ class LanguageTest extends LanguageClassesTestCase { array( 1024 * 1024 * 1024, "1 GB", - "1 gigabytes" + "1 gigabyte" ), array( pow( 1024, 4 ), @@ -1413,6 +1413,77 @@ class LanguageTest extends LanguageClassesTestCase { } /** + * @dataProvider provideHebrewNumeralsData + * @covers Language::hebrewNumeral + */ + public function testHebrewNumeral( $num, $numerals ) { + $this->assertEquals( + $numerals, + Language::hebrewNumeral( $num ), + "hebrewNumeral('$num')" + ); + } + + public static function provideHebrewNumeralsData() { + return array( + array( -1, -1 ), + array( 0, 0 ), + array( 1, "א'" ), + array( 2, "ב'" ), + array( 3, "ג'" ), + array( 4, "ד'" ), + array( 5, "ה'" ), + array( 6, "ו'" ), + array( 7, "ז'" ), + array( 8, "ח'" ), + array( 9, "ט'" ), + array( 10, "י'" ), + array( 11, 'י"א' ), + array( 14, 'י"ד' ), + array( 15, 'ט"ו' ), + array( 16, 'ט"ז' ), + array( 17, 'י"ז' ), + array( 20, "כ'" ), + array( 21, 'כ"א' ), + array( 30, "ל'" ), + array( 40, "מ'" ), + array( 50, "נ'" ), + array( 60, "ס'" ), + array( 70, "ע'" ), + array( 80, "פ'" ), + array( 90, "צ'" ), + array( 99, 'צ"ט' ), + array( 100, "ק'" ), + array( 101, 'ק"א' ), + array( 110, 'ק"י' ), + array( 200, "ר'" ), + array( 300, "ש'" ), + array( 400, "ת'" ), + array( 500, 'ת"ק' ), + array( 800, 'ת"ת' ), + array( 1000, "א' אלף" ), + array( 1001, "א'א'" ), + array( 1012, "א'י\"ב" ), + array( 1020, "א'ך'" ), + array( 1030, "א'ל'" ), + array( 1081, "א'פ\"א" ), + array( 2000, "ב' אלפים" ), + array( 2016, "ב'ט\"ז" ), + array( 3000, "ג' אלפים" ), + array( 4000, "ד' אלפים" ), + array( 4904, "ד'תתק\"ד" ), + array( 5000, "ה' אלפים" ), + array( 5680, "ה'תר\"ף" ), + array( 5690, "ה'תר\"ץ" ), + array( 5708, "ה'תש\"ח" ), + array( 5720, "ה'תש\"ך" ), + array( 5740, "ה'תש\"ם" ), + array( 5750, "ה'תש\"ן" ), + array( 5775, "ה'תשע\"ה" ), + ); + } + + /** * @dataProvider providePluralData * @covers Language::convertPlural */ @@ -1458,6 +1529,31 @@ class LanguageTest extends LanguageClassesTestCase { } /** + * @covers Language::embedBidi() + */ + public function testEmbedBidi() { + $lre = "\xE2\x80\xAA"; // U+202A LEFT-TO-RIGHT EMBEDDING + $rle = "\xE2\x80\xAB"; // U+202B RIGHT-TO-LEFT EMBEDDING + $pdf = "\xE2\x80\xAC"; // U+202C POP DIRECTIONAL FORMATTING + $lang = $this->getLang(); + $this->assertEquals( + '123', + $lang->embedBidi( '123' ), + 'embedBidi with neutral argument' + ); + $this->assertEquals( + $lre . 'Ben_(WMF)' . $pdf, + $lang->embedBidi( 'Ben_(WMF)' ), + 'embedBidi with LTR argument' + ); + $this->assertEquals( + $rle . 'יהודי (מנוחין)' . $pdf, + $lang->embedBidi( 'יהודי (מנוחין)' ), + 'embedBidi with RTL argument' + ); + } + + /** * @covers Language::translateBlockExpiry() * @dataProvider provideTranslateBlockExpiry */ diff --git a/tests/phpunit/languages/classes/LanguageArqTest.php b/tests/phpunit/languages/classes/LanguageArqTest.php index 3fa56d78..71e05838 100644 --- a/tests/phpunit/languages/classes/LanguageArqTest.php +++ b/tests/phpunit/languages/classes/LanguageArqTest.php @@ -18,7 +18,7 @@ class LanguageArqTest extends LanguageClassesTestCase { public static function provideNumber() { return array( - array( '1.234.567', '1234567'), + array( '1.234.567', '1234567' ), array( '-12,89', -12.89 ), ); } diff --git a/tests/phpunit/maintenance/backupTextPassTest.php b/tests/phpunit/maintenance/backupTextPassTest.php index a5ef7624..c216864e 100644 --- a/tests/phpunit/maintenance/backupTextPassTest.php +++ b/tests/phpunit/maintenance/backupTextPassTest.php @@ -418,7 +418,10 @@ class TextPassDumperDatabaseTest extends DumpTestCase { } /** + * Broken per T70653. + * * @group large + * @group Broken */ function testCheckpointPlain() { $this->checkpointHelper(); @@ -434,7 +437,10 @@ class TextPassDumperDatabaseTest extends DumpTestCase { * PHP extensions, we go for gzip instead, which triggers the same relevant code * paths while still being testable on more systems. * + * Broken per T70653. + * * @group large + * @group Broken */ function testCheckpointGzip() { $this->checkHasGzip(); @@ -630,7 +636,7 @@ class TextPassDumperDatabaselessTest extends MediaWikiLangTestCase { */ function testBufferSizeSetting( $expected, $size, $msg ) { $dumper = new TextPassDumperAccessor( array( "--buffersize=" . $size ) ); - $this->assertEquals( $expected, $dumper->getBufferSize(), $msg); + $this->assertEquals( $expected, $dumper->getBufferSize(), $msg ); } /** diff --git a/tests/phpunit/mocks/MockWebRequest.php b/tests/phpunit/mocks/MockWebRequest.php new file mode 100644 index 00000000..3ac5bfb0 --- /dev/null +++ b/tests/phpunit/mocks/MockWebRequest.php @@ -0,0 +1,26 @@ +<?php + +/** + * A mock WebRequest. + * + * If the code under test accesses the response via the request (see + * WebRequest#response), then you might be able to use this mock to simplify + * your tests. + */ +class MockWebRequest extends WebRequest +{ + /** + * @var WebResponse + */ + protected $response; + + public function __construct( WebResponse $response ) { + parent::__construct(); + + $this->response = $response; + } + + public function response() { + return $this->response; + } +} diff --git a/tests/phpunit/mocks/content/DummyContentForTesting.php b/tests/phpunit/mocks/content/DummyContentForTesting.php new file mode 100644 index 00000000..0c69027d --- /dev/null +++ b/tests/phpunit/mocks/content/DummyContentForTesting.php @@ -0,0 +1,121 @@ +<?php + +class DummyContentForTesting extends AbstractContent { + + public function __construct( $data ) { + parent::__construct( "testing" ); + + $this->data = $data; + } + + public function serialize( $format = null ) { + return serialize( $this->data ); + } + + /** + * @return string A string representing the content in a way useful for + * building a full text search index. If no useful representation exists, + * this method returns an empty string. + */ + public function getTextForSearchIndex() { + return ''; + } + + /** + * @return string|bool The wikitext to include when another page includes this content, + * or false if the content is not includable in a wikitext page. + */ + public function getWikitextForTransclusion() { + return false; + } + + /** + * Returns a textual representation of the content suitable for use in edit + * summaries and log messages. + * + * @param int $maxlength Maximum length of the summary text. + * @return string The summary text. + */ + public function getTextForSummary( $maxlength = 250 ) { + return ''; + } + + /** + * Returns native represenation of the data. Interpretation depends on the data model used, + * as given by getDataModel(). + * + * @return mixed The native representation of the content. Could be a string, a nested array + * structure, an object, a binary blob... anything, really. + */ + public function getNativeData() { + return $this->data; + } + + /** + * returns the content's nominal size in bogo-bytes. + * + * @return int + */ + public function getSize() { + return strlen( $this->data ); + } + + /** + * Return a copy of this Content object. The following must be true for the object returned + * if $copy = $original->copy() + * + * * get_class($original) === get_class($copy) + * * $original->getModel() === $copy->getModel() + * * $original->equals( $copy ) + * + * If and only if the Content object is imutable, the copy() method can and should + * return $this. That is, $copy === $original may be true, but only for imutable content + * objects. + * + * @return Content A copy of this object + */ + public function copy() { + return $this; + } + + /** + * Returns true if this content is countable as a "real" wiki page, provided + * that it's also in a countable location (e.g. a current revision in the main namespace). + * + * @param bool $hasLinks If it is known whether this content contains links, + * provide this information here, to avoid redundant parsing to find out. + * @return bool + */ + public function isCountable( $hasLinks = null ) { + return false; + } + + /** + * @param Title $title + * @param int $revId Unused. + * @param null|ParserOptions $options + * @param bool $generateHtml Whether to generate Html (default: true). If false, the result + * of calling getText() on the ParserOutput object returned by this method is undefined. + * + * @return ParserOutput + */ + public function getParserOutput( Title $title, $revId = null, + ParserOptions $options = null, $generateHtml = true + ) { + return new ParserOutput( $this->getNativeData() ); + } + + /** + * @see AbstractContent::fillParserOutput() + * + * @param Title $title Context title for parsing + * @param int|null $revId Revision ID (for {{REVISIONID}}) + * @param ParserOptions $options Parser options + * @param bool $generateHtml Whether or not to generate HTML + * @param ParserOutput &$output The output object to fill (reference). + */ + protected function fillParserOutput( Title $title, $revId, + ParserOptions $options, $generateHtml, ParserOutput &$output ) { + $output = new ParserOutput( $this->getNativeData() ); + } +} diff --git a/tests/phpunit/mocks/content/DummyContentHandlerForTesting.php b/tests/phpunit/mocks/content/DummyContentHandlerForTesting.php new file mode 100644 index 00000000..fd253f21 --- /dev/null +++ b/tests/phpunit/mocks/content/DummyContentHandlerForTesting.php @@ -0,0 +1,42 @@ +<?php + +class DummyContentHandlerForTesting extends ContentHandler { + + public function __construct( $dataModel ) { + parent::__construct( $dataModel, array( "testing" ) ); + } + + /** + * @see ContentHandler::serializeContent + * + * @param Content $content + * @param string $format + * + * @return string + */ + public function serializeContent( Content $content, $format = null ) { + return $content->serialize(); + } + + /** + * @see ContentHandler::unserializeContent + * + * @param string $blob + * @param string $format Unused. + * + * @return Content + */ + public function unserializeContent( $blob, $format = null ) { + $d = unserialize( $blob ); + + return new DummyContentForTesting( $d ); + } + + /** + * Creates an empty Content object of the type supported by this ContentHandler. + * + */ + public function makeEmptyContent() { + return new DummyContentForTesting( '' ); + } +} diff --git a/tests/phpunit/mocks/content/DummyNonTextContent.php b/tests/phpunit/mocks/content/DummyNonTextContent.php new file mode 100644 index 00000000..889efb71 --- /dev/null +++ b/tests/phpunit/mocks/content/DummyNonTextContent.php @@ -0,0 +1,121 @@ +<?php + +class DummyNonTextContent extends AbstractContent { + + public function __construct( $data ) { + parent::__construct( "testing-nontext" ); + + $this->data = $data; + } + + public function serialize( $format = null ) { + return serialize( $this->data ); + } + + /** + * @return string A string representing the content in a way useful for + * building a full text search index. If no useful representation exists, + * this method returns an empty string. + */ + public function getTextForSearchIndex() { + return ''; + } + + /** + * @return string|bool The wikitext to include when another page includes this content, + * or false if the content is not includable in a wikitext page. + */ + public function getWikitextForTransclusion() { + return false; + } + + /** + * Returns a textual representation of the content suitable for use in edit + * summaries and log messages. + * + * @param int $maxlength Maximum length of the summary text. + * @return string The summary text. + */ + public function getTextForSummary( $maxlength = 250 ) { + return ''; + } + + /** + * Returns native represenation of the data. Interpretation depends on the data model used, + * as given by getDataModel(). + * + * @return mixed The native representation of the content. Could be a string, a nested array + * structure, an object, a binary blob... anything, really. + */ + public function getNativeData() { + return $this->data; + } + + /** + * returns the content's nominal size in bogo-bytes. + * + * @return int + */ + public function getSize() { + return strlen( $this->data ); + } + + /** + * Return a copy of this Content object. The following must be true for the object returned + * if $copy = $original->copy() + * + * * get_class($original) === get_class($copy) + * * $original->getModel() === $copy->getModel() + * * $original->equals( $copy ) + * + * If and only if the Content object is imutable, the copy() method can and should + * return $this. That is, $copy === $original may be true, but only for imutable content + * objects. + * + * @return Content A copy of this object + */ + public function copy() { + return $this; + } + + /** + * Returns true if this content is countable as a "real" wiki page, provided + * that it's also in a countable location (e.g. a current revision in the main namespace). + * + * @param bool $hasLinks If it is known whether this content contains links, + * provide this information here, to avoid redundant parsing to find out. + * @return bool + */ + public function isCountable( $hasLinks = null ) { + return false; + } + + /** + * @param Title $title + * @param int $revId Unused. + * @param null|ParserOptions $options + * @param bool $generateHtml Whether to generate Html (default: true). If false, the result + * of calling getText() on the ParserOutput object returned by this method is undefined. + * + * @return ParserOutput + */ + public function getParserOutput( Title $title, $revId = null, + ParserOptions $options = null, $generateHtml = true + ) { + return new ParserOutput( $this->getNativeData() ); + } + + /** + * @see AbstractContent::fillParserOutput() + * + * @param Title $title Context title for parsing + * @param int|null $revId Revision ID (for {{REVISIONID}}) + * @param ParserOptions $options Parser options + * @param bool $generateHtml Whether or not to generate HTML + * @param ParserOutput &$output The output object to fill (reference). + */ + protected function fillParserOutput( Title $title, $revId, + ParserOptions $options, $generateHtml, ParserOutput &$output ) { + $output = new ParserOutput( $this->getNativeData() ); + } +} diff --git a/tests/phpunit/mocks/content/DummyNonTextContentHandler.php b/tests/phpunit/mocks/content/DummyNonTextContentHandler.php new file mode 100644 index 00000000..6995ae78 --- /dev/null +++ b/tests/phpunit/mocks/content/DummyNonTextContentHandler.php @@ -0,0 +1,46 @@ +<?php + +class DummyNonTextContentHandler extends DummyContentHandlerForTesting { + + public function __construct( $dataModel ) { + parent::__construct( $dataModel, array( "testing-nontext" ) ); + } + + /** + * @see ContentHandler::serializeContent + * + * @param Content $content + * @param string $format + * + * @return string + */ + public function serializeContent( Content $content, $format = null ) { + return $content->serialize(); + } + + /** + * @see ContentHandler::unserializeContent + * + * @param string $blob + * @param string $format Unused. + * + * @return Content + */ + public function unserializeContent( $blob, $format = null ) { + $d = unserialize( $blob ); + + return new DummyNonTextContent( $d ); + } + + /** + * Creates an empty Content object of the type supported by this ContentHandler. + */ + public function makeEmptyContent() { + return new DummyNonTextContent( '' ); + } + + public function supportsDirectApiEditing() { + return true; + } + +} diff --git a/tests/phpunit/phpunit.php b/tests/phpunit/phpunit.php index e59b5063..587d6d0c 100644 --- a/tests/phpunit/phpunit.php +++ b/tests/phpunit/phpunit.php @@ -55,7 +55,7 @@ class PHPUnitMaintClass extends Maintenance { public function finalSetup() { parent::finalSetup(); - global $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType; + global $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType, $wgMainWANCache; global $wgLanguageConverterCacheType, $wgUseDatabaseMessages; global $wgLocaltimezone, $wgLocalisationCacheConf; global $wgDevelopmentWarnings; @@ -67,6 +67,7 @@ class PHPUnitMaintClass extends Maintenance { $wgDevelopmentWarnings = true; $wgMainCacheType = CACHE_NONE; + $wgMainWANCache = CACHE_NONE; $wgMessageCacheType = CACHE_NONE; $wgParserCacheType = CACHE_NONE; $wgLanguageConverterCacheType = CACHE_NONE; @@ -215,25 +216,35 @@ if ( version_compare( PHP_VERSION, '5.4.0', '<' ) ) { $ok = false; -foreach ( array( - stream_resolve_include_path( 'phpunit.phar' ), - 'PHPUnit/Runner/Version.php', - 'PHPUnit/Autoload.php' -) as $includePath ) { - @include_once $includePath; - if ( class_exists( 'PHPUnit_TextUI_Command' ) ) { - $ok = true; - break; +if ( class_exists( 'PHPUnit_TextUI_Command' ) ) { + echo "PHPUnit already present\n"; + $ok = true; +} else { + foreach ( array( + stream_resolve_include_path( 'phpunit.phar' ), + 'PHPUnit/Runner/Version.php', + 'PHPUnit/Autoload.php' + ) as $includePath ) { + // @codingStandardsIgnoreStart + @include_once $includePath; + // @codingStandardsIgnoreEnd + if ( class_exists( 'PHPUnit_TextUI_Command' ) ) { + $ok = true; + echo "Using PHPUnit from $includePath\n"; + break; + } } } if ( !$ok ) { - die( "Couldn't find a usable PHPUnit.\n" ); + echo "Couldn't find a usable PHPUnit.\n"; + exit( 1 ); } $puVersion = PHPUnit_Runner_Version::id(); if ( $puVersion !== '@package_version@' && version_compare( $puVersion, '3.7.0', '<' ) ) { - die( "PHPUnit 3.7.0 or later required; you have {$puVersion}.\n" ); + echo "PHPUnit 3.7.0 or later required; you have {$puVersion}.\n"; + exit( 1 ); } PHPUnit_TextUI_Command::main(); diff --git a/tests/phpunit/structure/AutoLoaderTest.php b/tests/phpunit/structure/AutoLoaderTest.php index cde6547a..8674329a 100644 --- a/tests/phpunit/structure/AutoLoaderTest.php +++ b/tests/phpunit/structure/AutoLoaderTest.php @@ -58,9 +58,9 @@ class AutoLoaderTest extends MediaWikiTestCase { continue; } - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $contents = file_get_contents( $filePath ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( $contents === false ) { $actual[$class] = "[couldn't read file '$filePath']"; diff --git a/tests/phpunit/structure/AvailableRightsTest.php b/tests/phpunit/structure/AvailableRightsTest.php index 51d31aaa..ccf5393b 100644 --- a/tests/phpunit/structure/AvailableRightsTest.php +++ b/tests/phpunit/structure/AvailableRightsTest.php @@ -19,11 +19,11 @@ class AvailableRightsTest extends PHPUnit_Framework_TestCase { $rights = User::getAllRights(); - foreach( $wgGroupPermissions as $permissions ) { + foreach ( $wgGroupPermissions as $permissions ) { $rights = array_merge( $rights, array_keys( $permissions ) ); } - foreach( $wgRevokePermissions as $permissions ) { + foreach ( $wgRevokePermissions as $permissions ) { $rights = array_merge( $rights, array_keys( $permissions ) ); } diff --git a/tests/phpunit/structure/ResourcesTest.php b/tests/phpunit/structure/ResourcesTest.php index d2b699d9..eefc926a 100644 --- a/tests/phpunit/structure/ResourcesTest.php +++ b/tests/phpunit/structure/ResourcesTest.php @@ -36,9 +36,20 @@ class ResourcesTest extends MediaWikiTestCase { ); } + public function testVersionHash() { + $data = self::getAllModules(); + foreach ( $data['modules'] as $moduleName => $module ) { + $version = $module->getVersionHash( $data['context'] ); + $this->assertEquals( 8, strlen( $version ), "$moduleName must use ResourceLoader::makeHash" ); + } + } + /** * Verify that nothing explicitly depends on the 'jquery' and 'mediawiki' modules. * They are always loaded, depending on them is unsupported and leads to unexpected behaviour. + * TODO Modules can dynamically choose dependencies based on context. This method does not + * test such dependencies. The same goes for testMissingDependencies() and + * testUnsatisfiableDependencies(). */ public function testIllegalDependencies() { $data = self::getAllModules(); @@ -49,7 +60,7 @@ class ResourcesTest extends MediaWikiTestCase { foreach ( $illegalDeps as $illegalDep ) { $this->assertNotContains( $illegalDep, - $module->getDependencies(), + $module->getDependencies( $data['context'] ), "Module '$moduleName' must not depend on '$illegalDep'" ); } @@ -65,7 +76,7 @@ class ResourcesTest extends MediaWikiTestCase { /** @var ResourceLoaderModule $module */ foreach ( $data['modules'] as $moduleName => $module ) { - foreach ( $module->getDependencies() as $dep ) { + foreach ( $module->getDependencies( $data['context'] ) as $dep ) { $this->assertContains( $dep, $validDeps, @@ -89,7 +100,7 @@ class ResourcesTest extends MediaWikiTestCase { /** @var ResourceLoaderModule $module */ foreach ( $data['modules'] as $moduleName => $module ) { $moduleTargets = $module->getTargets(); - foreach ( $module->getDependencies() as $dep ) { + foreach ( $module->getDependencies( $data['context'] ) as $dep ) { if ( !isset( $data['modules'][$dep] ) ) { // Missing dependencies reported by testMissingDependencies continue; @@ -108,6 +119,22 @@ class ResourcesTest extends MediaWikiTestCase { } /** + * CSSMin::getAllLocalFileReferences should ignore url(...) expressions + * that have been commented out. + */ + public function testCommentedLocalFileReferences() { + $basepath = __DIR__ . '/../data/css/'; + $css = file_get_contents( $basepath . 'comments.css' ); + $files = CSSMin::getAllLocalFileReferences( $css, $basepath ); + $expected = array( $basepath . 'not-commented.gif' ); + $this->assertArrayEquals( + $expected, + $files, + 'Url(...) expression in comment should be omitted.' + ); + } + + /** * Get all registered modules from ResouceLoader. * @return array */ @@ -265,6 +292,25 @@ class ResourcesTest extends MediaWikiTestCase { ( $file instanceof ResourceLoaderFilePath ? $file->getPath() : $file ), ); } + + // To populate missingLocalFileRefs. Not sure how sane this is inside this test... + $module->readStyleFiles( + $module->getStyleFiles( $data['context'] ), + $module->getFlip( $data['context'] ), + $data['context'] + ); + + $property = $reflectedModule->getProperty( 'missingLocalFileRefs' ); + $property->setAccessible( true ); + $missingLocalFileRefs = $property->getValue( $module ); + + foreach ( $missingLocalFileRefs as $file ) { + $cases[] = array( + $file, + $moduleName, + $file, + ); + } } return $cases; diff --git a/tests/phpunit/suites/UploadFromUrlTestSuite.php b/tests/phpunit/suites/UploadFromUrlTestSuite.php index d4a7bd36..e8672501 100644 --- a/tests/phpunit/suites/UploadFromUrlTestSuite.php +++ b/tests/phpunit/suites/UploadFromUrlTestSuite.php @@ -18,7 +18,7 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { protected function setUp() { global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, - $wgEnableParserCache, $wgNamespaceAliases, $wgNamespaceProtection, + $wgParserCacheType, $wgNamespaceAliases, $wgNamespaceProtection, $parserMemc; $tmpGlobals = array(); @@ -56,7 +56,7 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { $wgNamespaceAliases['Image'] = NS_FILE; $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK; - $wgEnableParserCache = false; + $wgParserCacheType = CACHE_NONE; DeferredUpdates::clearPendingUpdates(); $wgMemc = wfGetMainCache(); $messageMemc = wfGetMessageCacheStorage(); |