diff options
Diffstat (limited to 'tests/phpunit/includes/libs')
17 files changed, 1156 insertions, 25 deletions
diff --git a/tests/phpunit/includes/libs/ArrayUtilsTest.php b/tests/phpunit/includes/libs/ArrayUtilsTest.php new file mode 100644 index 00000000..b5ea7b72 --- /dev/null +++ b/tests/phpunit/includes/libs/ArrayUtilsTest.php @@ -0,0 +1,311 @@ +<?php +/** + * Test class for ArrayUtils class + * + * @group Database + */ + +class ArrayUtilsTest extends PHPUnit_Framework_TestCase { + private $search; + + /** + * @covers ArrayUtils::findLowerBound + * @dataProvider provideFindLowerBound + */ + function testFindLowerBound( + $valueCallback, $valueCount, $comparisonCallback, $target, $expected + ) { + $this->assertSame( + ArrayUtils::findLowerBound( + $valueCallback, $valueCount, $comparisonCallback, $target + ), $expected + ); + } + + function provideFindLowerBound() { + $self = $this; + $indexValueCallback = function ( $size ) use ( $self ) { + return function ( $val ) use ( $self, $size ) { + $self->assertTrue( $val >= 0 ); + $self->assertTrue( $val < $size ); + return $val; + }; + }; + $comparisonCallback = function ( $a, $b ) { + return $a - $b; + }; + + return array( + array( + $indexValueCallback( 0 ), + 0, + $comparisonCallback, + 1, + false, + ), + array( + $indexValueCallback( 1 ), + 1, + $comparisonCallback, + -1, + false, + ), + array( + $indexValueCallback( 1 ), + 1, + $comparisonCallback, + 0, + 0, + ), + array( + $indexValueCallback( 1 ), + 1, + $comparisonCallback, + 1, + 0, + ), + array( + $indexValueCallback( 2 ), + 2, + $comparisonCallback, + -1, + false, + ), + array( + $indexValueCallback( 2 ), + 2, + $comparisonCallback, + 0, + 0, + ), + array( + $indexValueCallback( 2 ), + 2, + $comparisonCallback, + 0.5, + 0, + ), + array( + $indexValueCallback( 2 ), + 2, + $comparisonCallback, + 1, + 1, + ), + array( + $indexValueCallback( 2 ), + 2, + $comparisonCallback, + 1.5, + 1, + ), + array( + $indexValueCallback( 3 ), + 3, + $comparisonCallback, + 1, + 1, + ), + array( + $indexValueCallback( 3 ), + 3, + $comparisonCallback, + 1.5, + 1, + ), + array( + $indexValueCallback( 3 ), + 3, + $comparisonCallback, + 2, + 2, + ), + array( + $indexValueCallback( 3 ), + 3, + $comparisonCallback, + 3, + 2, + ), + ); + } + + /** + * @covers ArrayUtils::arrayDiffAssocRecursive + * @dataProvider provideArrayDiffAssocRecursive + */ + function testArrayDiffAssocRecursive( $expected ) { + $args = func_get_args(); + array_shift( $args ); + $this->assertEquals( call_user_func_array( + 'ArrayUtils::arrayDiffAssocRecursive', $args + ), $expected ); + } + + function provideArrayDiffAssocRecursive() { + return array( + array( + array(), + array(), + array(), + ), + array( + array(), + array(), + array(), + array(), + ), + array( + array( 1 ), + array( 1 ), + array(), + ), + array( + array( 1 ), + array( 1 ), + array(), + array(), + ), + array( + array(), + array(), + array( 1 ), + ), + array( + array(), + array(), + array( 1 ), + array( 2 ), + ), + array( + array( '' => 1 ), + array( '' => 1 ), + array(), + ), + array( + array(), + array(), + array( '' => 1 ), + ), + array( + array( 1 ), + array( 1 ), + array( 2 ), + ), + array( + array(), + array( 1 ), + array( 2 ), + array( 1 ), + ), + array( + array(), + array( 1 ), + array( 1, 2 ), + ), + array( + array( 1 => 1 ), + array( 1 => 1 ), + array( 1 ), + ), + array( + array(), + array( 1 => 1 ), + array( 1 ), + array( 1 => 1), + ), + array( + array(), + array( 1 => 1 ), + array( 1, 1, 1 ), + ), + array( + array(), + array( array() ), + array(), + ), + array( + array(), + array( array( array() ) ), + array(), + ), + array( + array( 1, array( 1 ) ), + array( 1, array( 1 ) ), + array(), + ), + array( + array( 1 ), + array( 1, array( 1 ) ), + array( 2, array( 1 ) ), + ), + array( + array(), + array( 1, array( 1 ) ), + array( 2, array( 1 ) ), + array( 1, array( 2 ) ), + ), + array( + array( 1 ), + array( 1, array() ), + array( 2 ), + ), + array( + array(), + array( 1, array() ), + array( 2 ), + array( 1 ), + ), + array( + array( 1, array( 1 => 2 ) ), + array( 1, array( 1, 2 ) ), + array( 2, array( 1 ) ), + ), + array( + array( 1 ), + array( 1, array( 1, 2 ) ), + array( 2, array( 1 ) ), + array( 2, array( 1 => 2 ) ), + ), + array( + array( 1 => array( 1, 2 ) ), + array( 1, array( 1, 2 ) ), + array( 1, array( 2 ) ), + ), + array( + array( 1 => array( array( 2, 3 ), 2 ) ), + array( 1, array( array( 2, 3 ), 2 ) ), + array( 1, array( 2 ) ), + ), + array( + array( 1 => array( array( 2 ), 2 ) ), + array( 1, array( array( 2, 3 ), 2 ) ), + array( 1, array( array( 1 => 3 ) ) ), + ), + array( + array( 1 => array( 1 => 2 ) ), + array( 1, array( array( 2, 3 ), 2 ) ), + array( 1, array( array( 1 => 3, 0 => 2 ) ) ), + ), + array( + array( 1 => array( 1 => 2 ) ), + array( 1, array( array( 2, 3 ), 2 ) ), + array( 1, array( array( 1 => 3 ) ) ), + array( 1 => array( array( 2 ) ) ), + ), + array( + array(), + array( 1, array( array( 2, 3 ), 2 ) ), + array( 1 => array( 1 => 2, 0 => array( 1 => 3, 0 => 2 ) ), 0 => 1 ), + ), + array( + array(), + array( 1, array( array( 2, 3 ), 2 ) ), + array( 1 => array( 1 => 2 ) ), + array( 1 => array( array( 1 => 3 ) ) ), + array( 1 => array( array( 2 ) ) ), + array( 1 ), + ), + ); + } +} diff --git a/tests/phpunit/includes/libs/CSSMinTest.php b/tests/phpunit/includes/libs/CSSMinTest.php index 43c50869..6142f967 100644 --- a/tests/phpunit/includes/libs/CSSMinTest.php +++ b/tests/phpunit/includes/libs/CSSMinTest.php @@ -141,15 +141,57 @@ class CSSMinTest extends MediaWikiTestCase { ); } + public static function provideIsRemoteUrl() { + return array( + array( true, 'http://localhost/w/red.gif?123' ), + array( true, 'https://example.org/x.png' ), + array( true, '//example.org/x.y.z/image.png' ), + array( true, '//localhost/styles.css?query=yes' ), + array( true, 'data:image/gif;base64,R0lGODlhAQABAIAAAP8AADAAACwAAAAAAQABAAACAkQBADs=' ), + array( false, 'x.gif' ), + array( false, '/x.gif' ), + array( false, './x.gif' ), + array( false, '../x.gif' ), + ); + } + + /** + * @dataProvider provideIsRemoteUrl + * @cover CSSMin::isRemoteUrl + */ + public function testIsRemoteUrl( $expect, $url ) { + $this->assertEquals( CSSMin::isRemoteUrl( $url ), $expect ); + } + + public static function provideIsLocalUrls() { + return array( + array( false, 'x.gif' ), + array( true, '/x.gif' ), + array( false, './x.gif' ), + array( false, '../x.gif' ), + ); + } + + /** + * @dataProvider provideIsLocalUrls + * @cover CSSMin::isLocalUrl + */ + public function testIsLocalUrl( $expect, $url ) { + $this->assertEquals( CSSMin::isLocalUrl( $url ), $expect ); + } + public static function provideRemapRemappingCases() { // red.gif and green.gif are one-pixel 35-byte GIFs. // large.png is a 35K PNG that should be non-embeddable. // Full paths start with http://localhost/w/. // Timestamps in output are replaced with 'timestamp'. - // data: URIs for red.gif and green.gif + // data: URIs for red.gif, green.gif, circle.svg $red = 'data:image/gif;base64,R0lGODlhAQABAIAAAP8AADAAACwAAAAAAQABAAACAkQBADs='; $green = 'data:image/gif;base64,R0lGODlhAQABAIAAAACAADAAACwAAAAAAQABAAACAkQBADs='; + $svg = 'data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%0A' + . '%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%228%22%20height%3D' + . '%228%22%3E%0A%3Ccircle%20cx%3D%224%22%20cy%3D%224%22%20r%3D%222%22%2F%3E%0A%3C%2Fsvg%3E%0A'; return array( array( @@ -234,6 +276,11 @@ class CSSMinTest extends MediaWikiTestCase { "foo { background: url(http://localhost/w/large.png?timestamp); }", ), array( + 'SVG files are embedded without base64 encoding and unnecessary IE 6 and 7 fallback', + 'foo { /* @embed */ background: url(circle.svg); }', + "foo { background: url($svg); }", + ), + array( 'Two regular files in one rule', 'foo { background: url(red.gif), url(green.gif); }', 'foo { background: url(http://localhost/w/red.gif?timestamp), ' diff --git a/tests/phpunit/includes/libs/DeferredStringifierTest.php b/tests/phpunit/includes/libs/DeferredStringifierTest.php new file mode 100644 index 00000000..8b7610ae --- /dev/null +++ b/tests/phpunit/includes/libs/DeferredStringifierTest.php @@ -0,0 +1,50 @@ +<?php + +class DeferredStringifierTest extends PHPUnit_Framework_TestCase { + + /** + * @covers DeferredStringifier + * @dataProvider provideToString + */ + public function testToString( $params, $expected ) { + $class = new ReflectionClass( 'DeferredStringifier' ); + $ds = $class->newInstanceArgs( $params ); + $this->assertEquals( $expected, (string)$ds ); + } + + public static function provideToString() { + return array( + // No args + array( + array( + function() { + return 'foo'; + } + ), + 'foo' + ), + // Has args + array( + array( + function( $i ) { + return $i; + }, + 'bar' + ), + 'bar' + ), + ); + } + + /** + * Verify that the callback is not called if + * it is never converted to a string + */ + public function testCallbackNotCalled() { + $ds = new DeferredStringifier( function() { + throw new Exception( 'This should not be reached!' ); + } ); + // No exception was thrown + $this->assertTrue( true ); + } +} diff --git a/tests/phpunit/includes/libs/GenericArrayObjectTest.php b/tests/phpunit/includes/libs/GenericArrayObjectTest.php index 4911f73a..315bc7ed 100644 --- a/tests/phpunit/includes/libs/GenericArrayObjectTest.php +++ b/tests/phpunit/includes/libs/GenericArrayObjectTest.php @@ -24,10 +24,9 @@ * @ingroup Test * @group GenericArrayObject * - * @licence GNU GPL v2+ * @author Jeroen De Dauw < jeroendedauw@gmail.com > */ -abstract class GenericArrayObjectTest extends MediaWikiTestCase { +abstract class GenericArrayObjectTest extends PHPUnit_Framework_TestCase { /** * Returns objects that can serve as elements in the concrete diff --git a/tests/phpunit/includes/libs/HashRingTest.php b/tests/phpunit/includes/libs/HashRingTest.php index 68dfea1f..b51eb3f4 100644 --- a/tests/phpunit/includes/libs/HashRingTest.php +++ b/tests/phpunit/includes/libs/HashRingTest.php @@ -3,7 +3,7 @@ /** * @group HashRing */ -class HashRingTest extends MediaWikiTestCase { +class HashRingTest extends PHPUnit_Framework_TestCase { /** * @covers HashRing */ diff --git a/tests/phpunit/includes/libs/IEUrlExtensionTest.php b/tests/phpunit/includes/libs/IEUrlExtensionTest.php index b7071230..e96953ee 100644 --- a/tests/phpunit/includes/libs/IEUrlExtensionTest.php +++ b/tests/phpunit/includes/libs/IEUrlExtensionTest.php @@ -5,7 +5,7 @@ * @todo tests below for findIE6Extension should be split into... * ...a dataprovider and test method. */ -class IEUrlExtensionTest extends MediaWikiTestCase { +class IEUrlExtensionTest extends PHPUnit_Framework_TestCase { /** * @covers IEUrlExtension::findIE6Extension */ diff --git a/tests/phpunit/includes/libs/IPSetTest.php b/tests/phpunit/includes/libs/IPSetTest.php index d4e5214a..5bbacef4 100644 --- a/tests/phpunit/includes/libs/IPSetTest.php +++ b/tests/phpunit/includes/libs/IPSetTest.php @@ -3,7 +3,7 @@ /** * @group IPSet */ -class IPSetTest extends MediaWikiTestCase { +class IPSetTest extends PHPUnit_Framework_TestCase { /** * Provides test cases for IPSetTest::testIPSet * diff --git a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php index c8795b2e..149a28c1 100644 --- a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php +++ b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php @@ -1,6 +1,6 @@ <?php -class JavaScriptMinifierTest extends MediaWikiTestCase { +class JavaScriptMinifierTest extends PHPUnit_Framework_TestCase { public static function provideCases() { return array( @@ -164,7 +164,7 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { ); } - public static function provideBug32548() { + public static function provideExponentLineBreaking() { return array( array( // This one gets interpreted all together by the prior code; @@ -183,14 +183,13 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { } /** - * @dataProvider provideBug32548 + * @dataProvider provideExponentLineBreaking * @covers JavaScriptMinifier::minify - * @todo give this test a real name explaining what is being tested here */ - public function testBug32548Exponent( $num ) { + public function testExponentLineBreaking( $num ) { // Long line breaking was being incorrectly done between the base and // exponent part of a number, causing a syntax error. The line should - // instead break at the start of the number. + // instead break at the start of the number. (T34548) $prefix = 'var longVarName' . str_repeat( '_', 973 ) . '='; $suffix = ',shortVarName=0;'; diff --git a/tests/phpunit/includes/libs/MWMessagePackTest.php b/tests/phpunit/includes/libs/MWMessagePackTest.php index f80f78df..ec145836 100644 --- a/tests/phpunit/includes/libs/MWMessagePackTest.php +++ b/tests/phpunit/includes/libs/MWMessagePackTest.php @@ -3,7 +3,7 @@ * PHP Unit tests for MWMessagePack * @covers MWMessagePack */ -class MWMessagePackTest extends MediaWikiTestCase { +class MWMessagePackTest extends PHPUnit_Framework_TestCase { /** * Provides test cases for MWMessagePackTest::testMessagePack diff --git a/tests/phpunit/includes/libs/ObjectFactoryTest.php b/tests/phpunit/includes/libs/ObjectFactoryTest.php new file mode 100644 index 00000000..92207325 --- /dev/null +++ b/tests/phpunit/includes/libs/ObjectFactoryTest.php @@ -0,0 +1,60 @@ +<?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 + */ + +class ObjectFactoryTest extends PHPUnit_Framework_TestCase { + + /** + * @covers ObjectFactory::getObjectFromSpec + */ + public function testClosureExpansionDisabled() { + $obj = ObjectFactory::getObjectFromSpec( array( + 'class' => 'ObjectFactoryTest_Fixture', + 'args' => array( function (){ return 'unwrapped'; }, ), + 'closure_expansion' => false, + ) ); + $this->assertInstanceOf( 'Closure', $obj->args[0] ); + $this->assertSame( 'unwrapped', $obj->args[0]() ); + } + + /** + * @covers ObjectFactory::getObjectFromSpec + */ + public function testClosureExpansionEnabled() { + $obj = ObjectFactory::getObjectFromSpec( array( + 'class' => 'ObjectFactoryTest_Fixture', + 'args' => array( function (){ return 'unwrapped'; }, ), + 'closure_expansion' => true, + ) ); + $this->assertInternalType( 'string', $obj->args[0] ); + $this->assertSame( 'unwrapped', $obj->args[0] ); + + $obj = ObjectFactory::getObjectFromSpec( array( + 'class' => 'ObjectFactoryTest_Fixture', + 'args' => array( function (){ return 'unwrapped'; }, ), + ) ); + $this->assertInternalType( 'string', $obj->args[0] ); + $this->assertSame( 'unwrapped', $obj->args[0] ); + } +} + +class ObjectFactoryTest_Fixture { + public $args; + public function __construct( /*...*/ ) { $this->args = func_get_args(); } +} diff --git a/tests/phpunit/includes/libs/ProcessCacheLRUTest.php b/tests/phpunit/includes/libs/ProcessCacheLRUTest.php index 1a8a1e56..43001979 100644 --- a/tests/phpunit/includes/libs/ProcessCacheLRUTest.php +++ b/tests/phpunit/includes/libs/ProcessCacheLRUTest.php @@ -9,20 +9,20 @@ * * @group Cache */ -class ProcessCacheLRUTest extends MediaWikiTestCase { +class ProcessCacheLRUTest extends PHPUnit_Framework_TestCase { /** * Helper to verify emptiness of a cache object. * Compare against an array so we get the cache content difference. */ - function assertCacheEmpty( $cache, $msg = 'Cache should be empty' ) { + protected function assertCacheEmpty( $cache, $msg = 'Cache should be empty' ) { $this->assertAttributeEquals( array(), 'cache', $cache, $msg ); } /** * Helper to fill a cache object passed by reference */ - function fillCache( &$cache, $numEntries ) { + protected function fillCache( &$cache, $numEntries ) { // Fill cache with three values for ( $i = 1; $i <= $numEntries; $i++ ) { $cache->set( "cache-key-$i", "prop-$i", "value-$i" ); @@ -33,17 +33,17 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { * Generates an array of what would be expected in cache for a given cache * size and a number of entries filled in sequentially */ - function getExpectedCache( $cacheMaxEntries, $entryToFill ) { + protected function getExpectedCache( $cacheMaxEntries, $entryToFill ) { $expected = array(); if ( $entryToFill === 0 ) { - # The cache is empty! + // The cache is empty! return array(); } elseif ( $entryToFill <= $cacheMaxEntries ) { - # Cache is not fully filled + // Cache is not fully filled $firstKey = 1; } else { - # Cache overflowed + // Cache overflowed $firstKey = 1 + $entryToFill - $cacheMaxEntries; } @@ -62,13 +62,16 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { public function testPhpUnitArrayEquality() { $one = array( 'A' => 1, 'B' => 2 ); $two = array( 'B' => 2, 'A' => 1 ); - $this->assertEquals( $one, $two ); // == - $this->assertNotSame( $one, $two ); // === + // == + $this->assertEquals( $one, $two ); + // === + $this->assertNotSame( $one, $two ); } /** * @dataProvider provideInvalidConstructorArg * @expectedException UnexpectedValueException + * @covers ProcessCacheLRU::__construct */ public function testConstructorGivenInvalidValue( $maxSize ) { new ProcessCacheLRUTestable( $maxSize ); @@ -88,6 +91,11 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { ); } + /** + * @covers ProcessCacheLRU::get + * @covers ProcessCacheLRU::set + * @covers ProcessCacheLRU::het + */ public function testAddAndGetAKey() { $oneCache = new ProcessCacheLRUTestable( 1 ); $this->assertCacheEmpty( $oneCache ); @@ -99,6 +107,10 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { $this->assertEquals( 'value1', $oneCache->get( 'cache-key', 'prop1' ) ); } + /** + * @covers ProcessCacheLRU::set + * @covers ProcessCacheLRU::get + */ public function testDeleteOldKey() { $oneCache = new ProcessCacheLRUTestable( 1 ); $this->assertCacheEmpty( $oneCache ); @@ -113,6 +125,7 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { * a sequence of always different cache-keys. Meant to verify we correclty * delete the older key. * + * @covers ProcessCacheLRU::set * @dataProvider provideCacheFilling * @param int $cacheMaxEntries Maximum entry the created cache will hold * @param int $entryToFill Number of entries to insert in the created cache. @@ -136,14 +149,18 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { return array( array( 1, 0 ), array( 1, 1 ), - array( 1, 2 ), # overflow - array( 5, 33 ), # overflow + // overflow + array( 1, 2 ), + // overflow + array( 5, 33 ), ); } /** * Create a cache with only one remaining entry then update * the first inserted entry. Should bump it to the top. + * + * @covers ProcessCacheLRU::set */ public function testReplaceExistingKeyShouldBumpEntryToTop() { $maxEntries = 3; @@ -164,6 +181,11 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { ); } + /** + * @covers ProcessCacheLRU::get + * @covers ProcessCacheLRU::set + * @covers ProcessCacheLRU::het + */ public function testRecentlyAccessedKeyStickIn() { $cache = new ProcessCacheLRUTestable( 2 ); $cache->set( 'first', 'prop1', 'value1' ); @@ -182,6 +204,9 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { * filled entry. * Given a cache having 1,2,3 as key, updating 2 should bump 2 to * the top of the queue with the new value: 1,3,2* (* = updated). + * + * @covers ProcessCacheLRU::set + * @covers ProcessCacheLRU::get */ public function testReplaceExistingKeyInAFullCacheShouldBumpToTop() { $maxEntries = 3; @@ -204,6 +229,9 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { ); } + /** + * @covers ProcessCacheLRU::set + */ public function testBumpExistingKeyToTop() { $cache = new ProcessCacheLRUTestable( 3 ); $this->fillCache( $cache, 3 ); diff --git a/tests/phpunit/includes/libs/RunningStatTest.php b/tests/phpunit/includes/libs/RunningStatTest.php index dc5db82c..edfaf162 100644 --- a/tests/phpunit/includes/libs/RunningStatTest.php +++ b/tests/phpunit/includes/libs/RunningStatTest.php @@ -3,7 +3,7 @@ * PHP Unit tests for RunningStat class. * @covers RunningStat */ -class RunningStatTest extends MediaWikiTestCase { +class RunningStatTest extends PHPUnit_Framework_TestCase { public $points = array( 49.7168, 74.3804, 7.0115, 96.5769, 34.9458, diff --git a/tests/phpunit/includes/libs/StringUtilsTest.php b/tests/phpunit/includes/libs/StringUtilsTest.php new file mode 100644 index 00000000..7c24fae6 --- /dev/null +++ b/tests/phpunit/includes/libs/StringUtilsTest.php @@ -0,0 +1,149 @@ +<?php + +class StringUtilsTest extends PHPUnit_Framework_TestCase { + + /** + * This tests StringUtils::isUtf8 whenever we have the mbstring extension + * loaded. + * + * @covers StringUtils::isUtf8 + * @dataProvider provideStringsForIsUtf8Check + */ + public function testIsUtf8WithMbstring( $expected, $string ) { + if ( !function_exists( 'mb_check_encoding' ) ) { + $this->markTestSkipped( 'Test requires the mbstring PHP extension' ); + } + $this->assertEquals( $expected, + StringUtils::isUtf8( $string ), + 'Testing string "' . $this->escaped( $string ) . '" with mb_check_encoding' + ); + } + + /** + * This tests StringUtils::isUtf8 making sure we use the pure PHP + * implementation used as a fallback when mb_check_encoding() is + * not available. + * + * @covers StringUtils::isUtf8 + * @dataProvider provideStringsForIsUtf8Check + */ + public function testIsUtf8WithPhpFallbackImplementation( $expected, $string ) { + $this->assertEquals( $expected, + StringUtils::isUtf8( $string, /** disable mbstring: */true ), + 'Testing string "' . $this->escaped( $string ) . '" with pure PHP implementation' + ); + } + + /** + * Print high range characters as a hexadecimal + * @param string $string + * @return string + */ + function escaped( $string ) { + $escaped = ''; + $length = strlen( $string ); + for ( $i = 0; $i < $length; $i++ ) { + $char = $string[$i]; + $val = ord( $char ); + if ( $val > 127 ) { + $escaped .= '\x' . dechex( $val ); + } else { + $escaped .= $char; + } + } + + return $escaped; + } + + /** + * See also "UTF-8 decoder capability and stress test" by + * Markus Kuhn: + * http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt + */ + public static function provideStringsForIsUtf8Check() { + // Expected return values for StringUtils::isUtf8() + $PASS = true; + $FAIL = false; + + return array( + 'some ASCII' => array( $PASS, 'Some ASCII' ), + 'euro sign' => array( $PASS, "Euro sign €" ), + + 'first possible sequence 1 byte' => array( $PASS, "\x00" ), + 'first possible sequence 2 bytes' => array( $PASS, "\xc2\x80" ), + 'first possible sequence 3 bytes' => array( $PASS, "\xe0\xa0\x80" ), + 'first possible sequence 4 bytes' => array( $PASS, "\xf0\x90\x80\x80" ), + 'first possible sequence 5 bytes' => array( $FAIL, "\xf8\x88\x80\x80\x80" ), + 'first possible sequence 6 bytes' => array( $FAIL, "\xfc\x84\x80\x80\x80\x80" ), + + 'last possible sequence 1 byte' => array( $PASS, "\x7f" ), + 'last possible sequence 2 bytes' => array( $PASS, "\xdf\xbf" ), + 'last possible sequence 3 bytes' => array( $PASS, "\xef\xbf\xbf" ), + 'last possible sequence 4 bytes (U+1FFFFF)' => array( $FAIL, "\xf7\xbf\xbf\xbf" ), + 'last possible sequence 5 bytes' => array( $FAIL, "\xfb\xbf\xbf\xbf\xbf" ), + 'last possible sequence 6 bytes' => array( $FAIL, "\xfd\xbf\xbf\xbf\xbf\xbf" ), + + 'boundary 1' => array( $PASS, "\xed\x9f\xbf" ), + 'boundary 2' => array( $PASS, "\xee\x80\x80" ), + 'boundary 3' => array( $PASS, "\xef\xbf\xbd" ), + 'boundary 4' => array( $PASS, "\xf2\x80\x80\x80" ), + 'boundary 5 (U+FFFFF)' => array( $PASS, "\xf3\xbf\xbf\xbf" ), + 'boundary 6 (U+100000)' => array( $PASS, "\xf4\x80\x80\x80" ), + 'boundary 7 (U+10FFFF)' => array( $PASS, "\xf4\x8f\xbf\xbf" ), + 'boundary 8 (U+110000)' => array( $FAIL, "\xf4\x90\x80\x80" ), + + 'malformed 1' => array( $FAIL, "\x80" ), + 'malformed 2' => array( $FAIL, "\xbf" ), + 'malformed 3' => array( $FAIL, "\x80\xbf" ), + 'malformed 4' => array( $FAIL, "\x80\xbf\x80" ), + 'malformed 5' => array( $FAIL, "\x80\xbf\x80\xbf" ), + 'malformed 6' => array( $FAIL, "\x80\xbf\x80\xbf\x80" ), + 'malformed 7' => array( $FAIL, "\x80\xbf\x80\xbf\x80\xbf" ), + 'malformed 8' => array( $FAIL, "\x80\xbf\x80\xbf\x80\xbf\x80" ), + + 'last byte missing 1' => array( $FAIL, "\xc0" ), + 'last byte missing 2' => array( $FAIL, "\xe0\x80" ), + 'last byte missing 3' => array( $FAIL, "\xf0\x80\x80" ), + 'last byte missing 4' => array( $FAIL, "\xf8\x80\x80\x80" ), + 'last byte missing 5' => array( $FAIL, "\xfc\x80\x80\x80\x80" ), + 'last byte missing 6' => array( $FAIL, "\xdf" ), + 'last byte missing 7' => array( $FAIL, "\xef\xbf" ), + 'last byte missing 8' => array( $FAIL, "\xf7\xbf\xbf" ), + 'last byte missing 9' => array( $FAIL, "\xfb\xbf\xbf\xbf" ), + 'last byte missing 10' => array( $FAIL, "\xfd\xbf\xbf\xbf\xbf" ), + + 'extra continuation byte 1' => array( $FAIL, "e\xaf" ), + 'extra continuation byte 2' => array( $FAIL, "\xc3\x89\xaf" ), + 'extra continuation byte 3' => array( $FAIL, "\xef\xbc\xa5\xaf" ), + 'extra continuation byte 4' => array( $FAIL, "\xf0\x9d\x99\xb4\xaf" ), + + 'impossible bytes 1' => array( $FAIL, "\xfe" ), + 'impossible bytes 2' => array( $FAIL, "\xff" ), + 'impossible bytes 3' => array( $FAIL, "\xfe\xfe\xff\xff" ), + + 'overlong sequences 1' => array( $FAIL, "\xc0\xaf" ), + 'overlong sequences 2' => array( $FAIL, "\xc1\xaf" ), + 'overlong sequences 3' => array( $FAIL, "\xe0\x80\xaf" ), + 'overlong sequences 4' => array( $FAIL, "\xf0\x80\x80\xaf" ), + 'overlong sequences 5' => array( $FAIL, "\xf8\x80\x80\x80\xaf" ), + 'overlong sequences 6' => array( $FAIL, "\xfc\x80\x80\x80\x80\xaf" ), + + 'maximum overlong sequences 1' => array( $FAIL, "\xc1\xbf" ), + 'maximum overlong sequences 2' => array( $FAIL, "\xe0\x9f\xbf" ), + 'maximum overlong sequences 3' => array( $FAIL, "\xf0\x8f\xbf\xbf" ), + 'maximum overlong sequences 4' => array( $FAIL, "\xf8\x87\xbf\xbf" ), + 'maximum overlong sequences 5' => array( $FAIL, "\xfc\x83\xbf\xbf\xbf\xbf" ), + + 'surrogates 1 (U+D799)' => array( $PASS, "\xed\x9f\xbf" ), + 'surrogates 2 (U+E000)' => array( $PASS, "\xee\x80\x80" ), + 'surrogates 3 (U+D800)' => array( $FAIL, "\xed\xa0\x80" ), + 'surrogates 4 (U+DBFF)' => array( $FAIL, "\xed\xaf\xbf" ), + 'surrogates 5 (U+DC00)' => array( $FAIL, "\xed\xb0\x80" ), + 'surrogates 6 (U+DFFF)' => array( $FAIL, "\xed\xbf\xbf" ), + 'surrogates 7 (U+D800 U+DC00)' => array( $FAIL, "\xed\xa0\x80\xed\xb0\x80" ), + + 'noncharacters 1' => array( $PASS, "\xef\xbf\xbe" ), + 'noncharacters 2' => array( $PASS, "\xef\xbf\xbf" ), + ); + } +} diff --git a/tests/phpunit/includes/libs/XhprofTest.php b/tests/phpunit/includes/libs/XhprofTest.php new file mode 100644 index 00000000..2440fc08 --- /dev/null +++ b/tests/phpunit/includes/libs/XhprofTest.php @@ -0,0 +1,320 @@ +<?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 + */ + +/** + * @uses Xhprof + * @uses AutoLoader + * @author Bryan Davis <bd808@wikimedia.org> + * @copyright © 2014 Bryan Davis and Wikimedia Foundation. + * @since 1.25 + */ +class XhprofTest extends PHPUnit_Framework_TestCase { + + public function setUp() { + if ( !function_exists( 'xhprof_enable' ) ) { + $this->markTestSkipped( 'No xhprof support detected.' ); + } + } + + /** + * @covers Xhprof::splitKey + * @dataProvider provideSplitKey + */ + public function testSplitKey( $key, $expect ) { + $this->assertSame( $expect, Xhprof::splitKey( $key ) ); + } + + public function provideSplitKey() { + return array( + array( 'main()', array( null, 'main()' ) ), + array( 'foo==>bar', array( 'foo', 'bar' ) ), + array( 'bar@1==>bar@2', array( 'bar@1', 'bar@2' ) ), + array( 'foo==>bar==>baz', array( 'foo', 'bar==>baz' ) ), + array( '==>bar', array( '', 'bar' ) ), + array( '', array( null, '' ) ), + ); + } + + /** + * @covers Xhprof::__construct + * @covers Xhprof::stop + * @covers Xhprof::getRawData + * @dataProvider provideRawData + */ + public function testRawData( $flags, $keys ) { + $xhprof = new Xhprof( array( 'flags' => $flags ) ); + $raw = $xhprof->getRawData(); + $this->assertArrayHasKey( 'main()', $raw ); + foreach ( $keys as $key ) { + $this->assertArrayHasKey( $key, $raw['main()'] ); + } + } + + public function provideRawData() { + $tests = array( + array( 0, array( 'ct', 'wt' ) ), + ); + + if ( defined( 'XHPROF_FLAGS_CPU' ) && defined( 'XHPROF_FLAGS_CPU' ) ) { + $tests[] = array( XHPROF_FLAGS_MEMORY, array( + 'ct', 'wt', 'mu', 'pmu', + ) ); + $tests[] = array( XHPROF_FLAGS_CPU, array( + 'ct', 'wt', 'cpu', + ) ); + $tests[] = array( XHPROF_FLAGS_MEMORY | XHPROF_FLAGS_CPU, array( + 'ct', 'wt', 'mu', 'pmu', 'cpu', + ) ); + } + + return $tests; + } + + /** + * @covers Xhprof::pruneData + */ + public function testInclude() { + $xhprof = $this->getXhprofFixture( array( + 'include' => array( 'main()' ), + ) ); + $raw = $xhprof->getRawData(); + $this->assertArrayHasKey( 'main()', $raw ); + $this->assertArrayHasKey( 'main()==>foo', $raw ); + $this->assertArrayHasKey( 'main()==>xhprof_disable', $raw ); + $this->assertSame( 3, count( $raw ) ); + } + + /** + * Validate the structure of data returned by + * Xhprof::getInclusiveMetrics(). This acts as a guard against unexpected + * structural changes to the returned data in lieu of using a more heavy + * weight typed response object. + * + * @covers Xhprof::getInclusiveMetrics + */ + public function testInclusiveMetricsStructure() { + $metricStruct = array( + 'ct' => 'int', + 'wt' => 'array', + 'cpu' => 'array', + 'mu' => 'array', + 'pmu' => 'array', + ); + $statStruct = array( + 'total' => 'numeric', + 'min' => 'numeric', + 'mean' => 'numeric', + 'max' => 'numeric', + 'variance' => 'numeric', + 'percent' => 'numeric', + ); + + $xhprof = $this->getXhprofFixture(); + $metrics = $xhprof->getInclusiveMetrics(); + + foreach ( $metrics as $name => $metric ) { + $this->assertArrayStructure( $metricStruct, $metric ); + + foreach ( $metricStruct as $key => $type ) { + if ( $type === 'array' ) { + $this->assertArrayStructure( $statStruct, $metric[$key] ); + if ( $name === 'main()' ) { + $this->assertEquals( 100, $metric[$key]['percent'] ); + } + } + } + } + } + + /** + * Validate the structure of data returned by + * Xhprof::getCompleteMetrics(). This acts as a guard against unexpected + * structural changes to the returned data in lieu of using a more heavy + * weight typed response object. + * + * @covers Xhprof::getCompleteMetrics + */ + public function testCompleteMetricsStructure() { + $metricStruct = array( + 'ct' => 'int', + 'wt' => 'array', + 'cpu' => 'array', + 'mu' => 'array', + 'pmu' => 'array', + 'calls' => 'array', + 'subcalls' => 'array', + ); + $statsMetrics = array( 'wt', 'cpu', 'mu', 'pmu' ); + $statStruct = array( + 'total' => 'numeric', + 'min' => 'numeric', + 'mean' => 'numeric', + 'max' => 'numeric', + 'variance' => 'numeric', + 'percent' => 'numeric', + 'exclusive' => 'numeric', + ); + + $xhprof = $this->getXhprofFixture(); + $metrics = $xhprof->getCompleteMetrics(); + + foreach ( $metrics as $name => $metric ) { + $this->assertArrayStructure( $metricStruct, $metric, $name ); + + foreach ( $metricStruct as $key => $type ) { + if ( in_array( $key, $statsMetrics ) ) { + $this->assertArrayStructure( + $statStruct, $metric[$key], $key + ); + $this->assertLessThanOrEqual( + $metric[$key]['total'], $metric[$key]['exclusive'] + ); + } + } + } + } + + /** + * @covers Xhprof::getCallers + * @covers Xhprof::getCallees + * @uses Xhprof + */ + public function testEdges() { + $xhprof = $this->getXhprofFixture(); + $this->assertSame( array(), $xhprof->getCallers( 'main()' ) ); + $this->assertSame( array( 'foo', 'xhprof_disable' ), + $xhprof->getCallees( 'main()' ) + ); + $this->assertSame( array( 'main()' ), + $xhprof->getCallers( 'foo' ) + ); + $this->assertSame( array(), $xhprof->getCallees( 'strlen' ) ); + } + + /** + * @covers Xhprof::getCriticalPath + * @uses Xhprof + */ + public function testCriticalPath() { + $xhprof = $this->getXhprofFixture(); + $path = $xhprof->getCriticalPath(); + + $last = null; + foreach ( $path as $key => $value ) { + list( $func, $call ) = Xhprof::splitKey( $key ); + $this->assertSame( $last, $func ); + $last = $call; + } + $this->assertSame( $last, 'bar@1' ); + } + + /** + * Get an Xhprof instance that has been primed with a set of known testing + * data. Tests for the Xhprof class should laregly be concerned with + * evaluating the manipulations of the data collected by xhprof rather + * than the data collection process itself. + * + * The returned Xhprof instance primed will be with a data set created by + * running this trivial program using the PECL xhprof implementation: + * @code + * function bar( $x ) { + * if ( $x > 0 ) { + * bar($x - 1); + * } + * } + * function foo() { + * for ( $idx = 0; $idx < 2; $idx++ ) { + * bar( $idx ); + * $x = strlen( 'abc' ); + * } + * } + * xhprof_enable( XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY ); + * foo(); + * $x = xhprof_disable(); + * var_export( $x ); + * @endcode + * + * @return Xhprof + */ + protected function getXhprofFixture( array $opts = array() ) { + $xhprof = new Xhprof( $opts ); + $xhprof->loadRawData( array ( + 'foo==>bar' => array ( + 'ct' => 2, + 'wt' => 57, + 'cpu' => 92, + 'mu' => 1896, + 'pmu' => 0, + ), + 'foo==>strlen' => array ( + 'ct' => 2, + 'wt' => 21, + 'cpu' => 141, + 'mu' => 752, + 'pmu' => 0, + ), + 'bar==>bar@1' => array ( + 'ct' => 1, + 'wt' => 18, + 'cpu' => 19, + 'mu' => 752, + 'pmu' => 0, + ), + 'main()==>foo' => array ( + 'ct' => 1, + 'wt' => 304, + 'cpu' => 307, + 'mu' => 4008, + 'pmu' => 0, + ), + 'main()==>xhprof_disable' => array ( + 'ct' => 1, + 'wt' => 8, + 'cpu' => 10, + 'mu' => 768, + 'pmu' => 392, + ), + 'main()' => array ( + 'ct' => 1, + 'wt' => 353, + 'cpu' => 351, + 'mu' => 6112, + 'pmu' => 1424, + ), + ) ); + return $xhprof; + } + + /** + * Assert that the given array has the described structure. + * + * @param array $struct Array of key => type mappings + * @param array $actual Array to check + * @param string $label + */ + protected function assertArrayStructure( $struct, $actual, $label = null ) { + $this->assertInternalType( 'array', $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/XmlTypeCheckTest.php b/tests/phpunit/includes/libs/XmlTypeCheckTest.php new file mode 100644 index 00000000..f0ba934e --- /dev/null +++ b/tests/phpunit/includes/libs/XmlTypeCheckTest.php @@ -0,0 +1,49 @@ +<?php +/** + * PHPUnit tests for XMLTypeCheck. + * @author physikerwelt + * @group Xml + * @covers XMLTypeCheck + */ +class XmlTypeCheckTest extends PHPUnit_Framework_TestCase { + const WELL_FORMED_XML = "<root><child /></root>"; + const MAL_FORMED_XML = "<root><child /></error>"; + const XML_WITH_PIH = '<?xml version="1.0"?><?xml-stylesheet type="text/xsl" href="/w/index.php"?><svg><child /></svg>'; + + /** + * @covers XMLTypeCheck::newFromString + * @covers XMLTypeCheck::getRootElement + */ + public function testWellFormedXML() { + $testXML = XmlTypeCheck::newFromString( self::WELL_FORMED_XML ); + $this->assertTrue( $testXML->wellFormed ); + $this->assertEquals( 'root', $testXML->getRootElement() ); + } + + /** + * @covers XMLTypeCheck::newFromString + */ + public function testMalFormedXML() { + $testXML = XmlTypeCheck::newFromString( self::MAL_FORMED_XML ); + $this->assertFalse( $testXML->wellFormed ); + } + + /** + * @covers XMLTypeCheck::processingInstructionHandler + */ + public function testProcessingInstructionHandler() { + $called = false; + $testXML = new XmlTypeCheck( + self::XML_WITH_PIH, + null, + false, + array( + 'processing_instruction_handler' => function() use ( &$called ) { + $called = true; + } + ) + ); + $this->assertTrue( $called ); + } + +} diff --git a/tests/phpunit/includes/libs/composer/ComposerJsonTest.php b/tests/phpunit/includes/libs/composer/ComposerJsonTest.php new file mode 100644 index 00000000..0c58b65a --- /dev/null +++ b/tests/phpunit/includes/libs/composer/ComposerJsonTest.php @@ -0,0 +1,57 @@ +<?php + +class ComposerJsonTest extends MediaWikiTestCase { + + private $json, $json2; + + public function setUp() { + parent::setUp(); + global $IP; + $this->json = "$IP/tests/phpunit/data/composer/composer.json"; + $this->json2 = "$IP/tests/phpunit/data/composer/new-composer.json"; + } + + public static function provideGetHash() { + return array( + array( 'json', 'cc6e7fc565b246cb30b0cac103a2b31e' ), + array( 'json2', '19921dd1fc457f1b00561da932432001' ), + ); + } + + /** + * @dataProvider provideGetHash + * @covers ComposerJson::getHash + */ + public function testIsHashUpToDate( $file, $expected ) { + $json = new ComposerJson( $this->$file ); + $this->assertEquals( $expected, $json->getHash() ); + } + + /** + * @covers ComposerJson::getRequiredDependencies + */ + public function testGetRequiredDependencies() { + $json = new ComposerJson( $this->json ); + $this->assertArrayEquals( array( + 'cdb/cdb' => '1.0.0', + 'cssjanus/cssjanus' => '1.1.1', + 'leafo/lessphp' => '0.5.0', + 'psr/log' => '1.0.0', + ), $json->getRequiredDependencies(), false, true ); + } + + public static function provideNormalizeVersion() { + return array( + array( 'v1.0.0', '1.0.0' ), + array( '0.0.5', '0.0.5' ), + ); + } + + /** + * @dataProvider provideNormalizeVersion + * @covers ComposerJson::normalizeVersion + */ + public function testNormalizeVersion( $input, $expected ) { + $this->assertEquals( $expected, ComposerJson::normalizeVersion( $input ) ); + } +} diff --git a/tests/phpunit/includes/libs/composer/ComposerLockTest.php b/tests/phpunit/includes/libs/composer/ComposerLockTest.php new file mode 100644 index 00000000..b5fd5f6e --- /dev/null +++ b/tests/phpunit/includes/libs/composer/ComposerLockTest.php @@ -0,0 +1,62 @@ +<?php + +class ComposerLockTest extends MediaWikiTestCase { + + private $lock; + + public function setUp() { + parent::setUp(); + global $IP; + $this->lock = "$IP/tests/phpunit/data/composer/composer.lock"; + } + + /** + * @covers ComposerLock::getHash + */ + public function testGetHash() { + $lock = new ComposerLock( $this->lock ); + $this->assertEquals( 'a3bb80b0ac4c4a31e52574d48c032923', $lock->getHash() ); + } + + /** + * @covers ComposerLock::getInstalledDependencies + */ + public function testGetInstalledDependencies() { + $lock = new ComposerLock( $this->lock ); + $this->assertArrayEquals( array( + 'wikimedia/cdb' => array( + 'version' => '1.0.1', + 'type' => 'library', + ), + 'cssjanus/cssjanus' => array( + 'version' => '1.1.1', + 'type' => 'library', + ), + 'leafo/lessphp' => array( + 'version' => '0.5.0', + 'type' => 'library', + ), + 'psr/log' => array( + 'version' => '1.0.0', + 'type' => 'library', + ), + 'oojs/oojs-ui' => array( + 'version' => '0.6.0', + 'type' => 'library', + ), + 'composer/installers' => array( + 'version' => '1.0.19', + 'type' => 'composer-installer', + ), + 'mediawiki/translate' => array( + 'version' => '2014.12', + 'type' => 'mediawiki-extension', + ), + 'mediawiki/universal-language-selector' => array( + 'version' => '2014.12', + 'type' => 'mediawiki-extension', + ), + ), $lock->getInstalledDependencies(), false, true ); + } + +} |