summaryrefslogtreecommitdiff
path: root/tests/phpunit/includes/libs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/phpunit/includes/libs')
-rw-r--r--tests/phpunit/includes/libs/CSSMinTest.php401
-rw-r--r--tests/phpunit/includes/libs/GenericArrayObjectTest.php280
-rw-r--r--tests/phpunit/includes/libs/HashRingTest.php56
-rw-r--r--tests/phpunit/includes/libs/IEUrlExtensionTest.php173
-rw-r--r--tests/phpunit/includes/libs/IPSetTest.php252
-rw-r--r--tests/phpunit/includes/libs/JavaScriptMinifierTest.php204
-rw-r--r--tests/phpunit/includes/libs/MWMessagePackTest.php75
-rw-r--r--tests/phpunit/includes/libs/ProcessCacheLRUTest.php237
-rw-r--r--tests/phpunit/includes/libs/RunningStatTest.php79
9 files changed, 1757 insertions, 0 deletions
diff --git a/tests/phpunit/includes/libs/CSSMinTest.php b/tests/phpunit/includes/libs/CSSMinTest.php
new file mode 100644
index 00000000..43c50869
--- /dev/null
+++ b/tests/phpunit/includes/libs/CSSMinTest.php
@@ -0,0 +1,401 @@
+<?php
+/**
+ * This file test the CSSMin library shipped with Mediawiki.
+ *
+ * @author Timo Tijhof
+ */
+
+class CSSMinTest extends MediaWikiTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+
+ $server = 'http://doc.example.org';
+
+ $this->setMwGlobals( array(
+ 'wgServer' => $server,
+ 'wgCanonicalServer' => $server,
+ ) );
+ }
+
+ /**
+ * @dataProvider provideMinifyCases
+ * @covers CSSMin::minify
+ */
+ public function testMinify( $code, $expectedOutput ) {
+ $minified = CSSMin::minify( $code );
+
+ $this->assertEquals(
+ $expectedOutput,
+ $minified,
+ 'Minified output should be in the form expected.'
+ );
+ }
+
+ public static function provideMinifyCases() {
+ return array(
+ // Whitespace
+ array( "\r\t\f \v\n\r", "" ),
+ array( "foo, bar {\n\tprop: value;\n}", "foo,bar{prop:value}" ),
+
+ // Loose comments
+ array( "/* foo */", "" ),
+ array( "/*******\n foo\n *******/", "" ),
+ array( "/*!\n foo\n */", "" ),
+
+ // Inline comments in various different places
+ array( "/* comment */foo, bar {\n\tprop: value;\n}", "foo,bar{prop:value}" ),
+ array( "foo/* comment */, bar {\n\tprop: value;\n}", "foo,bar{prop:value}" ),
+ array( "foo,/* comment */ bar {\n\tprop: value;\n}", "foo,bar{prop:value}" ),
+ array( "foo, bar/* comment */ {\n\tprop: value;\n}", "foo,bar{prop:value}" ),
+ array( "foo, bar {\n\t/* comment */prop: value;\n}", "foo,bar{prop:value}" ),
+ array( "foo, bar {\n\tprop: /* comment */value;\n}", "foo,bar{prop:value}" ),
+ array( "foo, bar {\n\tprop: value /* comment */;\n}", "foo,bar{prop:value }" ),
+ array( "foo, bar {\n\tprop: value; /* comment */\n}", "foo,bar{prop:value; }" ),
+
+ // Keep track of things that aren't as minified as much as they
+ // could be (bug 35493)
+ array( 'foo { prop: value ;}', 'foo{prop:value }' ),
+ array( 'foo { prop : value; }', 'foo{prop :value}' ),
+ array( 'foo { prop: value ; }', 'foo{prop:value }' ),
+ array( 'foo { font-family: "foo" , "bar"; }', 'foo{font-family:"foo" ,"bar"}' ),
+ array( "foo { src:\n\turl('foo') ,\n\turl('bar') ; }", "foo{src:url('foo') ,url('bar') }" ),
+
+ // Interesting cases with string values
+ // - Double quotes, single quotes
+ array( 'foo { content: ""; }', 'foo{content:""}' ),
+ array( "foo { content: ''; }", "foo{content:''}" ),
+ array( 'foo { content: "\'"; }', 'foo{content:"\'"}' ),
+ array( "foo { content: '\"'; }", "foo{content:'\"'}" ),
+ // - Whitespace in string values
+ array( 'foo { content: " "; }', 'foo{content:" "}' ),
+ );
+ }
+
+ /**
+ * This tests funky parameters to CSSMin::remap. testRemapRemapping tests
+ * the basic functionality.
+ *
+ * @dataProvider provideRemapCases
+ * @covers CSSMin::remap
+ */
+ public function testRemap( $message, $params, $expectedOutput ) {
+ $remapped = call_user_func_array( 'CSSMin::remap', $params );
+
+ $messageAdd = " Case: $message";
+ $this->assertEquals(
+ $expectedOutput,
+ $remapped,
+ 'CSSMin::remap should return the expected url form.' . $messageAdd
+ );
+ }
+
+ public static function provideRemapCases() {
+ // Parameter signature:
+ // CSSMin::remap( $code, $local, $remote, $embedData = true )
+ return array(
+ array(
+ 'Simple case',
+ array( 'foo { prop: url(bar.png); }', false, 'http://example.org', false ),
+ 'foo { prop: url(http://example.org/bar.png); }',
+ ),
+ array(
+ 'Without trailing slash',
+ array( 'foo { prop: url(../bar.png); }', false, 'http://example.org/quux', false ),
+ 'foo { prop: url(http://example.org/quux/../bar.png); }',
+ ),
+ array(
+ 'With trailing slash on remote (bug 27052)',
+ array( 'foo { prop: url(../bar.png); }', false, 'http://example.org/quux/', false ),
+ 'foo { prop: url(http://example.org/quux/../bar.png); }',
+ ),
+ array(
+ 'Guard against stripping double slashes from query',
+ array( 'foo { prop: url(bar.png?corge=//grault); }', false, 'http://example.org/quux/', false ),
+ 'foo { prop: url(http://example.org/quux/bar.png?corge=//grault); }',
+ ),
+ array(
+ 'Expand absolute paths',
+ array( 'foo { prop: url(/w/skin/images/bar.png); }', false, 'http://example.org/quux', false ),
+ 'foo { prop: url(http://doc.example.org/w/skin/images/bar.png); }',
+ ),
+ );
+ }
+
+ /**
+ * This tests basic functionality of CSSMin::remap. testRemapRemapping tests funky parameters.
+ *
+ * @dataProvider provideRemapRemappingCases
+ * @covers CSSMin::remap
+ */
+ public function testRemapRemapping( $message, $input, $expectedOutput ) {
+ $localPath = __DIR__ . '/../../data/cssmin/';
+ $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"
+ );
+ }
+
+ 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
+ $red = 'data:image/gif;base64,R0lGODlhAQABAIAAAP8AADAAACwAAAAAAQABAAACAkQBADs=';
+ $green = 'data:image/gif;base64,R0lGODlhAQABAIAAAACAADAAACwAAAAAAQABAAACAkQBADs=';
+
+ return array(
+ array(
+ 'Regular file',
+ 'foo { background: url(red.gif); }',
+ 'foo { background: url(http://localhost/w/red.gif?timestamp); }',
+ ),
+ array(
+ 'Regular file (missing)',
+ 'foo { background: url(theColorOfHerHair.gif); }',
+ 'foo { background: url(http://localhost/w/theColorOfHerHair.gif); }',
+ ),
+ array(
+ 'Remote URL',
+ 'foo { background: url(http://example.org/w/foo.png); }',
+ 'foo { background: url(http://example.org/w/foo.png); }',
+ ),
+ array(
+ 'Protocol-relative remote URL',
+ 'foo { background: url(//example.org/w/foo.png); }',
+ 'foo { background: url(//example.org/w/foo.png); }',
+ ),
+ array(
+ 'Remote URL with query',
+ 'foo { background: url(http://example.org/w/foo.png?query=yes); }',
+ 'foo { background: url(http://example.org/w/foo.png?query=yes); }',
+ ),
+ array(
+ 'Protocol-relative remote URL with query',
+ 'foo { background: url(//example.org/w/foo.png?query=yes); }',
+ 'foo { background: url(//example.org/w/foo.png?query=yes); }',
+ ),
+ array(
+ 'Domain-relative URL',
+ 'foo { background: url(/static/foo.png); }',
+ 'foo { background: url(http://doc.example.org/static/foo.png); }',
+ ),
+ array(
+ 'Domain-relative URL with query',
+ 'foo { background: url(/static/foo.png?query=yes); }',
+ 'foo { background: url(http://doc.example.org/static/foo.png?query=yes); }',
+ ),
+ array(
+ 'Remote URL (unnecessary quotes not preserved)',
+ 'foo { background: url("http://example.org/w/foo.png"); }',
+ 'foo { background: url(http://example.org/w/foo.png); }',
+ ),
+ array(
+ 'Embedded file',
+ 'foo { /* @embed */ background: url(red.gif); }',
+ "foo { background: url($red); background: url(http://localhost/w/red.gif?timestamp)!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; }",
+ ),
+ array(
+ 'Can not re-embed data: URIs',
+ "foo { /* @embed */ background: url($red); }",
+ "foo { background: url($red); }",
+ ),
+ array(
+ 'Can not remap data: URIs',
+ "foo { background: url($red); }",
+ "foo { background: url($red); }",
+ ),
+ array(
+ 'Can not embed remote URLs',
+ 'foo { /* @embed */ background: url(http://example.org/w/foo.png); }',
+ 'foo { background: url(http://example.org/w/foo.png); }',
+ ),
+ array(
+ 'Embedded file (inline @embed)',
+ 'foo { background: /* @embed */ url(red.gif); }',
+ "foo { background: url($red); "
+ . "background: url(http://localhost/w/red.gif?timestamp)!ie; }",
+ ),
+ array(
+ 'Can not embed large files',
+ 'foo { /* @embed */ background: url(large.png); }',
+ "foo { background: url(http://localhost/w/large.png?timestamp); }",
+ ),
+ 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); }',
+ ),
+ 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; }",
+ ),
+ 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; }",
+ ),
+ 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; }",
+ ),
+ 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; }",
+ ),
+ 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; }',
+ ),
+ array(
+ 'Spacing and miscellanea not changed (1)',
+ 'foo { background: url(red.gif); }',
+ 'foo { background: url(http://localhost/w/red.gif?timestamp); }',
+ ),
+ array(
+ 'Spacing and miscellanea not changed (2)',
+ 'foo {background:url(red.gif)}',
+ 'foo {background:url(http://localhost/w/red.gif?timestamp)}',
+ ),
+ array(
+ 'Spaces within url() parentheses are ignored',
+ 'foo { background: url( red.gif ); }',
+ 'foo { background: url(http://localhost/w/red.gif?timestamp); }',
+ ),
+ array(
+ '@import rule to local file (should we remap this?)',
+ '@import url(/styles.css)',
+ '@import url(http://doc.example.org/styles.css)',
+ ),
+ array(
+ '@import rule to URL (should we remap this?)',
+ '@import url(//localhost/styles.css?query=yes)',
+ '@import url(//localhost/styles.css?query=yes)',
+ ),
+ array(
+ 'Simple case with comments before url',
+ 'foo { prop: /* some {funny;} comment */ url(bar.png); }',
+ 'foo { prop: /* some {funny;} comment */ url(http://localhost/w/bar.png); }',
+ ),
+ 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 */ ; }',
+ ),
+ 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; }",
+ ),
+ 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 */ }",
+ ),
+ 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 */ }",
+ ),
+ array(
+ 'Rule with two urls, each with comments',
+ '{ background: /*asd*/ url(something.png); background: /*jkl*/ url(something.png); }',
+ '{ background: /*asd*/ url(http://localhost/w/something.png); background: /*jkl*/ url(http://localhost/w/something.png); }',
+ ),
+ array(
+ 'Sanity check for offending line from jquery.ui.theme.css (bug 60077)',
+ '.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3/*{borderColorDefault}*/; background: #e6e6e6/*{bgColorDefault}*/ url(images/ui-bg_glass_75_e6e6e6_1x400.png)/*{bgImgUrlDefault}*/ 50%/*{bgDefaultXPos}*/ 50%/*{bgDefaultYPos}*/ repeat-x/*{bgDefaultRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #555555/*{fcDefault}*/; }',
+ '.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3/*{borderColorDefault}*/; background: #e6e6e6/*{bgColorDefault}*/ url(http://localhost/w/images/ui-bg_glass_75_e6e6e6_1x400.png)/*{bgImgUrlDefault}*/ 50%/*{bgDefaultXPos}*/ 50%/*{bgDefaultYPos}*/ repeat-x/*{bgDefaultRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #555555/*{fcDefault}*/; }',
+ ),
+ );
+ }
+
+ /**
+ * This tests basic functionality of CSSMin::buildUrlValue.
+ *
+ * @dataProvider provideBuildUrlValueCases
+ * @covers CSSMin::buildUrlValue
+ */
+ public function testBuildUrlValue( $message, $input, $expectedOutput ) {
+ $this->assertEquals(
+ $expectedOutput,
+ CSSMin::buildUrlValue( $input ),
+ "CSSMin::buildUrlValue: $message"
+ );
+ }
+
+ public static function provideBuildUrlValueCases() {
+ return array(
+ array(
+ 'Full URL',
+ 'scheme://user@domain:port/~user/fi%20le.png?query=yes&really=y+s',
+ 'url(scheme://user@domain:port/~user/fi%20le.png?query=yes&really=y+s)',
+ ),
+ array(
+ 'data: URI',
+ 'data:image/png;base64,R0lGODlh/+==',
+ 'url(data:image/png;base64,R0lGODlh/+==)',
+ ),
+ array(
+ 'URL with quotes',
+ "https://en.wikipedia.org/wiki/Wendy's",
+ "url(\"https://en.wikipedia.org/wiki/Wendy's\")",
+ ),
+ array(
+ 'URL with parentheses',
+ 'https://en.wikipedia.org/wiki/Boston_(band)',
+ 'url("https://en.wikipedia.org/wiki/Boston_(band)")',
+ ),
+ );
+ }
+
+ /**
+ * Seperated because they are currently broken (bug 35492)
+ *
+ * @group Broken
+ * @dataProvider provideStringCases
+ * @covers CSSMin::remap
+ */
+ public function testMinifyWithCSSStringValues( $code, $expectedOutput ) {
+ $this->testMinifyOutput( $code, $expectedOutput );
+ }
+
+ public static function provideStringCases() {
+ return array(
+ // String values should be respected
+ // - More than one space in a string value
+ array( 'foo { content: " "; }', 'foo{content:" "}' ),
+ // - Using a tab in a string value (turns into a space)
+ array( "foo { content: '\t'; }", "foo{content:'\t'}" ),
+ // - Using css-like syntax in string values
+ array(
+ 'foo::after { content: "{;}"; position: absolute; }',
+ 'foo::after{content:"{;}";position:absolute}'
+ ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/libs/GenericArrayObjectTest.php b/tests/phpunit/includes/libs/GenericArrayObjectTest.php
new file mode 100644
index 00000000..4911f73a
--- /dev/null
+++ b/tests/phpunit/includes/libs/GenericArrayObjectTest.php
@@ -0,0 +1,280 @@
+<?php
+
+/**
+ * Tests for the GenericArrayObject and deriving classes.
+ *
+ * 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
+ * @since 1.20
+ *
+ * @ingroup Test
+ * @group GenericArrayObject
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+abstract class GenericArrayObjectTest extends MediaWikiTestCase {
+
+ /**
+ * Returns objects that can serve as elements in the concrete
+ * GenericArrayObject deriving class being tested.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ abstract public function elementInstancesProvider();
+
+ /**
+ * Returns the name of the concrete class being tested.
+ *
+ * @since 1.20
+ *
+ * @return string
+ */
+ abstract public function getInstanceClass();
+
+ /**
+ * Provides instances of the concrete class being tested.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function instanceProvider() {
+ $instances = array();
+
+ foreach ( $this->elementInstancesProvider() as $elementInstances ) {
+ $instances[] = $this->getNew( $elementInstances[0] );
+ }
+
+ return $this->arrayWrap( $instances );
+ }
+
+ /**
+ * @since 1.20
+ *
+ * @param array $elements
+ *
+ * @return GenericArrayObject
+ */
+ protected function getNew( array $elements = array() ) {
+ $class = $this->getInstanceClass();
+
+ return new $class( $elements );
+ }
+
+ /**
+ * @dataProvider elementInstancesProvider
+ *
+ * @since 1.20
+ *
+ * @param array $elements
+ *
+ * @covers GenericArrayObject::__construct
+ */
+ public function testConstructor( array $elements ) {
+ $arrayObject = $this->getNew( $elements );
+
+ $this->assertEquals( count( $elements ), $arrayObject->count() );
+ }
+
+ /**
+ * @dataProvider elementInstancesProvider
+ *
+ * @since 1.20
+ *
+ * @param array $elements
+ *
+ * @covers GenericArrayObject::isEmpty
+ */
+ public function testIsEmpty( array $elements ) {
+ $arrayObject = $this->getNew( $elements );
+
+ $this->assertEquals( $elements === array(), $arrayObject->isEmpty() );
+ }
+
+ /**
+ * @dataProvider instanceProvider
+ *
+ * @since 1.20
+ *
+ * @param GenericArrayObject $list
+ *
+ * @covers GenericArrayObject::offsetUnset
+ */
+ public function testUnset( GenericArrayObject $list ) {
+ if ( $list->isEmpty() ) {
+ $this->assertTrue( true ); // We cannot test unset if there are no elements
+ } else {
+ $offset = $list->getIterator()->key();
+ $count = $list->count();
+ $list->offsetUnset( $offset );
+ $this->assertEquals( $count - 1, $list->count() );
+ }
+
+ if ( !$list->isEmpty() ) {
+ $offset = $list->getIterator()->key();
+ $count = $list->count();
+ unset( $list[$offset] );
+ $this->assertEquals( $count - 1, $list->count() );
+ }
+ }
+
+ /**
+ * @dataProvider elementInstancesProvider
+ *
+ * @since 1.20
+ *
+ * @param array $elements
+ *
+ * @covers GenericArrayObject::append
+ */
+ public function testAppend( array $elements ) {
+ $list = $this->getNew();
+
+ $listSize = count( $elements );
+
+ foreach ( $elements as $element ) {
+ $list->append( $element );
+ }
+
+ $this->assertEquals( $listSize, $list->count() );
+
+ $list = $this->getNew();
+
+ foreach ( $elements as $element ) {
+ $list[] = $element;
+ }
+
+ $this->assertEquals( $listSize, $list->count() );
+
+ $this->checkTypeChecks( function ( GenericArrayObject $list, $element ) {
+ $list->append( $element );
+ } );
+ }
+
+ /**
+ * @since 1.20
+ *
+ * @param callable $function
+ *
+ * @covers GenericArrayObject::getObjectType
+ */
+ protected function checkTypeChecks( $function ) {
+ $excption = null;
+ $list = $this->getNew();
+
+ $elementClass = $list->getObjectType();
+
+ foreach ( array( 42, 'foo', array(), new stdClass(), 4.2 ) as $element ) {
+ $validValid = $element instanceof $elementClass;
+
+ try {
+ call_user_func( $function, $list, $element );
+ $valid = true;
+ } catch ( InvalidArgumentException $exception ) {
+ $valid = false;
+ }
+
+ $this->assertEquals(
+ $validValid,
+ $valid,
+ 'Object of invalid type got successfully added to a GenericArrayObject'
+ );
+ }
+ }
+
+ /**
+ * @dataProvider elementInstancesProvider
+ *
+ * @since 1.20
+ *
+ * @param array $elements
+ *
+ * @covers GenericArrayObject::offsetSet
+ */
+ public function testOffsetSet( array $elements ) {
+ if ( $elements === array() ) {
+ $this->assertTrue( true );
+
+ return;
+ }
+
+ $list = $this->getNew();
+
+ $element = reset( $elements );
+ $list->offsetSet( 42, $element );
+ $this->assertEquals( $element, $list->offsetGet( 42 ) );
+
+ $list = $this->getNew();
+
+ $element = reset( $elements );
+ $list['oHai'] = $element;
+ $this->assertEquals( $element, $list['oHai'] );
+
+ $list = $this->getNew();
+
+ $element = reset( $elements );
+ $list->offsetSet( 9001, $element );
+ $this->assertEquals( $element, $list[9001] );
+
+ $list = $this->getNew();
+
+ $element = reset( $elements );
+ $list->offsetSet( null, $element );
+ $this->assertEquals( $element, $list[0] );
+
+ $list = $this->getNew();
+ $offset = 0;
+
+ foreach ( $elements as $element ) {
+ $list->offsetSet( null, $element );
+ $this->assertEquals( $element, $list[$offset++] );
+ }
+
+ $this->assertEquals( count( $elements ), $list->count() );
+
+ $this->checkTypeChecks( function ( GenericArrayObject $list, $element ) {
+ $list->offsetSet( mt_rand(), $element );
+ } );
+ }
+
+ /**
+ * @dataProvider instanceProvider
+ *
+ * @since 1.21
+ *
+ * @param GenericArrayObject $list
+ *
+ * @covers GenericArrayObject::getSerializationData
+ * @covers GenericArrayObject::serialize
+ * @covers GenericArrayObject::unserialize
+ */
+ public function testSerialization( GenericArrayObject $list ) {
+ $serialization = serialize( $list );
+ $copy = unserialize( $serialization );
+
+ $this->assertEquals( $serialization, serialize( $copy ) );
+ $this->assertEquals( count( $list ), count( $copy ) );
+
+ $list = $list->getArrayCopy();
+ $copy = $copy->getArrayCopy();
+
+ $this->assertArrayEquals( $list, $copy, true, true );
+ }
+}
diff --git a/tests/phpunit/includes/libs/HashRingTest.php b/tests/phpunit/includes/libs/HashRingTest.php
new file mode 100644
index 00000000..68dfea1f
--- /dev/null
+++ b/tests/phpunit/includes/libs/HashRingTest.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @group HashRing
+ */
+class HashRingTest extends MediaWikiTestCase {
+ /**
+ * @covers HashRing
+ */
+ public function testHashRing() {
+ $ring = new HashRing( array( 's1' => 1, 's2' => 1, 's3' => 2, 's4' => 2, 's5' => 2, 's6' => 3 ) );
+
+ $locations = array();
+ for ( $i = 0; $i < 20; $i++ ) {
+ $locations[ "hello$i"] = $ring->getLocation( "hello$i" );
+ }
+ $expectedLocations = array(
+ "hello0" => "s5",
+ "hello1" => "s6",
+ "hello2" => "s2",
+ "hello3" => "s5",
+ "hello4" => "s6",
+ "hello5" => "s4",
+ "hello6" => "s5",
+ "hello7" => "s4",
+ "hello8" => "s5",
+ "hello9" => "s5",
+ "hello10" => "s3",
+ "hello11" => "s6",
+ "hello12" => "s1",
+ "hello13" => "s3",
+ "hello14" => "s3",
+ "hello15" => "s5",
+ "hello16" => "s4",
+ "hello17" => "s6",
+ "hello18" => "s6",
+ "hello19" => "s3"
+ );
+
+ $this->assertEquals( $expectedLocations, $locations, 'Items placed at proper locations' );
+
+ $locations = array();
+ for ( $i = 0; $i < 5; $i++ ) {
+ $locations[ "hello$i"] = $ring->getLocations( "hello$i", 2 );
+ }
+
+ $expectedLocations = array(
+ "hello0" => array( "s5", "s6" ),
+ "hello1" => array( "s6", "s4" ),
+ "hello2" => array( "s2", "s1" ),
+ "hello3" => array( "s5", "s6" ),
+ "hello4" => array( "s6", "s4" ),
+ );
+ $this->assertEquals( $expectedLocations, $locations, 'Items placed at proper locations' );
+ }
+}
diff --git a/tests/phpunit/includes/libs/IEUrlExtensionTest.php b/tests/phpunit/includes/libs/IEUrlExtensionTest.php
new file mode 100644
index 00000000..b7071230
--- /dev/null
+++ b/tests/phpunit/includes/libs/IEUrlExtensionTest.php
@@ -0,0 +1,173 @@
+<?php
+
+/**
+ * Tests for IEUrlExtension::findIE6Extension
+ * @todo tests below for findIE6Extension should be split into...
+ * ...a dataprovider and test method.
+ */
+class IEUrlExtensionTest extends MediaWikiTestCase {
+ /**
+ * @covers IEUrlExtension::findIE6Extension
+ */
+ public function testSimple() {
+ $this->assertEquals(
+ 'y',
+ IEUrlExtension::findIE6Extension( 'x.y' ),
+ 'Simple extension'
+ );
+ }
+
+ /**
+ * @covers IEUrlExtension::findIE6Extension
+ */
+ public function testSimpleNoExt() {
+ $this->assertEquals(
+ '',
+ IEUrlExtension::findIE6Extension( 'x' ),
+ 'No extension'
+ );
+ }
+
+ /**
+ * @covers IEUrlExtension::findIE6Extension
+ */
+ public function testEmpty() {
+ $this->assertEquals(
+ '',
+ IEUrlExtension::findIE6Extension( '' ),
+ 'Empty string'
+ );
+ }
+
+ /**
+ * @covers IEUrlExtension::findIE6Extension
+ */
+ public function testQuestionMark() {
+ $this->assertEquals(
+ '',
+ IEUrlExtension::findIE6Extension( '?' ),
+ 'Question mark only'
+ );
+ }
+
+ /**
+ * @covers IEUrlExtension::findIE6Extension
+ */
+ public function testExtQuestionMark() {
+ $this->assertEquals(
+ 'x',
+ IEUrlExtension::findIE6Extension( '.x?' ),
+ 'Extension then question mark'
+ );
+ }
+
+ /**
+ * @covers IEUrlExtension::findIE6Extension
+ */
+ public function testQuestionMarkExt() {
+ $this->assertEquals(
+ 'x',
+ IEUrlExtension::findIE6Extension( '?.x' ),
+ 'Question mark then extension'
+ );
+ }
+
+ /**
+ * @covers IEUrlExtension::findIE6Extension
+ */
+ public function testInvalidChar() {
+ $this->assertEquals(
+ '',
+ IEUrlExtension::findIE6Extension( '.x*' ),
+ 'Extension with invalid character'
+ );
+ }
+
+ /**
+ * @covers IEUrlExtension::findIE6Extension
+ */
+ public function testInvalidCharThenExtension() {
+ $this->assertEquals(
+ 'x',
+ IEUrlExtension::findIE6Extension( '*.x' ),
+ 'Invalid character followed by an extension'
+ );
+ }
+
+ /**
+ * @covers IEUrlExtension::findIE6Extension
+ */
+ public function testMultipleQuestionMarks() {
+ $this->assertEquals(
+ 'c',
+ IEUrlExtension::findIE6Extension( 'a?b?.c?.d?e?f' ),
+ 'Multiple question marks'
+ );
+ }
+
+ /**
+ * @covers IEUrlExtension::findIE6Extension
+ */
+ public function testExeException() {
+ $this->assertEquals(
+ 'd',
+ IEUrlExtension::findIE6Extension( 'a?b?.exe?.d?.e' ),
+ '.exe exception'
+ );
+ }
+
+ /**
+ * @covers IEUrlExtension::findIE6Extension
+ */
+ public function testExeException2() {
+ $this->assertEquals(
+ 'exe',
+ IEUrlExtension::findIE6Extension( 'a?b?.exe' ),
+ '.exe exception 2'
+ );
+ }
+
+ /**
+ * @covers IEUrlExtension::findIE6Extension
+ */
+ public function testHash() {
+ $this->assertEquals(
+ '',
+ IEUrlExtension::findIE6Extension( 'a#b.c' ),
+ 'Hash character preceding extension'
+ );
+ }
+
+ /**
+ * @covers IEUrlExtension::findIE6Extension
+ */
+ public function testHash2() {
+ $this->assertEquals(
+ '',
+ IEUrlExtension::findIE6Extension( 'a?#b.c' ),
+ 'Hash character preceding extension 2'
+ );
+ }
+
+ /**
+ * @covers IEUrlExtension::findIE6Extension
+ */
+ public function testDotAtEnd() {
+ $this->assertEquals(
+ '',
+ IEUrlExtension::findIE6Extension( '.' ),
+ 'Dot at end of string'
+ );
+ }
+
+ /**
+ * @covers IEUrlExtension::findIE6Extension
+ */
+ public function testTwoDots() {
+ $this->assertEquals(
+ 'z',
+ IEUrlExtension::findIE6Extension( 'x.y.z' ),
+ 'Two dots'
+ );
+ }
+}
diff --git a/tests/phpunit/includes/libs/IPSetTest.php b/tests/phpunit/includes/libs/IPSetTest.php
new file mode 100644
index 00000000..d4e5214a
--- /dev/null
+++ b/tests/phpunit/includes/libs/IPSetTest.php
@@ -0,0 +1,252 @@
+<?php
+
+/**
+ * @group IPSet
+ */
+class IPSetTest extends MediaWikiTestCase {
+ /**
+ * 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
new file mode 100644
index 00000000..c8795b2e
--- /dev/null
+++ b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php
@@ -0,0 +1,204 @@
+<?php
+
+class JavaScriptMinifierTest extends MediaWikiTestCase {
+
+ public static function provideCases() {
+ return array(
+
+ // Basic whitespace and comments that should be stripped entirely
+ array( "\r\t\f \v\n\r", "" ),
+ array( "/* Foo *\n*bar\n*/", "" ),
+
+ /**
+ * Slashes used inside block comments (bug 26931).
+ * At some point there was a bug that caused this comment to be ended at '* /',
+ * causing /M... to be left as the beginning of a regex.
+ */
+ array(
+ "/**\n * Foo\n * {\n * 'bar' : {\n * "
+ . "//Multiple rules with configurable operators\n * 'baz' : false\n * }\n */",
+ "" ),
+
+ /**
+ * ' Foo \' bar \
+ * baz \' quox ' .
+ */
+ array(
+ "' Foo \\' bar \\\n baz \\' quox ' .length",
+ "' Foo \\' bar \\\n baz \\' quox '.length"
+ ),
+ array(
+ "\" Foo \\\" bar \\\n baz \\\" quox \" .length",
+ "\" Foo \\\" bar \\\n baz \\\" quox \".length"
+ ),
+ array( "// Foo b/ar baz", "" ),
+ array(
+ "/ Foo \\/ bar [ / \\] / ] baz / .length",
+ "/ Foo \\/ bar [ / \\] / ] baz /.length"
+ ),
+
+ // HTML comments
+ array( "<!-- Foo bar", "" ),
+ array( "<!-- Foo --> bar", "" ),
+ array( "--> Foo", "" ),
+ array( "x --> y", "x-->y" ),
+
+ // Semicolon insertion
+ array( "(function(){return\nx;})", "(function(){return\nx;})" ),
+ array( "throw\nx;", "throw\nx;" ),
+ array( "while(p){continue\nx;}", "while(p){continue\nx;}" ),
+ array( "while(p){break\nx;}", "while(p){break\nx;}" ),
+ array( "var\nx;", "var x;" ),
+ array( "x\ny;", "x\ny;" ),
+ array( "x\n++y;", "x\n++y;" ),
+ array( "x\n!y;", "x\n!y;" ),
+ array( "x\n{y}", "x\n{y}" ),
+ array( "x\n+y;", "x+y;" ),
+ array( "x\n(y);", "x(y);" ),
+ array( "5.\nx;", "5.\nx;" ),
+ array( "0xFF.\nx;", "0xFF.x;" ),
+ array( "5.3.\nx;", "5.3.x;" ),
+
+ // Semicolon insertion between an expression having an inline
+ // comment after it, and a statement on the next line (bug 27046).
+ array(
+ "var a = this //foo bar \n for ( b = 0; c < d; b++ ) {}",
+ "var a=this\nfor(b=0;c<d;b++){}"
+ ),
+
+ // Token separation
+ array( "x in y", "x in y" ),
+ array( "/x/g in y", "/x/g in y" ),
+ array( "x in 30", "x in 30" ),
+ array( "x + ++ y", "x+ ++y" ),
+ array( "x ++ + y", "x++ +y" ),
+ array( "x / /y/.exec(z)", "x/ /y/.exec(z)" ),
+
+ // State machine
+ array( "/ x/g", "/ x/g" ),
+ array( "(function(){return/ x/g})", "(function(){return/ x/g})" ),
+ array( "+/ x/g", "+/ x/g" ),
+ array( "++/ x/g", "++/ x/g" ),
+ array( "x/ x/g", "x/x/g" ),
+ array( "(/ x/g)", "(/ x/g)" ),
+ array( "if(/ x/g);", "if(/ x/g);" ),
+ array( "(x/ x/g)", "(x/x/g)" ),
+ array( "([/ x/g])", "([/ x/g])" ),
+ array( "+x/ x/g", "+x/x/g" ),
+ array( "{}/ x/g", "{}/ x/g" ),
+ array( "+{}/ x/g", "+{}/x/g" ),
+ array( "(x)/ x/g", "(x)/x/g" ),
+ array( "if(x)/ x/g", "if(x)/ x/g" ),
+ array( "for(x;x;{}/ x/g);", "for(x;x;{}/x/g);" ),
+ array( "x;x;{}/ x/g", "x;x;{}/ x/g" ),
+ array( "x:{}/ x/g", "x:{}/ x/g" ),
+ array( "switch(x){case y?z:{}/ x/g:{}/ x/g;}", "switch(x){case y?z:{}/x/g:{}/ x/g;}" ),
+ array( "function x(){}/ x/g", "function x(){}/ x/g" ),
+ array( "+function x(){}/ x/g", "+function x(){}/x/g" ),
+
+ // Multiline quoted string
+ array( "var foo=\"\\\nblah\\\n\";", "var foo=\"\\\nblah\\\n\";" ),
+
+ // Multiline quoted string followed by string with spaces
+ array(
+ "var foo=\"\\\nblah\\\n\";\nvar baz = \" foo \";\n",
+ "var foo=\"\\\nblah\\\n\";var baz=\" foo \";"
+ ),
+
+ // URL in quoted string ( // is not a comment)
+ array(
+ "aNode.setAttribute('href','http://foo.bar.org/baz');",
+ "aNode.setAttribute('href','http://foo.bar.org/baz');"
+ ),
+
+ // URL in quoted string after multiline quoted string
+ array(
+ "var foo=\"\\\nblah\\\n\";\naNode.setAttribute('href','http://foo.bar.org/baz');",
+ "var foo=\"\\\nblah\\\n\";aNode.setAttribute('href','http://foo.bar.org/baz');"
+ ),
+
+ // Division vs. regex nastiness
+ array(
+ "alert( (10+10) / '/'.charCodeAt( 0 ) + '//' );",
+ "alert((10+10)/'/'.charCodeAt(0)+'//');"
+ ),
+ array( "if(1)/a /g.exec('Pa ss');", "if(1)/a /g.exec('Pa ss');" ),
+
+ // newline insertion after 1000 chars: break after the "++", not before
+ array( str_repeat( ';', 996 ) . "if(x++);", str_repeat( ';', 996 ) . "if(x++\n);" ),
+
+ // Unicode letter characters should pass through ok in identifiers (bug 31187)
+ array( "var KaŝSkatolVal = {}", 'var KaŝSkatolVal={}' ),
+
+ // Per spec unicode char escape values should work in identifiers,
+ // as long as it's a valid char. In future it might get normalized.
+ array( "var Ka\\u015dSkatolVal = {}", 'var Ka\\u015dSkatolVal={}' ),
+
+ // Some structures that might look invalid at first sight
+ array( "var a = 5.;", "var a=5.;" ),
+ array( "5.0.toString();", "5.0.toString();" ),
+ array( "5..toString();", "5..toString();" ),
+ array( "5...toString();", false ),
+ array( "5.\n.toString();", '5..toString();' ),
+ );
+ }
+
+ /**
+ * @dataProvider provideCases
+ * @covers JavaScriptMinifier::minify
+ */
+ public function testJavaScriptMinifierOutput( $code, $expectedOutput ) {
+ $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 );
+
+ $this->assertEquals(
+ $expectedOutput,
+ $minified,
+ "Minified output should be in the form expected."
+ );
+ }
+
+ public static function provideBug32548() {
+ return array(
+ array(
+ // This one gets interpreted all together by the prior code;
+ // no break at the 'E' happens.
+ '1.23456789E55',
+ ),
+ array(
+ // This one breaks under the bad code; splits between 'E' and '+'
+ '1.23456789E+5',
+ ),
+ array(
+ // This one breaks under the bad code; splits between 'E' and '-'
+ '1.23456789E-5',
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider provideBug32548
+ * @covers JavaScriptMinifier::minify
+ * @todo give this test a real name explaining what is being tested here
+ */
+ public function testBug32548Exponent( $num ) {
+ // Long line breaking was being incorrectly done between the base and
+ // exponent part of a number, causing a syntax error. The line should
+ // instead break at the start of the number.
+ $prefix = 'var longVarName' . str_repeat( '_', 973 ) . '=';
+ $suffix = ',shortVarName=0;';
+
+ $input = $prefix . $num . $suffix;
+ $expected = $prefix . "\n" . $num . $suffix;
+
+ $minified = JavaScriptMinifier::minify( $input );
+
+ $this->assertEquals( $expected, $minified, "Line breaks must not occur in middle of exponent" );
+ }
+}
diff --git a/tests/phpunit/includes/libs/MWMessagePackTest.php b/tests/phpunit/includes/libs/MWMessagePackTest.php
new file mode 100644
index 00000000..f80f78df
--- /dev/null
+++ b/tests/phpunit/includes/libs/MWMessagePackTest.php
@@ -0,0 +1,75 @@
+<?php
+/**
+ * PHP Unit tests for MWMessagePack
+ * @covers MWMessagePack
+ */
+class MWMessagePackTest extends MediaWikiTestCase {
+
+ /**
+ * Provides test cases for MWMessagePackTest::testMessagePack
+ *
+ * Returns an array of test cases. Each case is an array of (type, value,
+ * expected encoding as hex string). The expected values were generated
+ * using <https://github.com/msgpack/msgpack-php>, which includes a
+ * serialization function.
+ */
+ public static function providePacks() {
+ $tests = array(
+ array( 'nil', null, 'c0' ),
+ array( 'bool', true, 'c3' ),
+ array( 'bool', false, 'c2' ),
+ array( 'positive fixnum', 0, '00' ),
+ array( 'positive fixnum', 1, '01' ),
+ array( 'positive fixnum', 5, '05' ),
+ array( 'positive fixnum', 35, '23' ),
+ array( 'uint 8', 128, 'cc80' ),
+ array( 'uint 16', 1000, 'cd03e8' ),
+ array( 'uint 32', 100000, 'ce000186a0' ),
+ array( 'negative fixnum', -1, 'ff' ),
+ array( 'negative fixnum', -2, 'fe' ),
+ array( 'int 8', -128, 'd080' ),
+ array( 'int 8', -35, 'd0dd' ),
+ array( 'int 16', -1000, 'd1fc18' ),
+ array( 'int 32', -100000, 'd2fffe7960' ),
+ array( 'double', 0.1, 'cb3fb999999999999a' ),
+ array( 'double', 1.1, 'cb3ff199999999999a' ),
+ array( 'double', 123.456, 'cb405edd2f1a9fbe77' ),
+ array( 'fix raw', '', 'a0' ),
+ array( 'fix raw', 'foobar', 'a6666f6f626172' ),
+ array(
+ 'raw 16',
+ 'Lorem ipsum dolor sit amet amet.',
+ 'da00204c6f72656d20697073756d20646f6c6f722073697420616d657420616d65742e'
+ ),
+ array(
+ 'fix array',
+ array( 'abc', 'def', 'ghi' ),
+ '93a3616263a3646566a3676869'
+ ),
+ array(
+ 'fix map',
+ array( 'one' => 1, 'two' => 2 ),
+ '82a36f6e6501a374776f02'
+ ),
+ );
+
+ if ( PHP_INT_SIZE > 4 ) {
+ $tests[] = array( 'uint 64', 10000000000, 'cf00000002540be400' );
+ $tests[] = array( 'int 64', -10000000000, 'd3fffffffdabf41c00' );
+ $tests[] = array( 'int 64', -223372036854775807, 'd3fce66c50e2840001' );
+ $tests[] = array( 'int 64', -9223372036854775807, 'd38000000000000001' );
+ }
+
+ return $tests;
+ }
+
+ /**
+ * Verify that values are serialized correctly.
+ * @covers MWMessagePack::pack
+ * @dataProvider providePacks
+ */
+ public function testPack( $type, $value, $expected ) {
+ $actual = bin2hex( MWMessagePack::pack( $value ) );
+ $this->assertEquals( $expected, $actual, $type );
+ }
+}
diff --git a/tests/phpunit/includes/libs/ProcessCacheLRUTest.php b/tests/phpunit/includes/libs/ProcessCacheLRUTest.php
new file mode 100644
index 00000000..1a8a1e56
--- /dev/null
+++ b/tests/phpunit/includes/libs/ProcessCacheLRUTest.php
@@ -0,0 +1,237 @@
+<?php
+
+/**
+ * Test for ProcessCacheLRU class.
+ *
+ * Note that it uses the ProcessCacheLRUTestable class which extends some
+ * properties and methods visibility. That class is defined at the end of the
+ * file containing this class.
+ *
+ * @group Cache
+ */
+class ProcessCacheLRUTest extends MediaWikiTestCase {
+
+ /**
+ * 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' ) {
+ $this->assertAttributeEquals( array(), 'cache', $cache, $msg );
+ }
+
+ /**
+ * Helper to fill a cache object passed by reference
+ */
+ function fillCache( &$cache, $numEntries ) {
+ // Fill cache with three values
+ for ( $i = 1; $i <= $numEntries; $i++ ) {
+ $cache->set( "cache-key-$i", "prop-$i", "value-$i" );
+ }
+ }
+
+ /**
+ * Generates an array of what would be expected in cache for a given cache
+ * size and a number of entries filled in sequentially
+ */
+ function getExpectedCache( $cacheMaxEntries, $entryToFill ) {
+ $expected = array();
+
+ if ( $entryToFill === 0 ) {
+ # The cache is empty!
+ return array();
+ } elseif ( $entryToFill <= $cacheMaxEntries ) {
+ # Cache is not fully filled
+ $firstKey = 1;
+ } else {
+ # Cache overflowed
+ $firstKey = 1 + $entryToFill - $cacheMaxEntries;
+ }
+
+ $lastKey = $entryToFill;
+
+ for ( $i = $firstKey; $i <= $lastKey; $i++ ) {
+ $expected["cache-key-$i"] = array( "prop-$i" => "value-$i" );
+ }
+
+ return $expected;
+ }
+
+ /**
+ * Highlight diff between assertEquals and assertNotSame
+ */
+ public function testPhpUnitArrayEquality() {
+ $one = array( 'A' => 1, 'B' => 2 );
+ $two = array( 'B' => 2, 'A' => 1 );
+ $this->assertEquals( $one, $two ); // ==
+ $this->assertNotSame( $one, $two ); // ===
+ }
+
+ /**
+ * @dataProvider provideInvalidConstructorArg
+ * @expectedException UnexpectedValueException
+ */
+ public function testConstructorGivenInvalidValue( $maxSize ) {
+ new ProcessCacheLRUTestable( $maxSize );
+ }
+
+ /**
+ * Value which are forbidden by the constructor
+ */
+ public static function provideInvalidConstructorArg() {
+ return array(
+ array( null ),
+ array( array() ),
+ array( new stdClass() ),
+ array( 0 ),
+ array( '5' ),
+ array( -1 ),
+ );
+ }
+
+ public function testAddAndGetAKey() {
+ $oneCache = new ProcessCacheLRUTestable( 1 );
+ $this->assertCacheEmpty( $oneCache );
+
+ // First set just one value
+ $oneCache->set( 'cache-key', 'prop1', 'value1' );
+ $this->assertEquals( 1, $oneCache->getEntriesCount() );
+ $this->assertTrue( $oneCache->has( 'cache-key', 'prop1' ) );
+ $this->assertEquals( 'value1', $oneCache->get( 'cache-key', 'prop1' ) );
+ }
+
+ public function testDeleteOldKey() {
+ $oneCache = new ProcessCacheLRUTestable( 1 );
+ $this->assertCacheEmpty( $oneCache );
+
+ $oneCache->set( 'cache-key', 'prop1', 'value1' );
+ $oneCache->set( 'cache-key', 'prop1', 'value2' );
+ $this->assertEquals( 'value2', $oneCache->get( 'cache-key', 'prop1' ) );
+ }
+
+ /**
+ * This test that we properly overflow when filling a cache with
+ * a sequence of always different cache-keys. Meant to verify we correclty
+ * delete the older key.
+ *
+ * @dataProvider provideCacheFilling
+ * @param int $cacheMaxEntries Maximum entry the created cache will hold
+ * @param int $entryToFill Number of entries to insert in the created cache.
+ */
+ public function testFillingCache( $cacheMaxEntries, $entryToFill, $msg = '' ) {
+ $cache = new ProcessCacheLRUTestable( $cacheMaxEntries );
+ $this->fillCache( $cache, $entryToFill );
+
+ $this->assertSame(
+ $this->getExpectedCache( $cacheMaxEntries, $entryToFill ),
+ $cache->getCache(),
+ "Filling a $cacheMaxEntries entries cache with $entryToFill entries"
+ );
+ }
+
+ /**
+ * Provider for testFillingCache
+ */
+ public static function provideCacheFilling() {
+ // ($cacheMaxEntries, $entryToFill, $msg='')
+ return array(
+ array( 1, 0 ),
+ array( 1, 1 ),
+ array( 1, 2 ), # overflow
+ array( 5, 33 ), # overflow
+ );
+ }
+
+ /**
+ * Create a cache with only one remaining entry then update
+ * the first inserted entry. Should bump it to the top.
+ */
+ public function testReplaceExistingKeyShouldBumpEntryToTop() {
+ $maxEntries = 3;
+
+ $cache = new ProcessCacheLRUTestable( $maxEntries );
+ // Fill cache leaving just one remaining slot
+ $this->fillCache( $cache, $maxEntries - 1 );
+
+ // Set an existing cache key
+ $cache->set( "cache-key-1", "prop-1", "new-value-for-1" );
+
+ $this->assertSame(
+ array(
+ 'cache-key-2' => array( 'prop-2' => 'value-2' ),
+ 'cache-key-1' => array( 'prop-1' => 'new-value-for-1' ),
+ ),
+ $cache->getCache()
+ );
+ }
+
+ public function testRecentlyAccessedKeyStickIn() {
+ $cache = new ProcessCacheLRUTestable( 2 );
+ $cache->set( 'first', 'prop1', 'value1' );
+ $cache->set( 'second', 'prop2', 'value2' );
+
+ // Get first
+ $cache->get( 'first', 'prop1' );
+ // Cache a third value, should invalidate the least used one
+ $cache->set( 'third', 'prop3', 'value3' );
+
+ $this->assertFalse( $cache->has( 'second', 'prop2' ) );
+ }
+
+ /**
+ * This first create a full cache then update the value for the 2nd
+ * filled entry.
+ * Given a cache having 1,2,3 as key, updating 2 should bump 2 to
+ * the top of the queue with the new value: 1,3,2* (* = updated).
+ */
+ public function testReplaceExistingKeyInAFullCacheShouldBumpToTop() {
+ $maxEntries = 3;
+
+ $cache = new ProcessCacheLRUTestable( $maxEntries );
+ $this->fillCache( $cache, $maxEntries );
+
+ // Set an existing cache key
+ $cache->set( "cache-key-2", "prop-2", "new-value-for-2" );
+ $this->assertSame(
+ array(
+ 'cache-key-1' => array( 'prop-1' => 'value-1' ),
+ 'cache-key-3' => array( 'prop-3' => 'value-3' ),
+ 'cache-key-2' => array( 'prop-2' => 'new-value-for-2' ),
+ ),
+ $cache->getCache()
+ );
+ $this->assertEquals( 'new-value-for-2',
+ $cache->get( 'cache-key-2', 'prop-2' )
+ );
+ }
+
+ public function testBumpExistingKeyToTop() {
+ $cache = new ProcessCacheLRUTestable( 3 );
+ $this->fillCache( $cache, 3 );
+
+ // Set the very first cache key to a new value
+ $cache->set( "cache-key-1", "prop-1", "new value for 1" );
+ $this->assertEquals(
+ array(
+ 'cache-key-2' => array( 'prop-2' => 'value-2' ),
+ 'cache-key-3' => array( 'prop-3' => 'value-3' ),
+ 'cache-key-1' => array( 'prop-1' => 'new value for 1' ),
+ ),
+ $cache->getCache()
+ );
+ }
+}
+
+/**
+ * Overrides some ProcessCacheLRU methods and properties accessibility.
+ */
+class ProcessCacheLRUTestable extends ProcessCacheLRU {
+ public $cache = array();
+
+ public function getCache() {
+ return $this->cache;
+ }
+
+ public function getEntriesCount() {
+ return count( $this->cache );
+ }
+}
diff --git a/tests/phpunit/includes/libs/RunningStatTest.php b/tests/phpunit/includes/libs/RunningStatTest.php
new file mode 100644
index 00000000..dc5db82c
--- /dev/null
+++ b/tests/phpunit/includes/libs/RunningStatTest.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * PHP Unit tests for RunningStat class.
+ * @covers RunningStat
+ */
+class RunningStatTest extends MediaWikiTestCase {
+
+ public $points = array(
+ 49.7168, 74.3804, 7.0115, 96.5769, 34.9458,
+ 36.9947, 33.8926, 89.0774, 23.7745, 73.5154,
+ 86.1322, 53.2124, 16.2046, 73.5130, 10.4209,
+ 42.7299, 49.3330, 47.0215, 34.9950, 18.2914,
+ );
+
+ /**
+ * Verify that the statistical moments and extrema computed by RunningStat
+ * match expected values.
+ * @covers RunningStat::push
+ * @covers RunningStat::count
+ * @covers RunningStat::getMean
+ * @covers RunningStat::getVariance
+ * @covers RunningStat::getStdDev
+ */
+ public function testRunningStatAccuracy() {
+ $rstat = new RunningStat();
+ foreach( $this->points as $point ) {
+ $rstat->push( $point );
+ }
+
+ $mean = array_sum( $this->points ) / count( $this->points );
+ $variance = array_sum( array_map( function ( $x ) use ( $mean ) {
+ return pow( $mean - $x, 2 );
+ }, $this->points ) ) / ( count( $rstat ) - 1 );
+ $stddev = sqrt( $variance );
+
+ $this->assertEquals( count( $rstat ), count( $this->points ) );
+ $this->assertEquals( $rstat->min, min( $this->points ) );
+ $this->assertEquals( $rstat->max, max( $this->points ) );
+ $this->assertEquals( $rstat->getMean(), $mean );
+ $this->assertEquals( $rstat->getVariance(), $variance );
+ $this->assertEquals( $rstat->getStdDev(), $stddev );
+ }
+
+ /**
+ * When one RunningStat instance is merged into another, the state of the
+ * target RunningInstance should have the state that it would have had if
+ * all the data had been accumulated by it alone.
+ * @covers RunningStat::merge
+ * @covers RunningStat::count
+ */
+ public function testRunningStatMerge() {
+ $expected = new RunningStat();
+
+ foreach( $this->points as $point ) {
+ $expected->push( $point );
+ }
+
+ // Split the data into two sets
+ $sets = array_chunk( $this->points, floor( count( $this->points ) / 2 ) );
+
+ // Accumulate the first half into one RunningStat object
+ $first = new RunningStat();
+ foreach( $sets[0] as $point ) {
+ $first->push( $point );
+ }
+
+ // Accumulate the second half into another RunningStat object
+ $second = new RunningStat();
+ foreach( $sets[1] as $point ) {
+ $second->push( $point );
+ }
+
+ // Merge the second RunningStat object into the first
+ $first->merge( $second );
+
+ $this->assertEquals( count( $first ), count( $this->points ) );
+ $this->assertEquals( $first, $expected );
+ }
+}