Load GlobalFunctions.php to tests/phpunit/bootstrap.php
authorAmir Sarabadani <Ladsgroup@gmail.com>
Sat, 13 Jul 2019 20:50:28 +0000 (22:50 +0200)
committerAmir Sarabadani <ladsgroup@gmail.com>
Sat, 13 Jul 2019 23:28:07 +0000 (01:28 +0200)
That mostly enables testing global functions

Bug: T87781
Change-Id: Ib42c56a67926ebcdba53f4c6c54a5bff98cb77a3

62 files changed:
.phpcs.xml
tests/phpunit/bootstrap.php
tests/phpunit/includes/GlobalFunctions/wfAppendQueryTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfArrayPlus2dTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfEscapeShellArgTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfStringToBoolTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php [deleted file]
tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php [deleted file]
tests/phpunit/includes/PathRouterTest.php [deleted file]
tests/phpunit/includes/Rest/ResponseFactoryTest.php [deleted file]
tests/phpunit/includes/Revision/MainSlotRoleHandlerTest.php [deleted file]
tests/phpunit/includes/Revision/SlotRecordTest.php [deleted file]
tests/phpunit/includes/TitleArrayFromResultTest.php [deleted file]
tests/phpunit/includes/WikiReferenceTest.php [deleted file]
tests/phpunit/includes/debug/logger/monolog/CeeFormatterTest.php [deleted file]
tests/phpunit/includes/diff/DifferenceEngineSlotDiffRendererTest.php [deleted file]
tests/phpunit/includes/diff/SlotDiffRendererTest.php [deleted file]
tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php [deleted file]
tests/phpunit/includes/filerepo/file/ForeignDBFileTest.php [deleted file]
tests/phpunit/includes/htmlform/HTMLCheckMatrixTest.php [deleted file]
tests/phpunit/includes/json/FormatJsonTest.php [deleted file]
tests/phpunit/includes/media/JpegMetadataExtractorTest.php [deleted file]
tests/phpunit/includes/page/ArticleTest.php [deleted file]
tests/phpunit/includes/session/TokenTest.php [deleted file]
tests/phpunit/includes/shell/FirejailCommandTest.php [deleted file]
tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php [deleted file]
tests/phpunit/includes/utils/ZipDirectoryReaderTest.php [deleted file]
tests/phpunit/unit/includes/GlobalFunctions/wfAppendQueryTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfArrayPlus2dTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfAssembleUrlTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfBaseNameTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfEscapeShellArgTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfGetCallerTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfShorthandToIntegerTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfStringToBoolTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfTimestampTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/GlobalFunctions/wfUrlencodeTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/PathRouterTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/Rest/ResponseFactoryTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/Revision/MainSlotRoleHandlerTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/Revision/SlotRecordTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/TitleArrayFromResultTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/WikiReferenceTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/debug/logger/monolog/CeeFormatterTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/diff/DifferenceEngineSlotDiffRendererTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/diff/SlotDiffRendererTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/filerepo/FileBackendDBRepoWrapperTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/filerepo/file/ForeignDBFileTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/htmlform/HTMLCheckMatrixTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/json/FormatJsonTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/media/JpegMetadataExtractorTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/page/ArticleTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/session/TokenTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/shell/FirejailCommandTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/site/MediaWikiPageNameNormalizerTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/utils/ZipDirectoryReaderTest.php [new file with mode: 0644]

index 8f3bd8c..9f11ebc 100644 (file)
                <exclude-pattern>*/maintenance/storage/recompressTracked\.php</exclude-pattern>
                <exclude-pattern>*/maintenance/storage/trackBlobs\.php</exclude-pattern>
                <!-- Skip violations in some tests for now -->
+               <exclude-pattern>*/tests/phpunit/unit/includes/GlobalFunctions/*\.php</exclude-pattern>
                <exclude-pattern>*/tests/phpunit/includes/GlobalFunctions/*\.php</exclude-pattern>
                <exclude-pattern>*/tests/phpunit/maintenance/*\.php</exclude-pattern>
                <exclude-pattern>*/tests/phpunit/integration/includes/GlobalFunctions/*\.php</exclude-pattern>
index a7f0c09..4400475 100644 (file)
@@ -73,6 +73,7 @@ wfRequireOnceInGlobalScope( "$IP/includes/AutoLoader.php" );
 wfRequireOnceInGlobalScope( "$IP/tests/common/TestsAutoLoader.php" );
 wfRequireOnceInGlobalScope( "$IP/includes/Defines.php" );
 wfRequireOnceInGlobalScope( "$IP/includes/DefaultSettings.php" );
+wfRequireOnceInGlobalScope( "$IP/includes/GlobalFunctions.php" );
 
 // Load extensions/skins present in filesystem so that classes can be discovered.
 $directoryToJsonMap = [
diff --git a/tests/phpunit/includes/GlobalFunctions/wfAppendQueryTest.php b/tests/phpunit/includes/GlobalFunctions/wfAppendQueryTest.php
deleted file mode 100644 (file)
index bb71610..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfAppendQuery
- */
-class WfAppendQueryTest extends MediaWikiTestCase {
-       /**
-        * @dataProvider provideAppendQuery
-        */
-       public function testAppendQuery( $url, $query, $expected, $message = null ) {
-               $this->assertEquals( $expected, wfAppendQuery( $url, $query ), $message );
-       }
-
-       public static function provideAppendQuery() {
-               return [
-                       [
-                               'http://www.example.org/index.php',
-                               '',
-                               'http://www.example.org/index.php',
-                               'No query'
-                       ],
-                       [
-                               'http://www.example.org/index.php',
-                               [ 'foo' => 'bar' ],
-                               'http://www.example.org/index.php?foo=bar',
-                               'Set query array'
-                       ],
-                       [
-                               'http://www.example.org/index.php?foz=baz',
-                               'foo=bar',
-                               'http://www.example.org/index.php?foz=baz&foo=bar',
-                               'Set query string'
-                       ],
-                       [
-                               'http://www.example.org/index.php?foo=bar',
-                               '',
-                               'http://www.example.org/index.php?foo=bar',
-                               'Empty string with query'
-                       ],
-                       [
-                               'http://www.example.org/index.php?foo=bar',
-                               [ 'baz' => 'quux' ],
-                               'http://www.example.org/index.php?foo=bar&baz=quux',
-                               'Add query array'
-                       ],
-                       [
-                               'http://www.example.org/index.php?foo=bar',
-                               'baz=quux',
-                               'http://www.example.org/index.php?foo=bar&baz=quux',
-                               'Add query string'
-                       ],
-                       [
-                               'http://www.example.org/index.php?foo=bar',
-                               [ 'baz' => 'quux', 'foo' => 'baz' ],
-                               'http://www.example.org/index.php?foo=bar&baz=quux&foo=baz',
-                               'Modify query array'
-                       ],
-                       [
-                               'http://www.example.org/index.php?foo=bar',
-                               'baz=quux&foo=baz',
-                               'http://www.example.org/index.php?foo=bar&baz=quux&foo=baz',
-                               'Modify query string'
-                       ],
-                       [
-                               'http://www.example.org/index.php#baz',
-                               'foo=bar',
-                               'http://www.example.org/index.php?foo=bar#baz',
-                               'URL with fragment'
-                       ],
-                       [
-                               'http://www.example.org/index.php?foo=bar#baz',
-                               'quux=blah',
-                               'http://www.example.org/index.php?foo=bar&quux=blah#baz',
-                               'URL with query string and fragment'
-                       ]
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfArrayPlus2dTest.php b/tests/phpunit/includes/GlobalFunctions/wfArrayPlus2dTest.php
deleted file mode 100644 (file)
index 65b56ef..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-<?php
-/**
- * @group GlobalFunctions
- * @covers ::wfArrayPlus2d
- */
-class WfArrayPlus2dTest extends MediaWikiTestCase {
-       /**
-        * @dataProvider provideArrays
-        */
-       public function testWfArrayPlus2d( $baseArray, $newValues, $expected, $testName ) {
-               $this->assertEquals(
-                       $expected,
-                       wfArrayPlus2d( $baseArray, $newValues ),
-                       $testName
-               );
-       }
-
-       /**
-        * Provider for testing wfArrayPlus2d
-        *
-        * @return array
-        */
-       public static function provideArrays() {
-               return [
-                       // target array, new values array, expected result
-                       [
-                               [ 0 => '1dArray' ],
-                               [ 1 => '1dArray' ],
-                               [ 0 => '1dArray', 1 => '1dArray' ],
-                               "Test simple union of two arrays with different keys",
-                       ],
-                       [
-                               [
-                                       0 => [ 0 => '2dArray' ],
-                               ],
-                               [
-                                       0 => [ 1 => '2dArray' ],
-                               ],
-                               [
-                                       0 => [ 0 => '2dArray', 1 => '2dArray' ],
-                               ],
-                               "Test union of 2d arrays with different keys in the value array",
-                       ],
-                       [
-                               [
-                                       0 => [ 0 => '2dArray' ],
-                               ],
-                               [
-                                       0 => [ 0 => '1dArray' ],
-                               ],
-                               [
-                                       0 => [ 0 => '2dArray' ],
-                               ],
-                               "Test union of 2d arrays with same keys in the value array",
-                       ],
-                       [
-                               [
-                                       0 => [ 0 => [ 0 => '3dArray' ] ],
-                               ],
-                               [
-                                       0 => [ 0 => [ 1 => '2dArray' ] ],
-                               ],
-                               [
-                                       0 => [ 0 => [ 0 => '3dArray' ] ],
-                               ],
-                               "Test union of 3d array with different keys",
-                       ],
-                       [
-                               [
-                                       0 => [ 0 => [ 0 => '3dArray' ] ],
-                               ],
-                               [
-                                       0 => [ 1 => [ 0 => '2dArray' ] ],
-                               ],
-                               [
-                                       0 => [ 0 => [ 0 => '3dArray' ], 1 => [ 0 => '2dArray' ] ],
-                               ],
-                               "Test union of 3d array with different keys in the value array",
-                       ],
-                       [
-                               [
-                                       0 => [ 0 => [ 0 => '3dArray' ] ],
-                               ],
-                               [
-                                       0 => [ 0 => [ 0 => '2dArray' ] ],
-                               ],
-                               [
-                                       0 => [ 0 => [ 0 => '3dArray' ] ],
-                               ],
-                               "Test union of 3d array with same keys in the value array",
-                       ],
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php b/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php
deleted file mode 100644 (file)
index 7ddad36..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-<?php
-/**
- * @group GlobalFunctions
- * @covers ::wfAssembleUrl
- */
-class WfAssembleUrlTest extends MediaWikiTestCase {
-       /**
-        * @dataProvider provideURLParts
-        */
-       public function testWfAssembleUrl( $parts, $output ) {
-               $partsDump = print_r( $parts, true );
-               $this->assertEquals(
-                       $output,
-                       wfAssembleUrl( $parts ),
-                       "Testing $partsDump assembles to $output"
-               );
-       }
-
-       /**
-        * Provider of URL parts for testing wfAssembleUrl()
-        *
-        * @return array
-        */
-       public static function provideURLParts() {
-               $schemes = [
-                       '' => [],
-                       '//' => [
-                               'delimiter' => '//',
-                       ],
-                       'http://' => [
-                               'scheme' => 'http',
-                               'delimiter' => '://',
-                       ],
-               ];
-
-               $hosts = [
-                       '' => [],
-                       'example.com' => [
-                               'host' => 'example.com',
-                       ],
-                       'example.com:123' => [
-                               'host' => 'example.com',
-                               'port' => 123,
-                       ],
-                       'id@example.com' => [
-                               'user' => 'id',
-                               'host' => 'example.com',
-                       ],
-                       'id@example.com:123' => [
-                               'user' => 'id',
-                               'host' => 'example.com',
-                               'port' => 123,
-                       ],
-                       'id:key@example.com' => [
-                               'user' => 'id',
-                               'pass' => 'key',
-                               'host' => 'example.com',
-                       ],
-                       'id:key@example.com:123' => [
-                               'user' => 'id',
-                               'pass' => 'key',
-                               'host' => 'example.com',
-                               'port' => 123,
-                       ],
-               ];
-
-               $cases = [];
-               foreach ( $schemes as $scheme => $schemeParts ) {
-                       foreach ( $hosts as $host => $hostParts ) {
-                               foreach ( [ '', '/path' ] as $path ) {
-                                       foreach ( [ '', 'query' ] as $query ) {
-                                               foreach ( [ '', 'fragment' ] as $fragment ) {
-                                                       $parts = array_merge(
-                                                               $schemeParts,
-                                                               $hostParts
-                                                       );
-                                                       $url = $scheme .
-                                                               $host .
-                                                               $path;
-
-                                                       if ( $path ) {
-                                                               $parts['path'] = $path;
-                                                       }
-                                                       if ( $query ) {
-                                                               $parts['query'] = $query;
-                                                               $url .= '?' . $query;
-                                                       }
-                                                       if ( $fragment ) {
-                                                               $parts['fragment'] = $fragment;
-                                                               $url .= '#' . $fragment;
-                                                       }
-
-                                                       $cases[] = [
-                                                               $parts,
-                                                               $url,
-                                                       ];
-                                               }
-                                       }
-                               }
-                       }
-               }
-
-               $complexURL = 'http://id:key@example.org:321' .
-                       '/over/there?name=ferret&foo=bar#nose';
-               $cases[] = [
-                       wfParseUrl( $complexURL ),
-                       $complexURL,
-               ];
-
-               return $cases;
-       }
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php b/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php
deleted file mode 100644 (file)
index 78e09e6..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-/**
- * @group GlobalFunctions
- * @covers ::wfBaseName
- */
-class WfBaseNameTest extends MediaWikiTestCase {
-       /**
-        * @dataProvider providePaths
-        */
-       public function testBaseName( $fullpath, $basename ) {
-               $this->assertEquals( $basename, wfBaseName( $fullpath ),
-                       "wfBaseName('$fullpath') => '$basename'" );
-       }
-
-       public static function providePaths() {
-               return [
-                       [ '', '' ],
-                       [ '/', '' ],
-                       [ '\\', '' ],
-                       [ '//', '' ],
-                       [ '\\\\', '' ],
-                       [ 'a', 'a' ],
-                       [ 'aaaa', 'aaaa' ],
-                       [ '/a', 'a' ],
-                       [ '\\a', 'a' ],
-                       [ '/aaaa', 'aaaa' ],
-                       [ '\\aaaa', 'aaaa' ],
-                       [ '/aaaa/', 'aaaa' ],
-                       [ '\\aaaa\\', 'aaaa' ],
-                       [ '\\aaaa\\', 'aaaa' ],
-                       [
-                               '/mnt/upload3/wikipedia/en/thumb/8/8b/'
-                                       . 'Zork_Grand_Inquisitor_box_cover.jpg/93px-Zork_Grand_Inquisitor_box_cover.jpg',
-                               '93px-Zork_Grand_Inquisitor_box_cover.jpg'
-                       ],
-                       [ 'C:\\Progra~1\\Wikime~1\\Wikipe~1\\VIEWER.EXE', 'VIEWER.EXE' ],
-                       [ 'Östergötland_coat_of_arms.png', 'Östergötland_coat_of_arms.png' ],
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfEscapeShellArgTest.php b/tests/phpunit/includes/GlobalFunctions/wfEscapeShellArgTest.php
deleted file mode 100644 (file)
index 7402054..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfEscapeShellArg
- */
-class WfEscapeShellArgTest extends MediaWikiTestCase {
-       public function testSingleInput() {
-               if ( wfIsWindows() ) {
-                       $expected = '"blah"';
-               } else {
-                       $expected = "'blah'";
-               }
-
-               $actual = wfEscapeShellArg( 'blah' );
-
-               $this->assertEquals( $expected, $actual );
-       }
-
-       public function testMultipleArgs() {
-               if ( wfIsWindows() ) {
-                       $expected = '"foo" "bar" "baz"';
-               } else {
-                       $expected = "'foo' 'bar' 'baz'";
-               }
-
-               $actual = wfEscapeShellArg( 'foo', 'bar', 'baz' );
-
-               $this->assertEquals( $expected, $actual );
-       }
-
-       public function testMultipleArgsAsArray() {
-               if ( wfIsWindows() ) {
-                       $expected = '"foo" "bar" "baz"';
-               } else {
-                       $expected = "'foo' 'bar' 'baz'";
-               }
-
-               $actual = wfEscapeShellArg( [ 'foo', 'bar', 'baz' ] );
-
-               $this->assertEquals( $expected, $actual );
-       }
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php b/tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php
deleted file mode 100644 (file)
index 8a7bfa5..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfGetCaller
- */
-class WfGetCallerTest extends MediaWikiTestCase {
-       public function testZero() {
-               $this->assertEquals( 'WfGetCallerTest->testZero', wfGetCaller( 1 ) );
-       }
-
-       function callerOne() {
-               return wfGetCaller();
-       }
-
-       public function testOne() {
-               $this->assertEquals( 'WfGetCallerTest->testOne', self::callerOne() );
-       }
-
-       static function intermediateFunction( $level = 2, $n = 0 ) {
-               if ( $n > 0 ) {
-                       return self::intermediateFunction( $level, $n - 1 );
-               }
-
-               return wfGetCaller( $level );
-       }
-
-       public function testTwo() {
-               $this->assertEquals( 'WfGetCallerTest->testTwo', self::intermediateFunction() );
-       }
-
-       public function testN() {
-               $this->assertEquals( 'WfGetCallerTest->testN', self::intermediateFunction( 2, 0 ) );
-               $this->assertEquals(
-                       'WfGetCallerTest::intermediateFunction',
-                       self::intermediateFunction( 1, 0 )
-               );
-
-               for ( $i = 0; $i < 10; $i++ ) {
-                       $this->assertEquals(
-                               'WfGetCallerTest::intermediateFunction',
-                               self::intermediateFunction( $i + 1, $i )
-                       );
-               }
-       }
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php b/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php
deleted file mode 100644 (file)
index eae5588..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfRemoveDotSegments
- */
-class WfRemoveDotSegmentsTest extends MediaWikiTestCase {
-       /**
-        * @dataProvider providePaths
-        */
-       public function testWfRemoveDotSegments( $inputPath, $outputPath ) {
-               $this->assertEquals(
-                       $outputPath,
-                       wfRemoveDotSegments( $inputPath ),
-                       "Testing $inputPath expands to $outputPath"
-               );
-       }
-
-       /**
-        * Provider of URL paths for testing wfRemoveDotSegments()
-        *
-        * @return array
-        */
-       public static function providePaths() {
-               return [
-                       [ '/a/b/c/./../../g', '/a/g' ],
-                       [ 'mid/content=5/../6', 'mid/6' ],
-                       [ '/a//../b', '/a/b' ],
-                       [ '/.../a', '/.../a' ],
-                       [ '.../a', '.../a' ],
-                       [ '', '' ],
-                       [ '/', '/' ],
-                       [ '//', '//' ],
-                       [ '.', '' ],
-                       [ '..', '' ],
-                       [ '...', '...' ],
-                       [ '/.', '/' ],
-                       [ '/..', '/' ],
-                       [ './', '' ],
-                       [ '../', '' ],
-                       [ './a', 'a' ],
-                       [ '../a', 'a' ],
-                       [ '../../a', 'a' ],
-                       [ '.././a', 'a' ],
-                       [ './../a', 'a' ],
-                       [ '././a', 'a' ],
-                       [ '../../', '' ],
-                       [ '.././', '' ],
-                       [ './../', '' ],
-                       [ '././', '' ],
-                       [ '../..', '' ],
-                       [ '../.', '' ],
-                       [ './..', '' ],
-                       [ './.', '' ],
-                       [ '/../../a', '/a' ],
-                       [ '/.././a', '/a' ],
-                       [ '/./../a', '/a' ],
-                       [ '/././a', '/a' ],
-                       [ '/../../', '/' ],
-                       [ '/.././', '/' ],
-                       [ '/./../', '/' ],
-                       [ '/././', '/' ],
-                       [ '/../..', '/' ],
-                       [ '/../.', '/' ],
-                       [ '/./..', '/' ],
-                       [ '/./.', '/' ],
-                       [ 'b/../../a', '/a' ],
-                       [ 'b/.././a', '/a' ],
-                       [ 'b/./../a', '/a' ],
-                       [ 'b/././a', 'b/a' ],
-                       [ 'b/../../', '/' ],
-                       [ 'b/.././', '/' ],
-                       [ 'b/./../', '/' ],
-                       [ 'b/././', 'b/' ],
-                       [ 'b/../..', '/' ],
-                       [ 'b/../.', '/' ],
-                       [ 'b/./..', '/' ],
-                       [ 'b/./.', 'b/' ],
-                       [ '/b/../../a', '/a' ],
-                       [ '/b/.././a', '/a' ],
-                       [ '/b/./../a', '/a' ],
-                       [ '/b/././a', '/b/a' ],
-                       [ '/b/../../', '/' ],
-                       [ '/b/.././', '/' ],
-                       [ '/b/./../', '/' ],
-                       [ '/b/././', '/b/' ],
-                       [ '/b/../..', '/' ],
-                       [ '/b/../.', '/' ],
-                       [ '/b/./..', '/' ],
-                       [ '/b/./.', '/b/' ],
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php b/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php
deleted file mode 100644 (file)
index 40b2e63..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfShorthandToInteger
- */
-class WfShorthandToIntegerTest extends MediaWikiTestCase {
-       /**
-        * @dataProvider provideABunchOfShorthands
-        */
-       public function testWfShorthandToInteger( $input, $output, $description ) {
-               $this->assertEquals(
-                       wfShorthandToInteger( $input ),
-                       $output,
-                       $description
-               );
-       }
-
-       public static function provideABunchOfShorthands() {
-               return [
-                       [ '', -1, 'Empty string' ],
-                       [ '     ', -1, 'String of spaces' ],
-                       [ '1G', 1024 * 1024 * 1024, 'One gig uppercased' ],
-                       [ '1g', 1024 * 1024 * 1024, 'One gig lowercased' ],
-                       [ '1M', 1024 * 1024, 'One meg uppercased' ],
-                       [ '1m', 1024 * 1024, 'One meg lowercased' ],
-                       [ '1K', 1024, 'One kb uppercased' ],
-                       [ '1k', 1024, 'One kb lowercased' ],
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfStringToBoolTest.php b/tests/phpunit/includes/GlobalFunctions/wfStringToBoolTest.php
deleted file mode 100644 (file)
index 7f56b60..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfStringToBool
- */
-class WfStringToBoolTest extends MediaWikiTestCase {
-
-       public function getTestCases() {
-               return [
-                       [ 'true', true ],
-                       [ 'on', true ],
-                       [ 'yes', true ],
-                       [ 'TRUE', true ],
-                       [ 'YeS', true ],
-                       [ 'On', true ],
-                       [ '1', true ],
-                       [ '+1', true ],
-                       [ '01', true ],
-                       [ '-001', true ],
-                       [ '  1', true ],
-                       [ '-1  ', true ],
-                       [ '', false ],
-                       [ '0', false ],
-                       [ 'false', false ],
-                       [ 'NO', false ],
-                       [ 'NOT', false ],
-                       [ 'never', false ],
-                       [ '!&', false ],
-                       [ '-0', false ],
-                       [ '+0', false ],
-                       [ 'forget about it', false ],
-                       [ ' on', false ],
-                       [ 'true ', false ],
-               ];
-       }
-
-       /**
-        * @dataProvider getTestCases
-        * @param string $str
-        * @param bool $bool
-        */
-       public function testStr2Bool( $str, $bool ) {
-               if ( $bool ) {
-                       $this->assertTrue( wfStringToBool( $str ) );
-               } else {
-                       $this->assertFalse( wfStringToBool( $str ) );
-               }
-       }
-
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php
deleted file mode 100644 (file)
index a70f136..0000000
+++ /dev/null
@@ -1,194 +0,0 @@
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfTimestamp
- */
-class WfTimestampTest extends MediaWikiTestCase {
-       /**
-        * @dataProvider provideNormalTimestamps
-        */
-       public function testNormalTimestamps( $input, $format, $output, $desc ) {
-               $this->assertEquals( $output, wfTimestamp( $format, $input ), $desc );
-       }
-
-       public static function provideNormalTimestamps() {
-               $t = gmmktime( 12, 34, 56, 1, 15, 2001 );
-
-               return [
-                       // TS_UNIX
-                       [ $t, TS_MW, '20010115123456', 'TS_UNIX to TS_MW' ],
-                       [ -30281104, TS_MW, '19690115123456', 'Negative TS_UNIX to TS_MW' ],
-                       [ $t, TS_UNIX, 979562096, 'TS_UNIX to TS_UNIX' ],
-                       [ $t, TS_DB, '2001-01-15 12:34:56', 'TS_UNIX to TS_DB' ],
-                       [ $t + 0.01, TS_MW, '20010115123456', 'TS_UNIX float to TS_MW' ],
-
-                       [ $t, TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_ISO_8601_BASIC to TS_DB' ],
-
-                       // TS_MW
-                       [ '20010115123456', TS_MW, '20010115123456', 'TS_MW to TS_MW' ],
-                       [ '20010115123456', TS_UNIX, 979562096, 'TS_MW to TS_UNIX' ],
-                       [ '20010115123456', TS_DB, '2001-01-15 12:34:56', 'TS_MW to TS_DB' ],
-                       [ '20010115123456', TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_MW to TS_ISO_8601_BASIC' ],
-
-                       // TS_DB
-                       [ '2001-01-15 12:34:56', TS_MW, '20010115123456', 'TS_DB to TS_MW' ],
-                       [ '2001-01-15 12:34:56', TS_UNIX, 979562096, 'TS_DB to TS_UNIX' ],
-                       [ '2001-01-15 12:34:56', TS_DB, '2001-01-15 12:34:56', 'TS_DB to TS_DB' ],
-                       [
-                               '2001-01-15 12:34:56',
-                               TS_ISO_8601_BASIC,
-                               '20010115T123456Z',
-                               'TS_DB to TS_ISO_8601_BASIC'
-                       ],
-
-                       # rfc2822 section 3.3
-                       [ '20010115123456', TS_RFC2822, 'Mon, 15 Jan 2001 12:34:56 GMT', 'TS_MW to TS_RFC2822' ],
-                       [ 'Mon, 15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ],
-                       [
-                               ' Mon, 15 Jan 2001 12:34:56 GMT',
-                               TS_MW,
-                               '20010115123456',
-                               'TS_RFC2822 with leading space to TS_MW'
-                       ],
-                       [
-                               '15 Jan 2001 12:34:56 GMT',
-                               TS_MW,
-                               '20010115123456',
-                               'TS_RFC2822 without optional day-of-week to TS_MW'
-                       ],
-
-                       # FWS = ([*WSP CRLF] 1*WSP) / obs-FWS ; Folding white space
-                       # obs-FWS = 1*WSP *(CRLF 1*WSP) ; Section 4.2
-                       [ 'Mon, 15         Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ],
-
-                       # WSP = SP / HTAB ; rfc2234
-                       [
-                               "Mon, 15 Jan\x092001 12:34:56 GMT",
-                               TS_MW,
-                               '20010115123456',
-                               'TS_RFC2822 with HTAB to TS_MW'
-                       ],
-                       [
-                               "Mon, 15 Jan\x09 \x09  2001 12:34:56 GMT",
-                               TS_MW,
-                               '20010115123456',
-                               'TS_RFC2822 with HTAB and SP to TS_MW'
-                       ],
-                       [
-                               'Sun, 6 Nov 94 08:49:37 GMT',
-                               TS_MW,
-                               '19941106084937',
-                               'TS_RFC2822 with obsolete year to TS_MW'
-                       ],
-               ];
-       }
-
-       /**
-        * This test checks wfTimestamp() with values outside.
-        * It needs PHP 64 bits or PHP > 5.1.
-        * See r74778 and T27451
-        * @dataProvider provideOldTimestamps
-        */
-       public function testOldTimestamps( $input, $outputType, $output, $message ) {
-               $timestamp = wfTimestamp( $outputType, $input );
-               if ( substr( $output, 0, 1 ) === '/' ) {
-                       // T66946: Day of the week calculations for very old
-                       // timestamps varies from system to system.
-                       $this->assertRegExp( $output, $timestamp, $message );
-               } else {
-                       $this->assertEquals( $output, $timestamp, $message );
-               }
-       }
-
-       public static function provideOldTimestamps() {
-               return [
-                       [
-                               '19011213204554',
-                               TS_RFC2822,
-                               'Fri, 13 Dec 1901 20:45:54 GMT',
-                               'Earliest time according to PHP documentation'
-                       ],
-                       [ '20380119031407', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:07 GMT', 'Latest 32 bit time' ],
-                       [ '19011213204552', TS_UNIX, '-2147483648', 'Earliest 32 bit unix time' ],
-                       [ '20380119031407', TS_UNIX, '2147483647', 'Latest 32 bit unix time' ],
-                       [ '19011213204552', TS_RFC2822, 'Fri, 13 Dec 1901 20:45:52 GMT', 'Earliest 32 bit time' ],
-                       [
-                               '19011213204551',
-                               TS_RFC2822,
-                               'Fri, 13 Dec 1901 20:45:51 GMT', 'Earliest 32 bit time - 1'
-                       ],
-                       [ '20380119031408', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:08 GMT', 'Latest 32 bit time + 1' ],
-                       [ '19011212000000', TS_MW, '19011212000000', 'Convert to itself r74778#c10645' ],
-                       [ '19011213204551', TS_UNIX, '-2147483649', 'Earliest 32 bit unix time - 1' ],
-                       [ '20380119031408', TS_UNIX, '2147483648', 'Latest 32 bit unix time + 1' ],
-                       [ '-2147483649', TS_MW, '19011213204551', '1901 negative unix time to MediaWiki' ],
-                       [ '-5331871504', TS_MW, '18010115123456', '1801 negative unix time to MediaWiki' ],
-                       [
-                               '0117-08-09 12:34:56',
-                               TS_RFC2822,
-                               '/, 09 Aug 0117 12:34:56 GMT$/',
-                               'Death of Roman Emperor [[Trajan]]'
-                       ],
-
-                       /* @todo FIXME: 00 to 101 years are taken as being in [1970-2069] */
-                       [ '-58979923200', TS_RFC2822, '/, 01 Jan 0101 00:00:00 GMT$/', '1/1/101' ],
-                       [ '-62135596800', TS_RFC2822, 'Mon, 01 Jan 0001 00:00:00 GMT', 'Year 1' ],
-
-                       /* It is not clear if we should generate a year 0 or not
-                        * We are completely off RFC2822 requirement of year being
-                        * 1900 or later.
-                        */
-                       [
-                               '-62142076800',
-                               TS_RFC2822,
-                               'Wed, 18 Oct 0000 00:00:00 GMT',
-                               'ISO 8601:2004 [[year 0]], also called [[1 BC]]'
-                       ],
-               ];
-       }
-
-       /**
-        * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
-        * @dataProvider provideHttpDates
-        */
-       public function testHttpDate( $input, $output, $desc ) {
-               $this->assertEquals( $output, wfTimestamp( TS_MW, $input ), $desc );
-       }
-
-       public static function provideHttpDates() {
-               return [
-                       [ 'Sun, 06 Nov 1994 08:49:37 GMT', '19941106084937', 'RFC 822 date' ],
-                       [ 'Sunday, 06-Nov-94 08:49:37 GMT', '19941106084937', 'RFC 850 date' ],
-                       [ 'Sun Nov  6 08:49:37 1994', '19941106084937', "ANSI C's asctime() format" ],
-                       // See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html and r77171
-                       [
-                               'Mon, 22 Nov 2010 14:12:42 GMT; length=52626',
-                               '20101122141242',
-                               'Netscape extension to HTTP/1.0'
-                       ],
-               ];
-       }
-
-       /**
-        * There are a number of assumptions in our codebase where wfTimestamp()
-        * should give the current date but it is not given a 0 there. See r71751 CR
-        */
-       public function testTimestampParameter() {
-               $now = wfTimestamp( TS_UNIX );
-               // We check that wfTimestamp doesn't return false (error) and use a LessThan assert
-               // for the cases where the test is run in a second boundary.
-
-               $zero = wfTimestamp( TS_UNIX, 0 );
-               $this->assertNotEquals( false, $zero );
-               $this->assertLessThan( 5, $zero - $now );
-
-               $empty = wfTimestamp( TS_UNIX, '' );
-               $this->assertNotEquals( false, $empty );
-               $this->assertLessThan( 5, $empty - $now );
-
-               $null = wfTimestamp( TS_UNIX, null );
-               $this->assertNotEquals( false, $null );
-               $this->assertLessThan( 5, $null - $now );
-       }
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php b/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php
deleted file mode 100644 (file)
index f9735c1..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-<?php
-
-/**
- * The function only need a string parameter and might react to IIS7.0
- *
- * @group GlobalFunctions
- * @covers ::wfUrlencode
- */
-class WfUrlencodeTest extends MediaWikiTestCase {
-       # ### TESTS ##############################################################
-
-       /**
-        * @dataProvider provideURLS
-        */
-       public function testEncodingUrlWith( $input, $expected ) {
-               $this->verifyEncodingFor( 'Apache', $input, $expected );
-       }
-
-       /**
-        * @dataProvider provideURLS
-        */
-       public function testEncodingUrlWithMicrosoftIis7( $input, $expected ) {
-               $this->verifyEncodingFor( 'Microsoft-IIS/7', $input, $expected );
-       }
-
-       # ### HELPERS #############################################################
-
-       /**
-        * Internal helper that actually run the test.
-        * Called by the public methods testEncodingUrlWith...()
-        */
-       private function verifyEncodingFor( $server, $input, $expectations ) {
-               $expected = $this->extractExpect( $server, $expectations );
-
-               // save up global
-               $old = $_SERVER['SERVER_SOFTWARE'] ?? null;
-               $_SERVER['SERVER_SOFTWARE'] = $server;
-               wfUrlencode( null );
-
-               // do the requested test
-               $this->assertEquals(
-                       $expected,
-                       wfUrlencode( $input ),
-                       "Encoding '$input' for server '$server' should be '$expected'"
-               );
-
-               // restore global
-               if ( $old === null ) {
-                       unset( $_SERVER['SERVER_SOFTWARE'] );
-               } else {
-                       $_SERVER['SERVER_SOFTWARE'] = $old;
-               }
-               wfUrlencode( null );
-       }
-
-       /**
-        * Interprets the provider array. Return expected value depending
-        * the HTTP server name.
-        */
-       private function extractExpect( $server, $expectations ) {
-               if ( is_string( $expectations ) ) {
-                       return $expectations;
-               } elseif ( is_array( $expectations ) ) {
-                       if ( !array_key_exists( $server, $expectations ) ) {
-                               throw new MWException( __METHOD__ . " expectation does not have any "
-                                       . "value for server name $server. Check the provider array.\n" );
-                       } else {
-                               return $expectations[$server];
-                       }
-               } else {
-                       throw new MWException( __METHOD__ . " given invalid expectation for "
-                               . "'$server'. Should be a string or an array [ <http server name> => <string> ].\n" );
-               }
-       }
-
-       # ### PROVIDERS ###########################################################
-
-       /**
-        * Format is either:
-        *   [ 'input', 'expected' ];
-        * Or:
-        *   [ 'input',
-        *       [ 'Apache', 'expected' ],
-        *       [ 'Microsoft-IIS/7', 'expected' ],
-        *   ],
-        * If you want to add other HTTP server name, you will have to add a new
-        * testing method much like the testEncodingUrlWith() method above.
-        */
-       public static function provideURLS() {
-               return [
-                       # ## RFC 1738 chars
-                       // + is not safe
-                       [ '+', '%2B' ],
-                       // & and = not safe in queries
-                       [ '&', '%26' ],
-                       [ '=', '%3D' ],
-
-                       [ ':', [
-                               'Apache' => ':',
-                               'Microsoft-IIS/7' => '%3A',
-                       ] ],
-
-                       // remaining chars do not need encoding
-                       [
-                               ';@$-_.!*',
-                               ';@$-_.!*',
-                       ],
-
-                       # ## Other tests
-                       // slash remain unchanged. %2F seems to break things
-                       [ '/', '/' ],
-                       // T105265
-                       [ '~', '~' ],
-
-                       // Other 'funnies' chars
-                       [ '[]', '%5B%5D' ],
-                       [ '<>', '%3C%3E' ],
-
-                       // Apostrophe is encoded
-                       [ '\'', '%27' ],
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/PathRouterTest.php b/tests/phpunit/includes/PathRouterTest.php
deleted file mode 100644 (file)
index d891675..0000000
+++ /dev/null
@@ -1,325 +0,0 @@
-<?php
-
-/**
- * Tests for the PathRouter parsing.
- *
- * @covers PathRouter
- */
-class PathRouterTest extends MediaWikiTestCase {
-
-       /**
-        * @var PathRouter
-        */
-       protected $basicRouter;
-
-       protected function setUp() {
-               parent::setUp();
-               $router = new PathRouter;
-               $router->add( "/wiki/$1" );
-               $this->basicRouter = $router;
-       }
-
-       public static function provideParse() {
-               $tests = [
-                       // Basic path parsing
-                       'Basic path parsing' => [
-                               "/wiki/$1",
-                               "/wiki/Foo",
-                               [ 'title' => "Foo" ]
-                       ],
-                       //
-                       'Loose path auto-$1: /$1' => [
-                               "/",
-                               "/Foo",
-                               [ 'title' => "Foo" ]
-                       ],
-                       'Loose path auto-$1: /wiki' => [
-                               "/wiki",
-                               "/wiki/Foo",
-                               [ 'title' => "Foo" ]
-                       ],
-                       'Loose path auto-$1: /wiki/' => [
-                               "/wiki/",
-                               "/wiki/Foo",
-                               [ 'title' => "Foo" ]
-                       ],
-                       // Ensure that path is based on specificity, not order
-                       'Order, /$1 added first' => [
-                               [ "/$1", "/a/$1", "/b/$1" ],
-                               "/a/Foo",
-                               [ 'title' => "Foo" ]
-                       ],
-                       'Order, /$1 added last' => [
-                               [ "/b/$1", "/a/$1", "/$1" ],
-                               "/a/Foo",
-                               [ 'title' => "Foo" ]
-                       ],
-                       // Handling of key based arrays with a url parameter
-                       'Key based array' => [
-                               [ [
-                                       'path' => [ 'edit' => "/edit/$1" ],
-                                       'params' => [ 'action' => '$key' ],
-                               ] ],
-                               "/edit/Foo",
-                               [ 'title' => "Foo", 'action' => 'edit' ]
-                       ],
-                       // Additional parameter
-                       'Basic $2' => [
-                               [ [
-                                       'path' => '/$2/$1',
-                                       'params' => [ 'test' => '$2' ]
-                               ] ],
-                               "/asdf/Foo",
-                               [ 'title' => "Foo", 'test' => 'asdf' ]
-                       ],
-               ];
-               // Shared patterns for restricted value parameter tests
-               $restrictedPatterns = [
-                       [
-                               'path' => '/$2/$1',
-                               'params' => [ 'test' => '$2' ],
-                               'options' => [ '$2' => [ 'a', 'b' ] ]
-                       ],
-                       [
-                               'path' => '/$2/$1',
-                               'params' => [ 'test2' => '$2' ],
-                               'options' => [ '$2' => 'c' ]
-                       ],
-                       '/$1'
-               ];
-               $tests += [
-                       // Restricted value parameter tests
-                       'Restricted 1' => [
-                               $restrictedPatterns,
-                               "/asdf/Foo",
-                               [ 'title' => "asdf/Foo" ]
-                       ],
-                       'Restricted 2' => [
-                               $restrictedPatterns,
-                               "/a/Foo",
-                               [ 'title' => "Foo", 'test' => 'a' ]
-                       ],
-                       'Restricted 3' => [
-                               $restrictedPatterns,
-                               "/c/Foo",
-                               [ 'title' => "Foo", 'test2' => 'c' ]
-                       ],
-
-                       // Callback test
-                       'Callback' => [
-                               [ [
-                                       'path' => "/$1",
-                                       'params' => [ 'a' => 'b', 'data:foo' => 'bar' ],
-                                       'options' => [ 'callback' => [ __CLASS__, 'callbackForTest' ] ]
-                               ] ],
-                               '/Foo',
-                               [
-                                       'title' => "Foo",
-                                       'x' => 'Foo',
-                                       'a' => 'b',
-                                       'foo' => 'bar'
-                               ]
-                       ],
-
-                       // Test to ensure that matches are not made if a parameter expects nonexistent input
-                       'Fail' => [
-                               [ [
-                                       'path' => "/wiki/$1",
-                                       'params' => [ 'title' => "$1$2" ],
-                               ] ],
-                               "/wiki/A",
-                               []
-                       ],
-
-                       // Make sure the router handles titles like Special:Recentchanges correctly
-                       'Special title' => [
-                               "/wiki/$1",
-                               "/wiki/Special:Recentchanges",
-                               [ 'title' => "Special:Recentchanges" ]
-                       ],
-
-                       // Make sure the router decodes urlencoding properly
-                       'URL encoding' => [
-                               "/wiki/$1",
-                               "/wiki/Title_With%20Space",
-                               [ 'title' => "Title_With Space" ]
-                       ],
-
-                       // Double slash and dot expansion
-                       'Double slash in prefix' => [
-                               '/wiki/$1',
-                               '//wiki/Foo',
-                               [ 'title' => 'Foo' ]
-                       ],
-                       'Double slash at start of $1' => [
-                               '/wiki/$1',
-                               '/wiki//Foo',
-                               [ 'title' => '/Foo' ]
-                       ],
-                       'Double slash in middle of $1' => [
-                               '/wiki/$1',
-                               '/wiki/.hack//SIGN',
-                               [ 'title' => '.hack//SIGN' ]
-                       ],
-                       'Dots removed 1' => [
-                               '/wiki/$1',
-                               '/x/../wiki/Foo',
-                               [ 'title' => 'Foo' ]
-                       ],
-                       'Dots removed 2' => [
-                               '/wiki/$1',
-                               '/./wiki/Foo',
-                               [ 'title' => 'Foo' ]
-                       ],
-                       'Dots retained 1' => [
-                               '/wiki/$1',
-                               '/wiki/../wiki/Foo',
-                               [ 'title' => '../wiki/Foo' ]
-                       ],
-                       'Dots retained 2' => [
-                               '/wiki/$1',
-                               '/wiki/./Foo',
-                               [ 'title' => './Foo' ]
-                       ],
-                       'Triple slash' => [
-                               '/wiki/$1',
-                               '///wiki/Foo',
-                               [ 'title' => 'Foo' ]
-                       ],
-                       // '..' only traverses one slash, see e.g. RFC 3986
-                       'Dots traversing double slash 1' => [
-                               '/wiki/$1',
-                               '/a//b/../../wiki/Foo',
-                               []
-                       ],
-                       'Dots traversing double slash 2' => [
-                               '/wiki/$1',
-                               '/a//b/../../../wiki/Foo',
-                               [ 'title' => 'Foo' ]
-                       ],
-               ];
-
-               // Make sure the router doesn't break on special characters like $ used in regexp replacements
-               foreach ( [ "$", "$1", "\\", "\\$1" ] as $char ) {
-                       $tests["Regexp character $char"] = [
-                               "/wiki/$1",
-                               "/wiki/$char",
-                               [ 'title' => "$char" ]
-                       ];
-               }
-
-               $tests += [
-                       // Make sure the router handles characters like +&() properly
-                       "Special characters" => [
-                               "/wiki/$1",
-                               "/wiki/Plus+And&Dollar\\Stuff();[]{}*",
-                               [ 'title' => "Plus+And&Dollar\\Stuff();[]{}*" ],
-                       ],
-
-                       // Make sure the router handles unicode characters correctly
-                       "Unicode 1" => [
-                               "/wiki/$1",
-                               "/wiki/Spécial:Modifications_récentes" ,
-                               [ 'title' => "Spécial:Modifications_récentes" ],
-                       ],
-
-                       "Unicode 2" => [
-                               "/wiki/$1",
-                               "/wiki/Sp%C3%A9cial:Modifications_r%C3%A9centes",
-                               [ 'title' => "Spécial:Modifications_récentes" ],
-                       ]
-               ];
-
-               // Ensure the router doesn't choke on long paths.
-               $lorem = "Lorem_ipsum_dolor_sit_amet,_consectetur_adipisicing_elit,_sed_do_eiusmod_" .
-                       "tempor_incididunt_ut_labore_et_dolore_magna_aliqua._Ut_enim_ad_minim_veniam,_quis_" .
-                        "nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat._" .
-                        "Duis_aute_irure_dolor_in_reprehenderit_in_voluptate_velit_esse_cillum_dolore_" .
-                        "eu_fugiat_nulla_pariatur._Excepteur_sint_occaecat_cupidatat_non_proident,_sunt_" .
-                        "in_culpa_qui_officia_deserunt_mollit_anim_id_est_laborum.";
-
-               $tests += [
-                       "Long path" => [
-                               "/wiki/$1",
-                               "/wiki/$lorem",
-                               [ 'title' => $lorem ]
-                       ],
-
-                       // Ensure that the php passed site of parameter values are not urldecoded
-                       "Pattern urlencoding" => [
-                               [ [ 'path' => "/wiki/$1", 'params' => [ 'title' => '%20:$1' ] ] ],
-                               "/wiki/Foo",
-                               [ 'title' => '%20:Foo' ]
-                       ],
-
-                       // Ensure that raw parameter values do not have any variable replacements or urldecoding
-                       "Raw param value" => [
-                               [ [ 'path' => "/wiki/$1", 'params' => [ 'title' => [ 'value' => 'bar%20$1' ] ] ] ],
-                               "/wiki/Foo",
-                               [ 'title' => 'bar%20$1' ]
-                       ]
-               ];
-
-               return $tests;
-       }
-
-       /**
-        * Test path parsing
-        * @dataProvider provideParse
-        */
-       public function testParse( $patterns, $path, $expected ) {
-               $patterns = (array)$patterns;
-
-               $router = new PathRouter;
-               foreach ( $patterns as $pattern ) {
-                       if ( is_array( $pattern ) ) {
-                               $router->add( $pattern['path'], $pattern['params'] ?? [],
-                                       $pattern['options'] ?? [] );
-                       } else {
-                               $router->add( $pattern );
-                       }
-               }
-               $matches = $router->parse( $path );
-               $this->assertEquals( $matches, $expected );
-       }
-
-       public static function callbackForTest( &$matches, $data ) {
-               $matches['x'] = $data['$1'];
-               $matches['foo'] = $data['foo'];
-       }
-
-       public static function provideWeight() {
-               return [
-                       [ '/Foo', [ 'title' => 'Foo' ] ],
-                       [ '/Bar', [ 'ping' => 'pong' ] ],
-                       [ '/Baz', [ 'marco' => 'polo' ] ],
-                       [ '/asdf-foo', [ 'title' => 'qwerty-foo' ] ],
-                       [ '/qwerty-bar', [ 'title' => 'asdf-bar' ] ],
-                       [ '/a/Foo', [ 'title' => 'Foo' ] ],
-                       [ '/asdf/Foo', [ 'title' => 'Foo' ] ],
-                       [ '/qwerty/Foo', [ 'title' => 'Foo', 'qwerty' => 'qwerty' ] ],
-                       [ '/baz/Foo', [ 'title' => 'Foo', 'unrestricted' => 'baz' ] ],
-                       [ '/y/Foo', [ 'title' => 'Foo', 'restricted-to-y' => 'y' ] ],
-               ];
-       }
-
-       /**
-        * Test to ensure weight of paths is handled correctly
-        * @dataProvider provideWeight
-        */
-       public function testWeight( $path, $expected ) {
-               $router = new PathRouter;
-               $router->addStrict( "/Bar", [ 'ping' => 'pong' ] );
-               $router->add( "/asdf-$1", [ 'title' => 'qwerty-$1' ] );
-               $router->add( "/$1" );
-               $router->add( "/qwerty-$1", [ 'title' => 'asdf-$1' ] );
-               $router->addStrict( "/Baz", [ 'marco' => 'polo' ] );
-               $router->add( "/a/$1" );
-               $router->add( "/asdf/$1" );
-               $router->add( "/$2/$1", [ 'unrestricted' => '$2' ] );
-               $router->add( [ 'qwerty' => "/qwerty/$1" ], [ 'qwerty' => '$key' ] );
-               $router->add( "/$2/$1", [ 'restricted-to-y' => '$2' ], [ '$2' => 'y' ] );
-
-               $this->assertEquals( $router->parse( $path ), $expected );
-       }
-}
diff --git a/tests/phpunit/includes/Rest/ResponseFactoryTest.php b/tests/phpunit/includes/Rest/ResponseFactoryTest.php
deleted file mode 100644 (file)
index ae71272..0000000
+++ /dev/null
@@ -1,146 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Rest;
-
-use ArrayIterator;
-use MediaWiki\Rest\HttpException;
-use MediaWiki\Rest\ResponseFactory;
-use MediaWikiTestCase;
-
-/** @covers \MediaWiki\Rest\ResponseFactory */
-class ResponseFactoryTest extends MediaWikiTestCase {
-       public static function provideEncodeJson() {
-               return [
-                       [ (object)[], '{}' ],
-                       [ '/', '"/"' ],
-                       [ '£', '"£"' ],
-                       [ [], '[]' ],
-               ];
-       }
-
-       /** @dataProvider provideEncodeJson */
-       public function testEncodeJson( $input, $expected ) {
-               $rf = new ResponseFactory;
-               $this->assertSame( $expected, $rf->encodeJson( $input ) );
-       }
-
-       public function testCreateJson() {
-               $rf = new ResponseFactory;
-               $response = $rf->createJson( [] );
-               $response->getBody()->rewind();
-               $this->assertSame( 'application/json', $response->getHeaderLine( 'Content-Type' ) );
-               $this->assertSame( '[]', $response->getBody()->getContents() );
-               // Make sure getSize() is functional, since testCreateNoContent() depends on it
-               $this->assertSame( 2, $response->getBody()->getSize() );
-       }
-
-       public function testCreateNoContent() {
-               $rf = new ResponseFactory;
-               $response = $rf->createNoContent();
-               $this->assertSame( [], $response->getHeader( 'Content-Type' ) );
-               $this->assertSame( 0, $response->getBody()->getSize() );
-               $this->assertSame( 204, $response->getStatusCode() );
-       }
-
-       public function testCreatePermanentRedirect() {
-               $rf = new ResponseFactory;
-               $response = $rf->createPermanentRedirect( 'http://www.example.com/' );
-               $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
-               $this->assertSame( 301, $response->getStatusCode() );
-       }
-
-       public function testCreateLegacyTemporaryRedirect() {
-               $rf = new ResponseFactory;
-               $response = $rf->createLegacyTemporaryRedirect( 'http://www.example.com/' );
-               $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
-               $this->assertSame( 302, $response->getStatusCode() );
-       }
-
-       public function testCreateTemporaryRedirect() {
-               $rf = new ResponseFactory;
-               $response = $rf->createTemporaryRedirect( 'http://www.example.com/' );
-               $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
-               $this->assertSame( 307, $response->getStatusCode() );
-       }
-
-       public function testCreateSeeOther() {
-               $rf = new ResponseFactory;
-               $response = $rf->createSeeOther( 'http://www.example.com/' );
-               $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
-               $this->assertSame( 303, $response->getStatusCode() );
-       }
-
-       public function testCreateNotModified() {
-               $rf = new ResponseFactory;
-               $response = $rf->createNotModified();
-               $this->assertSame( 0, $response->getBody()->getSize() );
-               $this->assertSame( 304, $response->getStatusCode() );
-       }
-
-       /** @expectedException \InvalidArgumentException */
-       public function testCreateHttpErrorInvalid() {
-               $rf = new ResponseFactory;
-               $rf->createHttpError( 200 );
-       }
-
-       public function testCreateHttpError() {
-               $rf = new ResponseFactory;
-               $response = $rf->createHttpError( 415, [ 'message' => '...' ] );
-               $this->assertSame( 415, $response->getStatusCode() );
-               $body = $response->getBody();
-               $body->rewind();
-               $data = json_decode( $body->getContents(), true );
-               $this->assertSame( 415, $data['httpCode'] );
-               $this->assertSame( '...', $data['message'] );
-       }
-
-       public function testCreateFromExceptionUnlogged() {
-               $rf = new ResponseFactory;
-               $response = $rf->createFromException( new HttpException( 'hello', 415 ) );
-               $this->assertSame( 415, $response->getStatusCode() );
-               $body = $response->getBody();
-               $body->rewind();
-               $data = json_decode( $body->getContents(), true );
-               $this->assertSame( 415, $data['httpCode'] );
-               $this->assertSame( 'hello', $data['message'] );
-       }
-
-       public function testCreateFromExceptionLogged() {
-               $rf = new ResponseFactory;
-               $response = $rf->createFromException( new \Exception( "hello", 415 ) );
-               $this->assertSame( 500, $response->getStatusCode() );
-               $body = $response->getBody();
-               $body->rewind();
-               $data = json_decode( $body->getContents(), true );
-               $this->assertSame( 500, $data['httpCode'] );
-               $this->assertSame( 'Error: exception of type Exception', $data['message'] );
-       }
-
-       public static function provideCreateFromReturnValue() {
-               return [
-                       [ 'hello', '{"value":"hello"}' ],
-                       [ true, '{"value":true}' ],
-                       [ [ 'x' => 'y' ], '{"x":"y"}' ],
-                       [ [ 'x', 'y' ], '["x","y"]' ],
-                       [ [ 'a', 'x' => 'y' ], '{"0":"a","x":"y"}' ],
-                       [ (object)[ 'a', 'x' => 'y' ], '{"0":"a","x":"y"}' ],
-                       [ [], '[]' ],
-                       [ (object)[], '{}' ],
-               ];
-       }
-
-       /** @dataProvider provideCreateFromReturnValue */
-       public function testCreateFromReturnValue( $input, $expected ) {
-               $rf = new ResponseFactory;
-               $response = $rf->createFromReturnValue( $input );
-               $body = $response->getBody();
-               $body->rewind();
-               $this->assertSame( $expected, $body->getContents() );
-       }
-
-       /** @expectedException \InvalidArgumentException */
-       public function testCreateFromReturnValueInvalid() {
-               $rf = new ResponseFactory;
-               $rf->createFromReturnValue( new ArrayIterator );
-       }
-}
diff --git a/tests/phpunit/includes/Revision/MainSlotRoleHandlerTest.php b/tests/phpunit/includes/Revision/MainSlotRoleHandlerTest.php
deleted file mode 100644 (file)
index 5e32574..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Revision;
-
-use MediaWiki\Revision\MainSlotRoleHandler;
-use MediaWikiTestCase;
-use PHPUnit\Framework\MockObject\MockObject;
-use Title;
-
-/**
- * @covers \MediaWiki\Revision\MainSlotRoleHandler
- */
-class MainSlotRoleHandlerTest extends MediaWikiTestCase {
-
-       private function makeTitleObject( $ns ) {
-               /** @var Title|MockObject $title */
-               $title = $this->getMockBuilder( Title::class )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $title->method( 'getNamespace' )
-                       ->willReturn( $ns );
-
-               return $title;
-       }
-
-       /**
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::__construct
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getRole()
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getNameMessageKey()
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getOutputLayoutHints()
-        */
-       public function testConstruction() {
-               $handler = new MainSlotRoleHandler( [] );
-               $this->assertSame( 'main', $handler->getRole() );
-               $this->assertSame( 'slot-name-main', $handler->getNameMessageKey() );
-
-               $hints = $handler->getOutputLayoutHints();
-               $this->assertArrayHasKey( 'display', $hints );
-               $this->assertArrayHasKey( 'region', $hints );
-               $this->assertArrayHasKey( 'placement', $hints );
-       }
-
-       /**
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getDefaultModel()
-        */
-       public function testFetDefaultModel() {
-               $handler = new MainSlotRoleHandler( [ 100 => CONTENT_MODEL_TEXT ] );
-
-               // For the main handler, the namespace determins the default model
-               $titleMain = $this->makeTitleObject( NS_MAIN );
-               $this->assertSame( CONTENT_MODEL_WIKITEXT, $handler->getDefaultModel( $titleMain ) );
-
-               $title100 = $this->makeTitleObject( 100 );
-               $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title100 ) );
-       }
-
-       /**
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::isAllowedModel()
-        */
-       public function testIsAllowedModel() {
-               $handler = new MainSlotRoleHandler( [] );
-
-               // For the main handler, (nearly) all models are allowed
-               $title = $this->makeTitleObject( NS_MAIN );
-               $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_WIKITEXT, $title ) );
-               $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_TEXT, $title ) );
-       }
-
-       /**
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::supportsArticleCount()
-        */
-       public function testSupportsArticleCount() {
-               $handler = new MainSlotRoleHandler( [] );
-
-               $this->assertTrue( $handler->supportsArticleCount() );
-       }
-
-}
diff --git a/tests/phpunit/includes/Revision/SlotRecordTest.php b/tests/phpunit/includes/Revision/SlotRecordTest.php
deleted file mode 100644 (file)
index 6495967..0000000
+++ /dev/null
@@ -1,416 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Revision;
-
-use InvalidArgumentException;
-use LogicException;
-use MediaWiki\Revision\IncompleteRevisionException;
-use MediaWiki\Revision\SlotRecord;
-use MediaWiki\Revision\SuppressedDataException;
-use MediaWikiTestCase;
-use WikitextContent;
-
-/**
- * @covers \MediaWiki\Revision\SlotRecord
- */
-class SlotRecordTest extends MediaWikiTestCase {
-
-       private function makeRow( $data = [] ) {
-               $data = $data + [
-                       'slot_id' => 1234,
-                       'slot_content_id' => 33,
-                       'content_size' => '5',
-                       'content_sha1' => 'someHash',
-                       'content_address' => 'tt:456',
-                       'model_name' => CONTENT_MODEL_WIKITEXT,
-                       'format_name' => CONTENT_FORMAT_WIKITEXT,
-                       'slot_revision_id' => '2',
-                       'slot_origin' => '1',
-                       'role_name' => 'myRole',
-               ];
-               return (object)$data;
-       }
-
-       public function testCompleteConstruction() {
-               $row = $this->makeRow();
-               $record = new SlotRecord( $row, new WikitextContent( 'A' ) );
-
-               $this->assertTrue( $record->hasAddress() );
-               $this->assertTrue( $record->hasContentId() );
-               $this->assertTrue( $record->hasRevision() );
-               $this->assertTrue( $record->isInherited() );
-               $this->assertSame( 'A', $record->getContent()->getText() );
-               $this->assertSame( 5, $record->getSize() );
-               $this->assertSame( 'someHash', $record->getSha1() );
-               $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
-               $this->assertSame( 2, $record->getRevision() );
-               $this->assertSame( 1, $record->getOrigin() );
-               $this->assertSame( 'tt:456', $record->getAddress() );
-               $this->assertSame( 33, $record->getContentId() );
-               $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
-               $this->assertSame( 'myRole', $record->getRole() );
-       }
-
-       public function testConstructionDeferred() {
-               $row = $this->makeRow( [
-                       'content_size' => null, // to be computed
-                       'content_sha1' => null, // to be computed
-                       'format_name' => function () {
-                               return CONTENT_FORMAT_WIKITEXT;
-                       },
-                       'slot_revision_id' => '2',
-                       'slot_origin' => '2',
-                       'slot_content_id' => function () {
-                               return null;
-                       },
-               ] );
-
-               $content = function () {
-                       return new WikitextContent( 'A' );
-               };
-
-               $record = new SlotRecord( $row, $content );
-
-               $this->assertTrue( $record->hasAddress() );
-               $this->assertTrue( $record->hasRevision() );
-               $this->assertFalse( $record->hasContentId() );
-               $this->assertFalse( $record->isInherited() );
-               $this->assertSame( 'A', $record->getContent()->getText() );
-               $this->assertSame( 1, $record->getSize() );
-               $this->assertNotEmpty( $record->getSha1() );
-               $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
-               $this->assertSame( 2, $record->getRevision() );
-               $this->assertSame( 2, $record->getRevision() );
-               $this->assertSame( 'tt:456', $record->getAddress() );
-               $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
-               $this->assertSame( 'myRole', $record->getRole() );
-       }
-
-       public function testNewUnsaved() {
-               $record = SlotRecord::newUnsaved( 'myRole', new WikitextContent( 'A' ) );
-
-               $this->assertFalse( $record->hasAddress() );
-               $this->assertFalse( $record->hasContentId() );
-               $this->assertFalse( $record->hasRevision() );
-               $this->assertFalse( $record->isInherited() );
-               $this->assertFalse( $record->hasOrigin() );
-               $this->assertSame( 'A', $record->getContent()->getText() );
-               $this->assertSame( 1, $record->getSize() );
-               $this->assertNotEmpty( $record->getSha1() );
-               $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
-               $this->assertSame( 'myRole', $record->getRole() );
-       }
-
-       public function provideInvalidConstruction() {
-               yield 'both null' => [ null, null ];
-               yield 'null row' => [ null, new WikitextContent( 'A' ) ];
-               yield 'array row' => [ [], new WikitextContent( 'A' ) ];
-               yield 'empty row' => [ (object)[], new WikitextContent( 'A' ) ];
-               yield 'null content' => [ (object)[], null ];
-       }
-
-       /**
-        * @dataProvider provideInvalidConstruction
-        */
-       public function testInvalidConstruction( $row, $content ) {
-               $this->setExpectedException( InvalidArgumentException::class );
-               new SlotRecord( $row, $content );
-       }
-
-       public function testGetContentId_fails() {
-               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-               $this->setExpectedException( IncompleteRevisionException::class );
-
-               $record->getContentId();
-       }
-
-       public function testGetAddress_fails() {
-               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-               $this->setExpectedException( IncompleteRevisionException::class );
-
-               $record->getAddress();
-       }
-
-       public function provideIncomplete() {
-               $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-               yield 'unsaved' => [ $unsaved ];
-
-               $parent = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
-               $inherited = SlotRecord::newInherited( $parent );
-               yield 'inherited' => [ $inherited ];
-       }
-
-       /**
-        * @dataProvider provideIncomplete
-        */
-       public function testGetRevision_fails( SlotRecord $record ) {
-               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-               $this->setExpectedException( IncompleteRevisionException::class );
-
-               $record->getRevision();
-       }
-
-       /**
-        * @dataProvider provideIncomplete
-        */
-       public function testGetOrigin_fails( SlotRecord $record ) {
-               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-               $this->setExpectedException( IncompleteRevisionException::class );
-
-               $record->getOrigin();
-       }
-
-       public function provideHashStability() {
-               yield [ '', 'phoiac9h4m842xq45sp7s6u21eteeq1' ];
-               yield [ 'Lorem ipsum', 'hcr5u40uxr81d3nx89nvwzclfz6r9c5' ];
-       }
-
-       /**
-        * @dataProvider provideHashStability
-        */
-       public function testHashStability( $text, $hash ) {
-               // Changing the output of the hash function will break things horribly!
-
-               $this->assertSame( $hash, SlotRecord::base36Sha1( $text ) );
-
-               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( $text ) );
-               $this->assertSame( $hash, $record->getSha1() );
-       }
-
-       public function testHashComputed() {
-               $row = $this->makeRow();
-               $row->content_sha1 = '';
-
-               $rec = new SlotRecord( $row, new WikitextContent( 'A' ) );
-               $this->assertNotEmpty( $rec->getSha1() );
-       }
-
-       public function testNewWithSuppressedContent() {
-               $input = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
-               $output = SlotRecord::newWithSuppressedContent( $input );
-
-               $this->setExpectedException( SuppressedDataException::class );
-               $output->getContent();
-       }
-
-       public function testNewInherited() {
-               $row = $this->makeRow( [ 'slot_revision_id' => 7, 'slot_origin' => 7 ] );
-               $parent = new SlotRecord( $row, new WikitextContent( 'A' ) );
-
-               // This would happen while doing an edit, before saving revision meta-data.
-               $inherited = SlotRecord::newInherited( $parent );
-
-               $this->assertSame( $parent->getContentId(), $inherited->getContentId() );
-               $this->assertSame( $parent->getAddress(), $inherited->getAddress() );
-               $this->assertSame( $parent->getContent(), $inherited->getContent() );
-               $this->assertTrue( $inherited->isInherited() );
-               $this->assertTrue( $inherited->hasOrigin() );
-               $this->assertFalse( $inherited->hasRevision() );
-
-               // make sure we didn't mess with the internal state of $parent
-               $this->assertFalse( $parent->isInherited() );
-               $this->assertSame( 7, $parent->getRevision() );
-
-               // This would happen while doing an edit, after saving the revision meta-data
-               // and content meta-data.
-               $saved = SlotRecord::newSaved(
-                       10,
-                       $inherited->getContentId(),
-                       $inherited->getAddress(),
-                       $inherited
-               );
-               $this->assertSame( $parent->getContentId(), $saved->getContentId() );
-               $this->assertSame( $parent->getAddress(), $saved->getAddress() );
-               $this->assertSame( $parent->getContent(), $saved->getContent() );
-               $this->assertTrue( $saved->isInherited() );
-               $this->assertTrue( $saved->hasRevision() );
-               $this->assertSame( 10, $saved->getRevision() );
-
-               // make sure we didn't mess with the internal state of $parent or $inherited
-               $this->assertSame( 7, $parent->getRevision() );
-               $this->assertFalse( $inherited->hasRevision() );
-       }
-
-       public function testNewSaved() {
-               // This would happen while doing an edit, before saving revision meta-data.
-               $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-
-               // This would happen while doing an edit, after saving the revision meta-data
-               // and content meta-data.
-               $saved = SlotRecord::newSaved( 10, 20, 'theNewAddress', $unsaved );
-               $this->assertFalse( $saved->isInherited() );
-               $this->assertTrue( $saved->hasOrigin() );
-               $this->assertTrue( $saved->hasRevision() );
-               $this->assertTrue( $saved->hasAddress() );
-               $this->assertTrue( $saved->hasContentId() );
-               $this->assertSame( 'theNewAddress', $saved->getAddress() );
-               $this->assertSame( 20, $saved->getContentId() );
-               $this->assertSame( 'A', $saved->getContent()->getText() );
-               $this->assertSame( 10, $saved->getRevision() );
-               $this->assertSame( 10, $saved->getOrigin() );
-
-               // make sure we didn't mess with the internal state of $unsaved
-               $this->assertFalse( $unsaved->hasAddress() );
-               $this->assertFalse( $unsaved->hasContentId() );
-               $this->assertFalse( $unsaved->hasRevision() );
-       }
-
-       public function provideNewSaved_LogicException() {
-               $freshRow = $this->makeRow( [
-                       'content_id' => 10,
-                       'content_address' => 'address:1',
-                       'slot_origin' => 1,
-                       'slot_revision_id' => 1,
-               ] );
-
-               $freshSlot = new SlotRecord( $freshRow, new WikitextContent( 'A' ) );
-               yield 'mismatching address' => [ 1, 10, 'address:BAD', $freshSlot ];
-               yield 'mismatching revision' => [ 5, 10, 'address:1', $freshSlot ];
-               yield 'mismatching content ID' => [ 1, 17, 'address:1', $freshSlot ];
-
-               $inheritedRow = $this->makeRow( [
-                       'content_id' => null,
-                       'content_address' => null,
-                       'slot_origin' => 0,
-                       'slot_revision_id' => 1,
-               ] );
-
-               $inheritedSlot = new SlotRecord( $inheritedRow, new WikitextContent( 'A' ) );
-               yield 'inherited, but no address' => [ 1, 10, 'address:2', $inheritedSlot ];
-       }
-
-       /**
-        * @dataProvider provideNewSaved_LogicException
-        */
-       public function testNewSaved_LogicException(
-               $revisionId,
-               $contentId,
-               $contentAddress,
-               SlotRecord $protoSlot
-       ) {
-               $this->setExpectedException( LogicException::class );
-               SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
-       }
-
-       public function provideNewSaved_InvalidArgumentException() {
-               $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-
-               yield 'bad revision id' => [ 'xyzzy', 5, 'address', $unsaved ];
-               yield 'bad content id' => [ 7, 'xyzzy', 'address', $unsaved ];
-               yield 'bad content address' => [ 7, 5, 77, $unsaved ];
-       }
-
-       /**
-        * @dataProvider provideNewSaved_InvalidArgumentException
-        */
-       public function testNewSaved_InvalidArgumentException(
-               $revisionId,
-               $contentId,
-               $contentAddress,
-               SlotRecord $protoSlot
-       ) {
-               $this->setExpectedException( InvalidArgumentException::class );
-               SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
-       }
-
-       public function provideHasSameContent() {
-               $fail = function () {
-                       self::fail( 'There should be no need to actually load the content.' );
-               };
-
-               $a100a1 = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'A',
-                                       'content_size' => 100,
-                                       'content_sha1' => 'hash-a',
-                                       'content_address' => 'xxx:a1',
-                               ]
-                       ),
-                       $fail
-               );
-               $a100a1b = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'A',
-                                       'content_size' => 100,
-                                       'content_sha1' => 'hash-a',
-                                       'content_address' => 'xxx:a1',
-                               ]
-                       ),
-                       $fail
-               );
-               $a100null = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'A',
-                                       'content_size' => 100,
-                                       'content_sha1' => 'hash-a',
-                                       'content_address' => null,
-                               ]
-                       ),
-                       $fail
-               );
-               $a100a2 = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'A',
-                                       'content_size' => 100,
-                                       'content_sha1' => 'hash-a',
-                                       'content_address' => 'xxx:a2',
-                               ]
-                       ),
-                       $fail
-               );
-               $b100a1 = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'B',
-                                       'content_size' => 100,
-                                       'content_sha1' => 'hash-a',
-                                       'content_address' => 'xxx:a1',
-                               ]
-                       ),
-                       $fail
-               );
-               $a200a1 = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'A',
-                                       'content_size' => 200,
-                                       'content_sha1' => 'hash-a',
-                                       'content_address' => 'xxx:a2',
-                               ]
-                       ),
-                       $fail
-               );
-               $a100x1 = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'A',
-                                       'content_size' => 100,
-                                       'content_sha1' => 'hash-x',
-                                       'content_address' => 'xxx:x1',
-                               ]
-                       ),
-                       $fail
-               );
-
-               yield 'same instance' => [ $a100a1, $a100a1, true ];
-               yield 'no address' => [ $a100a1, $a100null, true ];
-               yield 'same address' => [ $a100a1, $a100a1b, true ];
-               yield 'different address' => [ $a100a1, $a100a2, true ];
-               yield 'different model' => [ $a100a1, $b100a1, false ];
-               yield 'different size' => [ $a100a1, $a200a1, false ];
-               yield 'different hash' => [ $a100a1, $a100x1, false ];
-       }
-
-       /**
-        * @dataProvider provideHasSameContent
-        */
-       public function testHasSameContent( SlotRecord $a, SlotRecord $b, $sameContent ) {
-               $this->assertSame( $sameContent, $a->hasSameContent( $b ) );
-               $this->assertSame( $sameContent, $b->hasSameContent( $a ) );
-       }
-
-}
diff --git a/tests/phpunit/includes/TitleArrayFromResultTest.php b/tests/phpunit/includes/TitleArrayFromResultTest.php
deleted file mode 100644 (file)
index 32c7571..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-<?php
-
-/**
- * @author Addshore
- * @covers TitleArrayFromResult
- */
-class TitleArrayFromResultTest extends PHPUnit\Framework\TestCase {
-
-       use MediaWikiCoversValidator;
-
-       private function getMockResultWrapper( $row = null, $numRows = 1 ) {
-               $resultWrapper = $this->getMockBuilder( Wikimedia\Rdbms\ResultWrapper::class )
-                       ->disableOriginalConstructor();
-
-               $resultWrapper = $resultWrapper->getMock();
-               $resultWrapper->expects( $this->atLeastOnce() )
-                       ->method( 'current' )
-                       ->will( $this->returnValue( $row ) );
-               $resultWrapper->expects( $this->any() )
-                       ->method( 'numRows' )
-                       ->will( $this->returnValue( $numRows ) );
-
-               return $resultWrapper;
-       }
-
-       private function getRowWithTitle( $namespace = 3, $title = 'foo' ) {
-               $row = new stdClass();
-               $row->page_namespace = $namespace;
-               $row->page_title = $title;
-               return $row;
-       }
-
-       /**
-        * @covers TitleArrayFromResult::__construct
-        */
-       public function testConstructionWithFalseRow() {
-               $row = false;
-               $resultWrapper = $this->getMockResultWrapper( $row );
-
-               $object = new TitleArrayFromResult( $resultWrapper );
-
-               $this->assertEquals( $resultWrapper, $object->res );
-               $this->assertSame( 0, $object->key );
-               $this->assertEquals( $row, $object->current );
-       }
-
-       /**
-        * @covers TitleArrayFromResult::__construct
-        */
-       public function testConstructionWithRow() {
-               $namespace = 0;
-               $title = 'foo';
-               $row = $this->getRowWithTitle( $namespace, $title );
-               $resultWrapper = $this->getMockResultWrapper( $row );
-
-               $object = new TitleArrayFromResult( $resultWrapper );
-
-               $this->assertEquals( $resultWrapper, $object->res );
-               $this->assertSame( 0, $object->key );
-               $this->assertInstanceOf( Title::class, $object->current );
-               $this->assertEquals( $namespace, $object->current->mNamespace );
-               $this->assertEquals( $title, $object->current->mTextform );
-       }
-
-       public static function provideNumberOfRows() {
-               return [
-                       [ 0 ],
-                       [ 1 ],
-                       [ 122 ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideNumberOfRows
-        * @covers TitleArrayFromResult::count
-        */
-       public function testCountWithVaryingValues( $numRows ) {
-               $object = new TitleArrayFromResult( $this->getMockResultWrapper(
-                       $this->getRowWithTitle(),
-                       $numRows
-               ) );
-               $this->assertEquals( $numRows, $object->count() );
-       }
-
-       /**
-        * @covers TitleArrayFromResult::current
-        */
-       public function testCurrentAfterConstruction() {
-               $namespace = 0;
-               $title = 'foo';
-               $row = $this->getRowWithTitle( $namespace, $title );
-               $object = new TitleArrayFromResult( $this->getMockResultWrapper( $row ) );
-               $this->assertInstanceOf( Title::class, $object->current() );
-               $this->assertEquals( $namespace, $object->current->mNamespace );
-               $this->assertEquals( $title, $object->current->mTextform );
-       }
-
-       public function provideTestValid() {
-               return [
-                       [ $this->getRowWithTitle(), true ],
-                       [ false, false ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideTestValid
-        * @covers TitleArrayFromResult::valid
-        */
-       public function testValid( $input, $expected ) {
-               $object = new TitleArrayFromResult( $this->getMockResultWrapper( $input ) );
-               $this->assertEquals( $expected, $object->valid() );
-       }
-
-       // @todo unit test for key()
-       // @todo unit test for next()
-       // @todo unit test for rewind()
-}
diff --git a/tests/phpunit/includes/WikiReferenceTest.php b/tests/phpunit/includes/WikiReferenceTest.php
deleted file mode 100644 (file)
index e4b21ce..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-<?php
-
-/**
- * @covers WikiReference
- */
-class WikiReferenceTest extends PHPUnit\Framework\TestCase {
-
-       use MediaWikiCoversValidator;
-
-       public function provideGetDisplayName() {
-               return [
-                       'http' => [ 'foo.bar', 'http://foo.bar' ],
-                       'https' => [ 'foo.bar', 'http://foo.bar' ],
-
-                       // apparently, this is the expected behavior
-                       'invalid' => [ 'purple kittens', 'purple kittens' ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideGetDisplayName
-        */
-       public function testGetDisplayName( $expected, $canonicalServer ) {
-               $reference = new WikiReference( $canonicalServer, '/wiki/$1' );
-               $this->assertEquals( $expected, $reference->getDisplayName() );
-       }
-
-       public function testGetCanonicalServer() {
-               $reference = new WikiReference( 'https://acme.com', '/wiki/$1', '//acme.com' );
-               $this->assertEquals( 'https://acme.com', $reference->getCanonicalServer() );
-       }
-
-       public function provideGetCanonicalUrl() {
-               return [
-                       'no fragment' => [
-                               'https://acme.com/wiki/Foo',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               null
-                       ],
-                       'empty fragment' => [
-                               'https://acme.com/wiki/Foo',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               ''
-                       ],
-                       'fragment' => [
-                               'https://acme.com/wiki/Foo#Bar',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               'Bar'
-                       ],
-                       'double fragment' => [
-                               'https://acme.com/wiki/Foo#Bar%23Xus',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               'Bar#Xus'
-                       ],
-                       'escaped fragment' => [
-                               'https://acme.com/wiki/Foo%23Bar',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo#Bar',
-                               null
-                       ],
-                       'empty path' => [
-                               'https://acme.com/Foo',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/$1',
-                               'Foo',
-                               null
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideGetCanonicalUrl
-        */
-       public function testGetCanonicalUrl(
-               $expected, $canonicalServer, $server, $path, $page, $fragmentId
-       ) {
-               $reference = new WikiReference( $canonicalServer, $path, $server );
-               $this->assertEquals( $expected, $reference->getCanonicalUrl( $page, $fragmentId ) );
-       }
-
-       /**
-        * @dataProvider provideGetCanonicalUrl
-        * @note getUrl is an alias for getCanonicalUrl
-        */
-       public function testGetUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
-               $reference = new WikiReference( $canonicalServer, $path, $server );
-               $this->assertEquals( $expected, $reference->getUrl( $page, $fragmentId ) );
-       }
-
-       public function provideGetFullUrl() {
-               return [
-                       'no fragment' => [
-                               '//acme.com/wiki/Foo',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               null
-                       ],
-                       'empty fragment' => [
-                               '//acme.com/wiki/Foo',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               ''
-                       ],
-                       'fragment' => [
-                               '//acme.com/wiki/Foo#Bar',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               'Bar'
-                       ],
-                       'double fragment' => [
-                               '//acme.com/wiki/Foo#Bar%23Xus',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               'Bar#Xus'
-                       ],
-                       'escaped fragment' => [
-                               '//acme.com/wiki/Foo%23Bar',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo#Bar',
-                               null
-                       ],
-                       'empty path' => [
-                               '//acme.com/Foo',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/$1',
-                               'Foo',
-                               null
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideGetFullUrl
-        */
-       public function testGetFullUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
-               $reference = new WikiReference( $canonicalServer, $path, $server );
-               $this->assertEquals( $expected, $reference->getFullUrl( $page, $fragmentId ) );
-       }
-
-}
diff --git a/tests/phpunit/includes/debug/logger/monolog/CeeFormatterTest.php b/tests/phpunit/includes/debug/logger/monolog/CeeFormatterTest.php
deleted file mode 100644 (file)
index b30c7a4..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-<?php
-
-namespace MediaWiki\Logger\Monolog;
-
-/**
- * Flay per https://phabricator.wikimedia.org/T218688.
- *
- * @group Broken
- * @covers \MediaWiki\Logger\Monolog\CeeFormatter
- */
-class CeeFormatterTest extends \PHPUnit\Framework\TestCase {
-       public function testV1() {
-               $ls_formatter = new LogstashFormatter( 'app', 'system', null, 'ctx_', LogstashFormatter::V1 );
-               $cee_formatter = new CeeFormatter( 'app', 'system', null, 'ctx_', LogstashFormatter::V1 );
-               $record = [ 'extra' => [ 'url' => 1 ], 'context' => [ 'url' => 2 ] ];
-               $this->assertSame(
-                       $cee_formatter->format( $record ),
-                       "@cee: " . $ls_formatter->format( $record ) );
-       }
-}
diff --git a/tests/phpunit/includes/diff/DifferenceEngineSlotDiffRendererTest.php b/tests/phpunit/includes/diff/DifferenceEngineSlotDiffRendererTest.php
deleted file mode 100644 (file)
index fe129b7..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-/**
- * @covers DifferenceEngineSlotDiffRenderer
- */
-class DifferenceEngineSlotDiffRendererTest extends \PHPUnit\Framework\TestCase {
-
-       public function testGetDiff() {
-               $differenceEngine = new CustomDifferenceEngine();
-               $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
-               $oldContent = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT );
-               $newContent = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT );
-
-               $diff = $slotDiffRenderer->getDiff( $oldContent, $newContent );
-               $this->assertEquals( 'xxx|yyy', $diff );
-
-               $diff = $slotDiffRenderer->getDiff( null, $newContent );
-               $this->assertEquals( '|yyy', $diff );
-
-               $diff = $slotDiffRenderer->getDiff( $oldContent, null );
-               $this->assertEquals( 'xxx|', $diff );
-       }
-
-       public function testAddModules() {
-               $output = $this->getMockBuilder( OutputPage::class )
-                       ->disableOriginalConstructor()
-                       ->setMethods( [ 'addModules' ] )
-                       ->getMock();
-               $output->expects( $this->once() )
-                       ->method( 'addModules' )
-                       ->with( 'foo' );
-               $differenceEngine = new CustomDifferenceEngine();
-               $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
-               $slotDiffRenderer->addModules( $output );
-       }
-
-       public function testGetExtraCacheKeys() {
-               $differenceEngine = new CustomDifferenceEngine();
-               $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
-               $extraCacheKeys = $slotDiffRenderer->getExtraCacheKeys();
-               $this->assertSame( [ 'foo' ], $extraCacheKeys );
-       }
-
-}
diff --git a/tests/phpunit/includes/diff/SlotDiffRendererTest.php b/tests/phpunit/includes/diff/SlotDiffRendererTest.php
deleted file mode 100644 (file)
index a03280d..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-<?php
-
-use Wikimedia\Assert\ParameterTypeException;
-use Wikimedia\TestingAccessWrapper;
-
-/**
- * @covers SlotDiffRenderer
- */
-class SlotDiffRendererTest extends \PHPUnit\Framework\TestCase {
-
-       /**
-        * @dataProvider provideNormalizeContents
-        */
-       public function testNormalizeContents(
-               $oldContent, $newContent, $allowedClasses,
-               $expectedOldContent, $expectedNewContent, $expectedExceptionClass
-       ) {
-               $slotDiffRenderer = $this->getMockBuilder( SlotDiffRenderer::class )
-                       ->getMock();
-               try {
-                       // __call needs help deciding which parameter to take by reference
-                       call_user_func_array( [ TestingAccessWrapper::newFromObject( $slotDiffRenderer ),
-                               'normalizeContents' ], [ &$oldContent, &$newContent, $allowedClasses ] );
-                       $this->assertEquals( $expectedOldContent, $oldContent );
-                       $this->assertEquals( $expectedNewContent, $newContent );
-               } catch ( Exception $e ) {
-                       if ( !$expectedExceptionClass ) {
-                               throw $e;
-                       }
-                       $this->assertInstanceOf( $expectedExceptionClass, $e );
-               }
-       }
-
-       public function provideNormalizeContents() {
-               return [
-                       'both null' => [ null, null, null, null, null, InvalidArgumentException::class ],
-                       'left null' => [
-                               null, new WikitextContent( 'abc' ), null,
-                               new WikitextContent( '' ), new WikitextContent( 'abc' ), null,
-                       ],
-                       'right null' => [
-                               new WikitextContent( 'def' ), null, null,
-                               new WikitextContent( 'def' ), new WikitextContent( '' ), null,
-                       ],
-                       'type filter' => [
-                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
-                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
-                       ],
-                       'type filter (subclass)' => [
-                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), TextContent::class,
-                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
-                       ],
-                       'type filter (null)' => [
-                               new WikitextContent( 'abc' ), null, TextContent::class,
-                               new WikitextContent( 'abc' ), new WikitextContent( '' ), null,
-                       ],
-                       'type filter failure (left)' => [
-                               new TextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
-                               null, null, ParameterTypeException::class,
-                       ],
-                       'type filter failure (right)' => [
-                               new WikitextContent( 'abc' ), new TextContent( 'def' ), WikitextContent::class,
-                               null, null, ParameterTypeException::class,
-                       ],
-                       'type filter (array syntax)' => [
-                               new WikitextContent( 'abc' ), new JsonContent( 'def' ),
-                               [ JsonContent::class, WikitextContent::class ],
-                               new WikitextContent( 'abc' ), new JsonContent( 'def' ), null,
-                       ],
-                       'type filter failure (array syntax)' => [
-                               new WikitextContent( 'abc' ), new CssContent( 'def' ),
-                               [ JsonContent::class, WikitextContent::class ],
-                               null, null, ParameterTypeException::class,
-                       ],
-               ];
-       }
-
-}
diff --git a/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php b/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php
deleted file mode 100644 (file)
index 346be7a..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-<?php
-
-class FileBackendDBRepoWrapperTest extends MediaWikiTestCase {
-       protected $backendName = 'foo-backend';
-       protected $repoName = 'pureTestRepo';
-
-       /**
-        * @dataProvider getBackendPathsProvider
-        * @covers FileBackendDBRepoWrapper::getBackendPaths
-        */
-       public function testGetBackendPaths(
-               $mocks,
-               $latest,
-               $dbReadsExpected,
-               $dbReturnValue,
-               $originalPath,
-               $expectedBackendPath,
-               $message ) {
-               list( $dbMock, $backendMock, $wrapperMock ) = $mocks;
-
-               $dbMock->expects( $dbReadsExpected )
-                       ->method( 'selectField' )
-                       ->will( $this->returnValue( $dbReturnValue ) );
-
-               $newPaths = $wrapperMock->getBackendPaths( [ $originalPath ], $latest );
-
-               $this->assertEquals(
-                       $expectedBackendPath,
-                       $newPaths[0],
-                       $message );
-       }
-
-       public function getBackendPathsProvider() {
-               $prefix = 'mwstore://' . $this->backendName . '/' . $this->repoName;
-               $mocksForCaching = $this->getMocks();
-
-               return [
-                       [
-                               $mocksForCaching,
-                               false,
-                               $this->once(),
-                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               $prefix . '-public/f/o/foobar.jpg',
-                               $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               'Public path translated correctly',
-                       ],
-                       [
-                               $mocksForCaching,
-                               false,
-                               $this->never(),
-                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               $prefix . '-public/f/o/foobar.jpg',
-                               $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               'LRU cache leveraged',
-                       ],
-                       [
-                               $this->getMocks(),
-                               true,
-                               $this->once(),
-                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               $prefix . '-public/f/o/foobar.jpg',
-                               $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               'Latest obtained',
-                       ],
-                       [
-                               $this->getMocks(),
-                               true,
-                               $this->never(),
-                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               $prefix . '-deleted/f/o/foobar.jpg',
-                               $prefix . '-original/f/o/o/foobar',
-                               'Deleted path translated correctly',
-                       ],
-                       [
-                               $this->getMocks(),
-                               true,
-                               $this->once(),
-                               null,
-                               $prefix . '-public/b/a/baz.jpg',
-                               $prefix . '-public/b/a/baz.jpg',
-                               'Path left untouched if no sha1 can be found',
-                       ],
-               ];
-       }
-
-       /**
-        * @covers FileBackendDBRepoWrapper::getFileContentsMulti
-        */
-       public function testGetFileContentsMulti() {
-               list( $dbMock, $backendMock, $wrapperMock ) = $this->getMocks();
-
-               $sha1Path = 'mwstore://' . $this->backendName . '/' . $this->repoName
-                       . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9';
-               $filenamePath = 'mwstore://' . $this->backendName . '/' . $this->repoName
-                       . '-public/f/o/foobar.jpg';
-
-               $dbMock->expects( $this->once() )
-                       ->method( 'selectField' )
-                       ->will( $this->returnValue( '96246614d75ba1703bdfd5d7660bb57407aaf5d9' ) );
-
-               $backendMock->expects( $this->once() )
-                       ->method( 'getFileContentsMulti' )
-                       ->will( $this->returnValue( [ $sha1Path => 'foo' ] ) );
-
-               $result = $wrapperMock->getFileContentsMulti( [ 'srcs' => [ $filenamePath ] ] );
-
-               $this->assertEquals(
-                       [ $filenamePath => 'foo' ],
-                       $result,
-                       'File contents paths translated properly'
-               );
-       }
-
-       protected function getMocks() {
-               $dbMock = $this->getMockBuilder( Wikimedia\Rdbms\IDatabase::class )
-                       ->disableOriginalClone()
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $backendMock = $this->getMockBuilder( FSFileBackend::class )
-                       ->setConstructorArgs( [ [
-                                       'name' => $this->backendName,
-                                       'wikiId' => wfWikiID()
-                               ] ] )
-                       ->getMock();
-
-               $wrapperMock = $this->getMockBuilder( FileBackendDBRepoWrapper::class )
-                       ->setMethods( [ 'getDB' ] )
-                       ->setConstructorArgs( [ [
-                                       'backend' => $backendMock,
-                                       'repoName' => $this->repoName,
-                                       'dbHandleFactory' => null
-                               ] ] )
-                       ->getMock();
-
-               $wrapperMock->expects( $this->any() )->method( 'getDB' )->will( $this->returnValue( $dbMock ) );
-
-               return [ $dbMock, $backendMock, $wrapperMock ];
-       }
-}
diff --git a/tests/phpunit/includes/filerepo/file/ForeignDBFileTest.php b/tests/phpunit/includes/filerepo/file/ForeignDBFileTest.php
deleted file mode 100644 (file)
index 3c92ecb..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php
-
-/** @covers ForeignDBFile */
-class ForeignDBFileTest extends \PHPUnit\Framework\TestCase {
-
-       use PHPUnit4And6Compat;
-
-       public function testShouldConstructCorrectInstanceFromTitle() {
-               $title = Title::makeTitle( NS_FILE, 'Awesome_file' );
-               $repoMock = $this->createMock( LocalRepo::class );
-
-               $file = ForeignDBFile::newFromTitle( $title, $repoMock );
-
-               $this->assertInstanceOf( ForeignDBFile::class, $file );
-       }
-}
diff --git a/tests/phpunit/includes/htmlform/HTMLCheckMatrixTest.php b/tests/phpunit/includes/htmlform/HTMLCheckMatrixTest.php
deleted file mode 100644 (file)
index 05c567d..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-<?php
-
-/**
- * @covers HTMLCheckMatrix
- */
-class HTMLCheckMatrixTest extends MediaWikiTestCase {
-       private static $defaultOptions = [
-               'rows' => [ 'r1', 'r2' ],
-               'columns' => [ 'c1', 'c2' ],
-               'fieldname' => 'test',
-       ];
-
-       public function testPlainInstantiation() {
-               try {
-                       new HTMLCheckMatrix( [] );
-               } catch ( MWException $e ) {
-                       $this->assertInstanceOf( HTMLFormFieldRequiredOptionsException::class, $e );
-                       return;
-               }
-
-               $this->fail( 'Expected MWException indicating missing parameters but none was thrown.' );
-       }
-
-       public function testInstantiationWithMinimumRequiredParameters() {
-               new HTMLCheckMatrix( self::$defaultOptions );
-               $this->assertTrue( true ); // form instantiation must throw exception on failure
-       }
-
-       public function testValidateCallsUserDefinedValidationCallback() {
-               $called = false;
-               $field = new HTMLCheckMatrix( self::$defaultOptions + [
-                       'validation-callback' => function () use ( &$called ) {
-                               $called = true;
-
-                               return false;
-                       },
-               ] );
-               $this->assertEquals( false, $this->validate( $field, [] ) );
-               $this->assertTrue( $called );
-       }
-
-       public function testValidateRequiresArrayInput() {
-               $field = new HTMLCheckMatrix( self::$defaultOptions );
-               $this->assertEquals( false, $this->validate( $field, null ) );
-               $this->assertEquals( false, $this->validate( $field, true ) );
-               $this->assertEquals( false, $this->validate( $field, 'abc' ) );
-               $this->assertEquals( false, $this->validate( $field, new stdClass ) );
-               $this->assertEquals( true, $this->validate( $field, [] ) );
-       }
-
-       public function testValidateAllowsOnlyKnownTags() {
-               $field = new HTMLCheckMatrix( self::$defaultOptions );
-               $this->assertInstanceOf( Message::class, $this->validate( $field, [ 'foo' ] ) );
-       }
-
-       public function testValidateAcceptsPartialTagList() {
-               $field = new HTMLCheckMatrix( self::$defaultOptions );
-               $this->assertTrue( $this->validate( $field, [] ) );
-               $this->assertTrue( $this->validate( $field, [ 'c1-r1' ] ) );
-               $this->assertTrue( $this->validate( $field, [ 'c1-r1', 'c1-r2', 'c2-r1', 'c2-r2' ] ) );
-       }
-
-       /**
-        * This form object actually has no visibility into what happens later on, but essentially
-        * if the data submitted by the user passes validate the following is run:
-        * foreach ( $field->filterDataForSubmit( $data ) as $k => $v ) {
-        *     $user->setOption( $k, $v );
-        * }
-        */
-       public function testValuesForcedOnRemainOn() {
-               $field = new HTMLCheckMatrix( self::$defaultOptions + [
-                               'force-options-on' => [ 'c2-r1' ],
-                       ] );
-               $expected = [
-                       'c1-r1' => false,
-                       'c1-r2' => false,
-                       'c2-r1' => true,
-                       'c2-r2' => false,
-               ];
-               $this->assertEquals( $expected, $field->filterDataForSubmit( [] ) );
-       }
-
-       public function testValuesForcedOffRemainOff() {
-               $field = new HTMLCheckMatrix( self::$defaultOptions + [
-                               'force-options-off' => [ 'c1-r2', 'c2-r2' ],
-                       ] );
-               $expected = [
-                       'c1-r1' => true,
-                       'c1-r2' => false,
-                       'c2-r1' => true,
-                       'c2-r2' => false,
-               ];
-               // array_keys on the result simulates submitting all fields checked
-               $this->assertEquals( $expected, $field->filterDataForSubmit( array_keys( $expected ) ) );
-       }
-
-       protected function validate( HTMLFormField $field, $submitted ) {
-               return $field->validate(
-                       $submitted,
-                       [ self::$defaultOptions['fieldname'] => $submitted ]
-               );
-       }
-
-}
diff --git a/tests/phpunit/includes/json/FormatJsonTest.php b/tests/phpunit/includes/json/FormatJsonTest.php
deleted file mode 100644 (file)
index 1a99775..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-
-/**
- * @covers FormatJson
- */
-class FormatJsonTest extends MediaWikiTestCase {
-
-       /**
-        * Test data for testParseTryFixing.
-        *
-        * Some PHP interpreters use json-c rather than the JSON.org canonical
-        * parser to avoid being encumbered by the "shall be used for Good, not
-        * Evil" clause of the JSON.org parser's license. By default, json-c
-        * parses in a non-strict mode which allows trailing commas for array and
-        * object delarations among other things, so our JSON_ERROR_SYNTAX rescue
-        * block is not always triggered. It however isn't lenient in exactly the
-        * same ways as our TRY_FIXING mode, so the assertions in this test are
-        * a bit more complicated than they ideally would be:
-        *
-        * Optional third argument: true if json-c parses the value without
-        * intervention, false otherwise. Defaults to true.
-        *
-        * Optional fourth argument: expected cannonical JSON serialization of
-        * json-c parsed result. Defaults to the second argument's value.
-        */
-       public static function provideParseTryFixing() {
-               return [
-                       [ "[,]", '[]', false ],
-                       [ "[ , ]", '[]', false ],
-                       [ "[ , }", false ],
-                       [ '[1],', false, true, '[1]' ],
-                       [ "[1,]", '[1]' ],
-                       [ "[1\n,]", '[1]' ],
-                       [ "[1,\n]", '[1]' ],
-                       [ "[1,]\n", '[1]' ],
-                       [ "[1\n,\n]\n", '[1]' ],
-                       [ '["a,",]', '["a,"]' ],
-                       [ "[[1,]\n,[2,\n],[3\n,]]", '[[1],[2],[3]]' ],
-                       // I wish we could parse this, but would need quote parsing
-                       [ '[[1,],[2,],[3,]]', false, true, '[[1],[2],[3]]' ],
-                       [ '[1,,]', false, false, '[1]' ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideParseTryFixing
-        * @param string $value
-        * @param string|bool $expected Expected result with strict parser
-        * @param bool $jsoncParses Will json-c parse this value without TRY_FIXING?
-        * @param string|bool $expectedJsonc Expected result with lenient parser
-        * if different from the strict expectation
-        */
-       public function testParseTryFixing(
-               $value, $expected,
-               $jsoncParses = true, $expectedJsonc = null
-       ) {
-               // PHP5 results are always expected to have isGood() === false
-               $expectedGoodStatus = false;
-
-               // Check to see if json parser allows trailing commas
-               if ( json_decode( '[1,]' ) !== null ) {
-                       // Use json-c specific expected result if provided
-                       $expected = ( $expectedJsonc === null ) ? $expected : $expectedJsonc;
-                       // If json-c parses the value natively, expect isGood() === true
-                       $expectedGoodStatus = $jsoncParses;
-               }
-
-               $st = FormatJson::parse( $value, FormatJson::TRY_FIXING );
-               $this->assertInstanceOf( Status::class, $st );
-               if ( $expected === false ) {
-                       $this->assertFalse( $st->isOK(), 'Expected isOK() == false' );
-               } else {
-                       $this->assertSame( $expectedGoodStatus, $st->isGood(),
-                               'Expected isGood() == ' . ( $expectedGoodStatus ? 'true' : 'false' )
-                       );
-                       $this->assertTrue( $st->isOK(), 'Expected isOK == true' );
-                       $val = FormatJson::encode( $st->getValue(), false, FormatJson::ALL_OK );
-                       $this->assertEquals( $expected, $val );
-               }
-       }
-
-}
diff --git a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php
deleted file mode 100644 (file)
index c943cef..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-<?php
-/**
- * @todo Could use a test of extended XMP segments. Hard to find programs that
- * create example files, and creating my own in vim propbably wouldn't
- * serve as a very good "test". (Adobe photoshop probably creates such files
- * but it costs money). The implementation of it currently in MediaWiki is based
- * solely on reading the standard, without any real world test files.
- *
- * @group Media
- * @covers JpegMetadataExtractor
- */
-class JpegMetadataExtractorTest extends MediaWikiTestCase {
-
-       protected $filePath;
-
-       protected function setUp() {
-               parent::setUp();
-
-               $this->filePath = __DIR__ . '/../../data/media/';
-       }
-
-       /**
-        * We also use this test to test padding bytes don't
-        * screw stuff up
-        *
-        * @param string $file Filename
-        *
-        * @dataProvider provideUtf8Comment
-        */
-       public function testUtf8Comment( $file ) {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . $file );
-               $this->assertEquals( [ 'UTF-8 JPEG Comment — ¼' ], $res['COM'] );
-       }
-
-       public static function provideUtf8Comment() {
-               return [
-                       [ 'jpeg-comment-utf.jpg' ],
-                       [ 'jpeg-padding-even.jpg' ],
-                       [ 'jpeg-padding-odd.jpg' ],
-               ];
-       }
-
-       /** The file is iso-8859-1, but it should get auto converted */
-       public function testIso88591Comment() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-iso8859-1.jpg' );
-               $this->assertEquals( [ 'ISO-8859-1 JPEG Comment - ¼' ], $res['COM'] );
-       }
-
-       /** Comment values that are non-textual (random binary junk) should not be shown.
-        * The example test file has a comment with a 0x5 byte in it which is a control character
-        * and considered binary junk for our purposes.
-        */
-       public function testBinaryCommentStripped() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-binary.jpg' );
-               $this->assertEmpty( $res['COM'] );
-       }
-
-       /* Very rarely a file can have multiple comments.
-        *   Order of comments is based on order inside the file.
-        */
-       public function testMultipleComment() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-multiple.jpg' );
-               $this->assertEquals( [ 'foo', 'bar' ], $res['COM'] );
-       }
-
-       public function testXMPExtraction() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
-               $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
-               $this->assertEquals( $expected, $res['XMP'] );
-       }
-
-       public function testPSIRExtraction() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
-               $expected = '50686f746f73686f7020332e30003842494d04040000000'
-                       . '000181c02190004746573741c02190003666f6f1c020000020004';
-               $this->assertEquals( $expected, bin2hex( $res['PSIR'][0] ) );
-       }
-
-       public function testXMPExtractionAltAppId() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-alt.jpg' );
-               $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
-               $this->assertEquals( $expected, $res['XMP'] );
-       }
-
-       public function testIPTCHashComparisionNoHash() {
-               $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
-               $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
-
-               $this->assertEquals( 'iptc-no-hash', $res );
-       }
-
-       public function testIPTCHashComparisionBadHash() {
-               $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' );
-               $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
-
-               $this->assertEquals( 'iptc-bad-hash', $res );
-       }
-
-       public function testIPTCHashComparisionGoodHash() {
-               $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-good-hash.jpg' );
-               $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
-
-               $this->assertEquals( 'iptc-good-hash', $res );
-       }
-
-       public function testExifByteOrder() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'exif-user-comment.jpg' );
-               $expected = 'BE';
-               $this->assertEquals( $expected, $res['byteOrder'] );
-       }
-
-       public function testInfiniteRead() {
-               // test file truncated right after a segment, which previously
-               // caused an infinite loop looking for the next segment byte.
-               // Should get past infinite loop and throw in wfUnpack()
-               $this->setExpectedException( 'MWException' );
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop1.jpg' );
-       }
-
-       public function testInfiniteRead2() {
-               // test file truncated after a segment's marker and size, which
-               // would cause a seek past end of file. Seek past end of file
-               // doesn't actually fail, but prevents further reading and was
-               // devolving into the previous case (testInfiniteRead).
-               $this->setExpectedException( 'MWException' );
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop2.jpg' );
-       }
-}
diff --git a/tests/phpunit/includes/page/ArticleTest.php b/tests/phpunit/includes/page/ArticleTest.php
deleted file mode 100644 (file)
index df4a281..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-
-class ArticleTest extends MediaWikiTestCase {
-
-       /**
-        * @var Title
-        */
-       private $title;
-       /**
-        * @var Article
-        */
-       private $article;
-
-       /** creates a title object and its article object */
-       protected function setUp() {
-               parent::setUp();
-               $this->title = Title::makeTitle( NS_MAIN, 'SomePage' );
-               $this->article = new Article( $this->title );
-       }
-
-       /** cleanup title object and its article object */
-       protected function tearDown() {
-               parent::tearDown();
-               $this->title = null;
-               $this->article = null;
-       }
-
-       /**
-        * @covers Article::__get
-        */
-       public function testImplementsGetMagic() {
-               $this->assertEquals( false, $this->article->mLatest, "Article __get magic" );
-       }
-
-       /**
-        * @depends testImplementsGetMagic
-        * @covers Article::__set
-        */
-       public function testImplementsSetMagic() {
-               $this->article->mLatest = 2;
-               $this->assertEquals( 2, $this->article->mLatest, "Article __set magic" );
-       }
-
-       /**
-        * @covers Article::__get
-        * @covers Article::__set
-        */
-       public function testGetOrSetOnNewProperty() {
-               $this->article->ext_someNewProperty = 12;
-               $this->assertEquals( 12, $this->article->ext_someNewProperty,
-                       "Article get/set magic on new field" );
-
-               $this->article->ext_someNewProperty = -8;
-               $this->assertEquals( -8, $this->article->ext_someNewProperty,
-                       "Article get/set magic on update to new field" );
-       }
-}
diff --git a/tests/phpunit/includes/session/TokenTest.php b/tests/phpunit/includes/session/TokenTest.php
deleted file mode 100644 (file)
index 4797652..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-
-namespace MediaWiki\Session;
-
-use MediaWikiTestCase;
-use Wikimedia\TestingAccessWrapper;
-
-/**
- * @group Session
- * @covers MediaWiki\Session\Token
- */
-class TokenTest extends MediaWikiTestCase {
-
-       public function testBasics() {
-               $token = $this->getMockBuilder( Token::class )
-                       ->setMethods( [ 'toStringAtTimestamp' ] )
-                       ->setConstructorArgs( [ 'sekret', 'salty', true ] )
-                       ->getMock();
-               $token->expects( $this->any() )->method( 'toStringAtTimestamp' )
-                       ->will( $this->returnValue( 'faketoken+\\' ) );
-
-               $this->assertSame( 'faketoken+\\', $token->toString() );
-               $this->assertSame( 'faketoken+\\', (string)$token );
-               $this->assertTrue( $token->wasNew() );
-
-               $token = new Token( 'sekret', 'salty', false );
-               $this->assertFalse( $token->wasNew() );
-       }
-
-       public function testToStringAtTimestamp() {
-               $token = TestingAccessWrapper::newFromObject( new Token( 'sekret', 'salty', false ) );
-
-               $this->assertSame(
-                       'd9ade0c7d4349e9df9094e61c33a5a0d5644fde2+\\',
-                       $token->toStringAtTimestamp( 1447362018 )
-               );
-               $this->assertSame(
-                       'ee2f7a2488dea9176c224cfb400d43be5644fdea+\\',
-                       $token->toStringAtTimestamp( 1447362026 )
-               );
-       }
-
-       public function testGetTimestamp() {
-               $this->assertSame(
-                       1447362018, Token::getTimestamp( 'd9ade0c7d4349e9df9094e61c33a5a0d5644fde2+\\' )
-               );
-               $this->assertSame(
-                       1447362026, Token::getTimestamp( 'ee2f7a2488dea9176c224cfb400d43be5644fdea+\\' )
-               );
-               $this->assertNull( Token::getTimestamp( 'ee2f7a2488dea9176c224cfb400d43be5644fdea-\\' ) );
-               $this->assertNull( Token::getTimestamp( 'ee2f7a2488dea9176c224cfb400d43be+\\' ) );
-
-               $this->assertNull( Token::getTimestamp( 'ee2f7a2488dea9x76c224cfb400d43be5644fdea+\\' ) );
-       }
-
-       public function testMatch() {
-               $token = TestingAccessWrapper::newFromObject( new Token( 'sekret', 'salty', false ) );
-
-               $test = $token->toStringAtTimestamp( time() - 10 );
-               $this->assertTrue( $token->match( $test ) );
-               $this->assertTrue( $token->match( $test, 12 ) );
-               $this->assertFalse( $token->match( $test, 8 ) );
-
-               $this->assertFalse( $token->match( 'ee2f7a2488dea9176c224cfb400d43be5644fdea-\\' ) );
-       }
-
-}
diff --git a/tests/phpunit/includes/shell/FirejailCommandTest.php b/tests/phpunit/includes/shell/FirejailCommandTest.php
deleted file mode 100644 (file)
index 681c3dc..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-<?php
-
-/**
- * Copyright (C) 2017 Kunal Mehta <legoktm@member.fsf.org>
- *
- * 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.
- *
- */
-
-use MediaWiki\Shell\FirejailCommand;
-use MediaWiki\Shell\Shell;
-use Wikimedia\TestingAccessWrapper;
-
-class FirejailCommandTest extends PHPUnit\Framework\TestCase {
-
-       use MediaWikiCoversValidator;
-
-       public function provideBuildFinalCommand() {
-               global $IP;
-               // phpcs:ignore Generic.Files.LineLength
-               $env = "'MW_INCLUDE_STDERR=;MW_CPU_LIMIT=180; MW_CGROUP='\'''\''; MW_MEM_LIMIT=307200; MW_FILE_SIZE_LIMIT=102400; MW_WALL_CLOCK_LIMIT=180; MW_USE_LOG_PIPE=yes'";
-               $limit = "/bin/bash '$IP/includes/shell/limit.sh'";
-               $profile = "--profile=$IP/includes/shell/firejail.profile";
-               $blacklist = '--blacklist=' . realpath( MW_CONFIG_FILE );
-               $default = "$blacklist --noroot --seccomp --private-dev";
-               return [
-                       [
-                               'No restrictions',
-                               'ls', 0, "$limit ''\''ls'\''' $env"
-                       ],
-                       [
-                               'default restriction',
-                               'ls', Shell::RESTRICT_DEFAULT,
-                               "$limit 'firejail --quiet $profile $default -- '\''ls'\''' $env"
-                       ],
-                       [
-                               'no network',
-                               'ls', Shell::NO_NETWORK,
-                               "$limit 'firejail --quiet $profile --net=none -- '\''ls'\''' $env"
-                       ],
-                       [
-                               'default restriction & no network',
-                               'ls', Shell::RESTRICT_DEFAULT | Shell::NO_NETWORK,
-                               "$limit 'firejail --quiet $profile $default --net=none -- '\''ls'\''' $env"
-                       ],
-                       [
-                               'seccomp',
-                               'ls', Shell::SECCOMP,
-                               "$limit 'firejail --quiet $profile --seccomp -- '\''ls'\''' $env"
-                       ],
-                       [
-                               'seccomp & no execve',
-                               'ls', Shell::SECCOMP | Shell::NO_EXECVE,
-                               "$limit 'firejail --quiet $profile --shell=none --seccomp=execve -- '\''ls'\''' $env"
-                       ],
-               ];
-       }
-
-       /**
-        * @covers \MediaWiki\Shell\FirejailCommand::buildFinalCommand()
-        * @dataProvider provideBuildFinalCommand
-        */
-       public function testBuildFinalCommand( $desc, $params, $flags, $expected ) {
-               $command = new FirejailCommand( 'firejail' );
-               $command
-                       ->params( $params )
-                       ->restrict( $flags );
-               $wrapper = TestingAccessWrapper::newFromObject( $command );
-               $output = $wrapper->buildFinalCommand( $wrapper->command );
-               $this->assertEquals( $expected, $output[0], $desc );
-       }
-
-}
diff --git a/tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php b/tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php
deleted file mode 100644 (file)
index 15894a3..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-<?php
-
-use MediaWiki\Site\MediaWikiPageNameNormalizer;
-
-/**
- * @covers MediaWiki\Site\MediaWikiPageNameNormalizer
- *
- * 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
- *
- * @since 1.27
- *
- * @group Site
- * @group medium
- *
- * @author Marius Hoch
- */
-class MediaWikiPageNameNormalizerTest extends PHPUnit\Framework\TestCase {
-
-       use MediaWikiCoversValidator;
-
-       /**
-        * @dataProvider normalizePageTitleProvider
-        */
-       public function testNormalizePageTitle( $expected, $pageName, $getResponse ) {
-               MediaWikiPageNameNormalizerTestMockHttp::$response = $getResponse;
-
-               $normalizer = new MediaWikiPageNameNormalizer(
-                       new MediaWikiPageNameNormalizerTestMockHttp()
-               );
-
-               $this->assertSame(
-                       $expected,
-                       $normalizer->normalizePageName( $pageName, 'https://www.wikidata.org/w/api.php' )
-               );
-       }
-
-       public function normalizePageTitleProvider() {
-               // Response are taken from wikidata and kkwiki using the following API request
-               // api.php?action=query&prop=info&redirects=1&converttitles=1&format=json&titles=…
-               return [
-                       'universe (Q1)' => [
-                               'Q1',
-                               'Q1',
-                               '{"batchcomplete":"","query":{"pages":{"129":{"pageid":129,"ns":0,'
-                               . '"title":"Q1","contentmodel":"wikibase-item","pagelanguage":"en",'
-                               . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
-                               . '"touched":"2016-06-23T05:11:21Z","lastrevid":350004448,"length":58001}}}}'
-                       ],
-                       'Q404 redirects to Q395' => [
-                               'Q395',
-                               'Q404',
-                               '{"batchcomplete":"","query":{"redirects":[{"from":"Q404","to":"Q395"}],"pages"'
-                               . ':{"601":{"pageid":601,"ns":0,"title":"Q395","contentmodel":"wikibase-item",'
-                               . '"pagelanguage":"en","pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
-                               . '"touched":"2016-06-23T08:00:20Z","lastrevid":350021914,"length":60108}}}}'
-                       ],
-                       'D converted to Д (Latin to Cyrillic) (taken from kkwiki)' => [
-                               'Д',
-                               'D',
-                               '{"batchcomplete":"","query":{"converted":[{"from":"D","to":"\u0414"}],'
-                               . '"pages":{"510541":{"pageid":510541,"ns":0,"title":"\u0414",'
-                               . '"contentmodel":"wikitext","pagelanguage":"kk","pagelanguagehtmlcode":"kk",'
-                               . '"pagelanguagedir":"ltr","touched":"2015-11-22T09:16:18Z",'
-                               . '"lastrevid":2373618,"length":3501}}}}'
-                       ],
-                       'there is no Q0' => [
-                               false,
-                               'Q0',
-                               '{"batchcomplete":"","query":{"pages":{"-1":{"ns":0,"title":"Q0",'
-                               . '"missing":"","contentmodel":"wikibase-item","pagelanguage":"en",'
-                               . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr"}}}}'
-                       ],
-                       'invalid title' => [
-                               false,
-                               '{{',
-                               '{"batchcomplete":"","query":{"pages":{"-1":{"title":"{{",'
-                               . '"invalidreason":"The requested page title contains invalid '
-                               . 'characters: \"{\".","invalid":""}}}}'
-                       ],
-                       'error on get' => [ false, 'ABC', false ]
-               ];
-       }
-
-}
-
-/**
- * @private
- * @see Http
- */
-class MediaWikiPageNameNormalizerTestMockHttp extends Http {
-
-       /**
-        * @var mixed
-        */
-       public static $response;
-
-       public static function get( $url, array $options = [], $caller = __METHOD__ ) {
-               PHPUnit_Framework_Assert::assertInternalType( 'string', $url );
-               PHPUnit_Framework_Assert::assertInternalType( 'string', $caller );
-
-               return self::$response;
-       }
-}
diff --git a/tests/phpunit/includes/utils/ZipDirectoryReaderTest.php b/tests/phpunit/includes/utils/ZipDirectoryReaderTest.php
deleted file mode 100644 (file)
index a1a3fd7..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-<?php
-
-/**
- * @covers ZipDirectoryReader
- * NOTE: this test is more like an integration test than a unit test
- */
-class ZipDirectoryReaderTest extends PHPUnit\Framework\TestCase {
-
-       use MediaWikiCoversValidator;
-
-       protected $zipDir;
-       protected $entries;
-
-       protected function setUp() {
-               parent::setUp();
-               $this->zipDir = __DIR__ . '/../../data/zip';
-       }
-
-       function zipCallback( $entry ) {
-               $this->entries[] = $entry;
-       }
-
-       function readZipAssertError( $file, $error, $assertMessage ) {
-               $this->entries = [];
-               $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
-               $this->assertTrue( $status->hasMessage( $error ), $assertMessage );
-       }
-
-       function readZipAssertSuccess( $file, $assertMessage ) {
-               $this->entries = [];
-               $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
-               $this->assertTrue( $status->isOK(), $assertMessage );
-       }
-
-       public function testEmpty() {
-               $this->readZipAssertSuccess( 'empty.zip', 'Empty zip' );
-       }
-
-       public function testMultiDisk0() {
-               $this->readZipAssertError( 'split.zip', 'zip-unsupported',
-                       'Split zip error' );
-       }
-
-       public function testNoSignature() {
-               $this->readZipAssertError( 'nosig.zip', 'zip-wrong-format',
-                       'No signature should give "wrong format" error' );
-       }
-
-       public function testSimple() {
-               $this->readZipAssertSuccess( 'class.zip', 'Simple ZIP' );
-               $this->assertEquals( $this->entries, [ [
-                       'name' => 'Class.class',
-                       'mtime' => '20010115000000',
-                       'size' => 1,
-               ] ] );
-       }
-
-       public function testBadCentralEntrySignature() {
-               $this->readZipAssertError( 'wrong-central-entry-sig.zip', 'zip-bad',
-                       'Bad central entry error' );
-       }
-
-       public function testTrailingBytes() {
-               // Due to T40432 this is now zip-wrong-format instead of zip-bad
-               $this->readZipAssertError( 'trail.zip', 'zip-wrong-format',
-                       'Trailing bytes error' );
-       }
-
-       public function testWrongCDStart() {
-               $this->readZipAssertError( 'wrong-cd-start-disk.zip', 'zip-unsupported',
-                       'Wrong CD start disk error' );
-       }
-
-       public function testCentralDirectoryGap() {
-               $this->readZipAssertError( 'cd-gap.zip', 'zip-bad',
-                       'CD gap error' );
-       }
-
-       public function testCentralDirectoryTruncated() {
-               $this->readZipAssertError( 'cd-truncated.zip', 'zip-bad',
-                       'CD truncated error (should hit unpack() overrun)' );
-       }
-
-       public function testLooksLikeZip64() {
-               $this->readZipAssertError( 'looks-like-zip64.zip', 'zip-unsupported',
-                       'A file which looks like ZIP64 but isn\'t, should give error' );
-       }
-}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfAppendQueryTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfAppendQueryTest.php
new file mode 100644 (file)
index 0000000..11d4475
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfAppendQuery
+ */
+class WfAppendQueryTest extends MediaWikiUnitTestCase {
+       /**
+        * @dataProvider provideAppendQuery
+        */
+       public function testAppendQuery( $url, $query, $expected, $message = null ) {
+               $this->assertEquals( $expected, wfAppendQuery( $url, $query ), $message );
+       }
+
+       public static function provideAppendQuery() {
+               return [
+                       [
+                               'http://www.example.org/index.php',
+                               '',
+                               'http://www.example.org/index.php',
+                               'No query'
+                       ],
+                       [
+                               'http://www.example.org/index.php',
+                               [ 'foo' => 'bar' ],
+                               'http://www.example.org/index.php?foo=bar',
+                               'Set query array'
+                       ],
+                       [
+                               'http://www.example.org/index.php?foz=baz',
+                               'foo=bar',
+                               'http://www.example.org/index.php?foz=baz&foo=bar',
+                               'Set query string'
+                       ],
+                       [
+                               'http://www.example.org/index.php?foo=bar',
+                               '',
+                               'http://www.example.org/index.php?foo=bar',
+                               'Empty string with query'
+                       ],
+                       [
+                               'http://www.example.org/index.php?foo=bar',
+                               [ 'baz' => 'quux' ],
+                               'http://www.example.org/index.php?foo=bar&baz=quux',
+                               'Add query array'
+                       ],
+                       [
+                               'http://www.example.org/index.php?foo=bar',
+                               'baz=quux',
+                               'http://www.example.org/index.php?foo=bar&baz=quux',
+                               'Add query string'
+                       ],
+                       [
+                               'http://www.example.org/index.php?foo=bar',
+                               [ 'baz' => 'quux', 'foo' => 'baz' ],
+                               'http://www.example.org/index.php?foo=bar&baz=quux&foo=baz',
+                               'Modify query array'
+                       ],
+                       [
+                               'http://www.example.org/index.php?foo=bar',
+                               'baz=quux&foo=baz',
+                               'http://www.example.org/index.php?foo=bar&baz=quux&foo=baz',
+                               'Modify query string'
+                       ],
+                       [
+                               'http://www.example.org/index.php#baz',
+                               'foo=bar',
+                               'http://www.example.org/index.php?foo=bar#baz',
+                               'URL with fragment'
+                       ],
+                       [
+                               'http://www.example.org/index.php?foo=bar#baz',
+                               'quux=blah',
+                               'http://www.example.org/index.php?foo=bar&quux=blah#baz',
+                               'URL with query string and fragment'
+                       ]
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfArrayPlus2dTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfArrayPlus2dTest.php
new file mode 100644 (file)
index 0000000..9c3e56c
--- /dev/null
@@ -0,0 +1,94 @@
+<?php
+/**
+ * @group GlobalFunctions
+ * @covers ::wfArrayPlus2d
+ */
+class WfArrayPlus2dTest extends MediaWikiUnitTestCase {
+       /**
+        * @dataProvider provideArrays
+        */
+       public function testWfArrayPlus2d( $baseArray, $newValues, $expected, $testName ) {
+               $this->assertEquals(
+                       $expected,
+                       wfArrayPlus2d( $baseArray, $newValues ),
+                       $testName
+               );
+       }
+
+       /**
+        * Provider for testing wfArrayPlus2d
+        *
+        * @return array
+        */
+       public static function provideArrays() {
+               return [
+                       // target array, new values array, expected result
+                       [
+                               [ 0 => '1dArray' ],
+                               [ 1 => '1dArray' ],
+                               [ 0 => '1dArray', 1 => '1dArray' ],
+                               "Test simple union of two arrays with different keys",
+                       ],
+                       [
+                               [
+                                       0 => [ 0 => '2dArray' ],
+                               ],
+                               [
+                                       0 => [ 1 => '2dArray' ],
+                               ],
+                               [
+                                       0 => [ 0 => '2dArray', 1 => '2dArray' ],
+                               ],
+                               "Test union of 2d arrays with different keys in the value array",
+                       ],
+                       [
+                               [
+                                       0 => [ 0 => '2dArray' ],
+                               ],
+                               [
+                                       0 => [ 0 => '1dArray' ],
+                               ],
+                               [
+                                       0 => [ 0 => '2dArray' ],
+                               ],
+                               "Test union of 2d arrays with same keys in the value array",
+                       ],
+                       [
+                               [
+                                       0 => [ 0 => [ 0 => '3dArray' ] ],
+                               ],
+                               [
+                                       0 => [ 0 => [ 1 => '2dArray' ] ],
+                               ],
+                               [
+                                       0 => [ 0 => [ 0 => '3dArray' ] ],
+                               ],
+                               "Test union of 3d array with different keys",
+                       ],
+                       [
+                               [
+                                       0 => [ 0 => [ 0 => '3dArray' ] ],
+                               ],
+                               [
+                                       0 => [ 1 => [ 0 => '2dArray' ] ],
+                               ],
+                               [
+                                       0 => [ 0 => [ 0 => '3dArray' ], 1 => [ 0 => '2dArray' ] ],
+                               ],
+                               "Test union of 3d array with different keys in the value array",
+                       ],
+                       [
+                               [
+                                       0 => [ 0 => [ 0 => '3dArray' ] ],
+                               ],
+                               [
+                                       0 => [ 0 => [ 0 => '2dArray' ] ],
+                               ],
+                               [
+                                       0 => [ 0 => [ 0 => '3dArray' ] ],
+                               ],
+                               "Test union of 3d array with same keys in the value array",
+                       ],
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfAssembleUrlTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfAssembleUrlTest.php
new file mode 100644 (file)
index 0000000..f1f1986
--- /dev/null
@@ -0,0 +1,112 @@
+<?php
+/**
+ * @group GlobalFunctions
+ * @covers ::wfAssembleUrl
+ */
+class WfAssembleUrlTest extends MediaWikiUnitTestCase {
+       /**
+        * @dataProvider provideURLParts
+        */
+       public function testWfAssembleUrl( $parts, $output ) {
+               $partsDump = print_r( $parts, true );
+               $this->assertEquals(
+                       $output,
+                       wfAssembleUrl( $parts ),
+                       "Testing $partsDump assembles to $output"
+               );
+       }
+
+       /**
+        * Provider of URL parts for testing wfAssembleUrl()
+        *
+        * @return array
+        */
+       public static function provideURLParts() {
+               $schemes = [
+                       '' => [],
+                       '//' => [
+                               'delimiter' => '//',
+                       ],
+                       'http://' => [
+                               'scheme' => 'http',
+                               'delimiter' => '://',
+                       ],
+               ];
+
+               $hosts = [
+                       '' => [],
+                       'example.com' => [
+                               'host' => 'example.com',
+                       ],
+                       'example.com:123' => [
+                               'host' => 'example.com',
+                               'port' => 123,
+                       ],
+                       'id@example.com' => [
+                               'user' => 'id',
+                               'host' => 'example.com',
+                       ],
+                       'id@example.com:123' => [
+                               'user' => 'id',
+                               'host' => 'example.com',
+                               'port' => 123,
+                       ],
+                       'id:key@example.com' => [
+                               'user' => 'id',
+                               'pass' => 'key',
+                               'host' => 'example.com',
+                       ],
+                       'id:key@example.com:123' => [
+                               'user' => 'id',
+                               'pass' => 'key',
+                               'host' => 'example.com',
+                               'port' => 123,
+                       ],
+               ];
+
+               $cases = [];
+               foreach ( $schemes as $scheme => $schemeParts ) {
+                       foreach ( $hosts as $host => $hostParts ) {
+                               foreach ( [ '', '/path' ] as $path ) {
+                                       foreach ( [ '', 'query' ] as $query ) {
+                                               foreach ( [ '', 'fragment' ] as $fragment ) {
+                                                       $parts = array_merge(
+                                                               $schemeParts,
+                                                               $hostParts
+                                                       );
+                                                       $url = $scheme .
+                                                               $host .
+                                                               $path;
+
+                                                       if ( $path ) {
+                                                               $parts['path'] = $path;
+                                                       }
+                                                       if ( $query ) {
+                                                               $parts['query'] = $query;
+                                                               $url .= '?' . $query;
+                                                       }
+                                                       if ( $fragment ) {
+                                                               $parts['fragment'] = $fragment;
+                                                               $url .= '#' . $fragment;
+                                                       }
+
+                                                       $cases[] = [
+                                                               $parts,
+                                                               $url,
+                                                       ];
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               $complexURL = 'http://id:key@example.org:321' .
+                       '/over/there?name=ferret&foo=bar#nose';
+               $cases[] = [
+                       wfParseUrl( $complexURL ),
+                       $complexURL,
+               ];
+
+               return $cases;
+       }
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfBaseNameTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfBaseNameTest.php
new file mode 100644 (file)
index 0000000..4d55fb7
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @group GlobalFunctions
+ * @covers ::wfBaseName
+ */
+class WfBaseNameTest extends MediaWikiUnitTestCase {
+       /**
+        * @dataProvider providePaths
+        */
+       public function testBaseName( $fullpath, $basename ) {
+               $this->assertEquals( $basename, wfBaseName( $fullpath ),
+                       "wfBaseName('$fullpath') => '$basename'" );
+       }
+
+       public static function providePaths() {
+               return [
+                       [ '', '' ],
+                       [ '/', '' ],
+                       [ '\\', '' ],
+                       [ '//', '' ],
+                       [ '\\\\', '' ],
+                       [ 'a', 'a' ],
+                       [ 'aaaa', 'aaaa' ],
+                       [ '/a', 'a' ],
+                       [ '\\a', 'a' ],
+                       [ '/aaaa', 'aaaa' ],
+                       [ '\\aaaa', 'aaaa' ],
+                       [ '/aaaa/', 'aaaa' ],
+                       [ '\\aaaa\\', 'aaaa' ],
+                       [ '\\aaaa\\', 'aaaa' ],
+                       [
+                               '/mnt/upload3/wikipedia/en/thumb/8/8b/'
+                                       . 'Zork_Grand_Inquisitor_box_cover.jpg/93px-Zork_Grand_Inquisitor_box_cover.jpg',
+                               '93px-Zork_Grand_Inquisitor_box_cover.jpg'
+                       ],
+                       [ 'C:\\Progra~1\\Wikime~1\\Wikipe~1\\VIEWER.EXE', 'VIEWER.EXE' ],
+                       [ 'Östergötland_coat_of_arms.png', 'Östergötland_coat_of_arms.png' ],
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfEscapeShellArgTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfEscapeShellArgTest.php
new file mode 100644 (file)
index 0000000..d86b397
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfEscapeShellArg
+ */
+class WfEscapeShellArgTest extends MediaWikiUnitTestCase {
+       public function testSingleInput() {
+               if ( wfIsWindows() ) {
+                       $expected = '"blah"';
+               } else {
+                       $expected = "'blah'";
+               }
+
+               $actual = wfEscapeShellArg( 'blah' );
+
+               $this->assertEquals( $expected, $actual );
+       }
+
+       public function testMultipleArgs() {
+               if ( wfIsWindows() ) {
+                       $expected = '"foo" "bar" "baz"';
+               } else {
+                       $expected = "'foo' 'bar' 'baz'";
+               }
+
+               $actual = wfEscapeShellArg( 'foo', 'bar', 'baz' );
+
+               $this->assertEquals( $expected, $actual );
+       }
+
+       public function testMultipleArgsAsArray() {
+               if ( wfIsWindows() ) {
+                       $expected = '"foo" "bar" "baz"';
+               } else {
+                       $expected = "'foo' 'bar' 'baz'";
+               }
+
+               $actual = wfEscapeShellArg( [ 'foo', 'bar', 'baz' ] );
+
+               $this->assertEquals( $expected, $actual );
+       }
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfGetCallerTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfGetCallerTest.php
new file mode 100644 (file)
index 0000000..ae397d5
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfGetCaller
+ */
+class WfGetCallerTest extends MediaWikiUnitTestCase {
+       public function testZero() {
+               $this->assertEquals( 'WfGetCallerTest->testZero', wfGetCaller( 1 ) );
+       }
+
+       function callerOne() {
+               return wfGetCaller();
+       }
+
+       public function testOne() {
+               $this->assertEquals( 'WfGetCallerTest->testOne', self::callerOne() );
+       }
+
+       static function intermediateFunction( $level = 2, $n = 0 ) {
+               if ( $n > 0 ) {
+                       return self::intermediateFunction( $level, $n - 1 );
+               }
+
+               return wfGetCaller( $level );
+       }
+
+       public function testTwo() {
+               $this->assertEquals( 'WfGetCallerTest->testTwo', self::intermediateFunction() );
+       }
+
+       public function testN() {
+               $this->assertEquals( 'WfGetCallerTest->testN', self::intermediateFunction( 2, 0 ) );
+               $this->assertEquals(
+                       'WfGetCallerTest::intermediateFunction',
+                       self::intermediateFunction( 1, 0 )
+               );
+
+               for ( $i = 0; $i < 10; $i++ ) {
+                       $this->assertEquals(
+                               'WfGetCallerTest::intermediateFunction',
+                               self::intermediateFunction( $i + 1, $i )
+                       );
+               }
+       }
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php
new file mode 100644 (file)
index 0000000..199fa21
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfRemoveDotSegments
+ */
+class WfRemoveDotSegmentsTest extends MediaWikiUnitTestCase {
+       /**
+        * @dataProvider providePaths
+        */
+       public function testWfRemoveDotSegments( $inputPath, $outputPath ) {
+               $this->assertEquals(
+                       $outputPath,
+                       wfRemoveDotSegments( $inputPath ),
+                       "Testing $inputPath expands to $outputPath"
+               );
+       }
+
+       /**
+        * Provider of URL paths for testing wfRemoveDotSegments()
+        *
+        * @return array
+        */
+       public static function providePaths() {
+               return [
+                       [ '/a/b/c/./../../g', '/a/g' ],
+                       [ 'mid/content=5/../6', 'mid/6' ],
+                       [ '/a//../b', '/a/b' ],
+                       [ '/.../a', '/.../a' ],
+                       [ '.../a', '.../a' ],
+                       [ '', '' ],
+                       [ '/', '/' ],
+                       [ '//', '//' ],
+                       [ '.', '' ],
+                       [ '..', '' ],
+                       [ '...', '...' ],
+                       [ '/.', '/' ],
+                       [ '/..', '/' ],
+                       [ './', '' ],
+                       [ '../', '' ],
+                       [ './a', 'a' ],
+                       [ '../a', 'a' ],
+                       [ '../../a', 'a' ],
+                       [ '.././a', 'a' ],
+                       [ './../a', 'a' ],
+                       [ '././a', 'a' ],
+                       [ '../../', '' ],
+                       [ '.././', '' ],
+                       [ './../', '' ],
+                       [ '././', '' ],
+                       [ '../..', '' ],
+                       [ '../.', '' ],
+                       [ './..', '' ],
+                       [ './.', '' ],
+                       [ '/../../a', '/a' ],
+                       [ '/.././a', '/a' ],
+                       [ '/./../a', '/a' ],
+                       [ '/././a', '/a' ],
+                       [ '/../../', '/' ],
+                       [ '/.././', '/' ],
+                       [ '/./../', '/' ],
+                       [ '/././', '/' ],
+                       [ '/../..', '/' ],
+                       [ '/../.', '/' ],
+                       [ '/./..', '/' ],
+                       [ '/./.', '/' ],
+                       [ 'b/../../a', '/a' ],
+                       [ 'b/.././a', '/a' ],
+                       [ 'b/./../a', '/a' ],
+                       [ 'b/././a', 'b/a' ],
+                       [ 'b/../../', '/' ],
+                       [ 'b/.././', '/' ],
+                       [ 'b/./../', '/' ],
+                       [ 'b/././', 'b/' ],
+                       [ 'b/../..', '/' ],
+                       [ 'b/../.', '/' ],
+                       [ 'b/./..', '/' ],
+                       [ 'b/./.', 'b/' ],
+                       [ '/b/../../a', '/a' ],
+                       [ '/b/.././a', '/a' ],
+                       [ '/b/./../a', '/a' ],
+                       [ '/b/././a', '/b/a' ],
+                       [ '/b/../../', '/' ],
+                       [ '/b/.././', '/' ],
+                       [ '/b/./../', '/' ],
+                       [ '/b/././', '/b/' ],
+                       [ '/b/../..', '/' ],
+                       [ '/b/../.', '/' ],
+                       [ '/b/./..', '/' ],
+                       [ '/b/./.', '/b/' ],
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfShorthandToIntegerTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfShorthandToIntegerTest.php
new file mode 100644 (file)
index 0000000..c2d1f4a
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfShorthandToInteger
+ */
+class WfShorthandToIntegerTest extends MediaWikiUnitTestCase {
+       /**
+        * @dataProvider provideABunchOfShorthands
+        */
+       public function testWfShorthandToInteger( $input, $output, $description ) {
+               $this->assertEquals(
+                       wfShorthandToInteger( $input ),
+                       $output,
+                       $description
+               );
+       }
+
+       public static function provideABunchOfShorthands() {
+               return [
+                       [ '', -1, 'Empty string' ],
+                       [ '     ', -1, 'String of spaces' ],
+                       [ '1G', 1024 * 1024 * 1024, 'One gig uppercased' ],
+                       [ '1g', 1024 * 1024 * 1024, 'One gig lowercased' ],
+                       [ '1M', 1024 * 1024, 'One meg uppercased' ],
+                       [ '1m', 1024 * 1024, 'One meg lowercased' ],
+                       [ '1K', 1024, 'One kb uppercased' ],
+                       [ '1k', 1024, 'One kb lowercased' ],
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfStringToBoolTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfStringToBoolTest.php
new file mode 100644 (file)
index 0000000..b99d695
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfStringToBool
+ */
+class WfStringToBoolTest extends MediaWikiUnitTestCase {
+
+       public function getTestCases() {
+               return [
+                       [ 'true', true ],
+                       [ 'on', true ],
+                       [ 'yes', true ],
+                       [ 'TRUE', true ],
+                       [ 'YeS', true ],
+                       [ 'On', true ],
+                       [ '1', true ],
+                       [ '+1', true ],
+                       [ '01', true ],
+                       [ '-001', true ],
+                       [ '  1', true ],
+                       [ '-1  ', true ],
+                       [ '', false ],
+                       [ '0', false ],
+                       [ 'false', false ],
+                       [ 'NO', false ],
+                       [ 'NOT', false ],
+                       [ 'never', false ],
+                       [ '!&', false ],
+                       [ '-0', false ],
+                       [ '+0', false ],
+                       [ 'forget about it', false ],
+                       [ ' on', false ],
+                       [ 'true ', false ],
+               ];
+       }
+
+       /**
+        * @dataProvider getTestCases
+        * @param string $str
+        * @param bool $bool
+        */
+       public function testStr2Bool( $str, $bool ) {
+               if ( $bool ) {
+                       $this->assertTrue( wfStringToBool( $str ) );
+               } else {
+                       $this->assertFalse( wfStringToBool( $str ) );
+               }
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfTimestampTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfTimestampTest.php
new file mode 100644 (file)
index 0000000..94347bd
--- /dev/null
@@ -0,0 +1,194 @@
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfTimestamp
+ */
+class WfTimestampTest extends MediaWikiUnitTestCase {
+       /**
+        * @dataProvider provideNormalTimestamps
+        */
+       public function testNormalTimestamps( $input, $format, $output, $desc ) {
+               $this->assertEquals( $output, wfTimestamp( $format, $input ), $desc );
+       }
+
+       public static function provideNormalTimestamps() {
+               $t = gmmktime( 12, 34, 56, 1, 15, 2001 );
+
+               return [
+                       // TS_UNIX
+                       [ $t, TS_MW, '20010115123456', 'TS_UNIX to TS_MW' ],
+                       [ -30281104, TS_MW, '19690115123456', 'Negative TS_UNIX to TS_MW' ],
+                       [ $t, TS_UNIX, 979562096, 'TS_UNIX to TS_UNIX' ],
+                       [ $t, TS_DB, '2001-01-15 12:34:56', 'TS_UNIX to TS_DB' ],
+                       [ $t + 0.01, TS_MW, '20010115123456', 'TS_UNIX float to TS_MW' ],
+
+                       [ $t, TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_ISO_8601_BASIC to TS_DB' ],
+
+                       // TS_MW
+                       [ '20010115123456', TS_MW, '20010115123456', 'TS_MW to TS_MW' ],
+                       [ '20010115123456', TS_UNIX, 979562096, 'TS_MW to TS_UNIX' ],
+                       [ '20010115123456', TS_DB, '2001-01-15 12:34:56', 'TS_MW to TS_DB' ],
+                       [ '20010115123456', TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_MW to TS_ISO_8601_BASIC' ],
+
+                       // TS_DB
+                       [ '2001-01-15 12:34:56', TS_MW, '20010115123456', 'TS_DB to TS_MW' ],
+                       [ '2001-01-15 12:34:56', TS_UNIX, 979562096, 'TS_DB to TS_UNIX' ],
+                       [ '2001-01-15 12:34:56', TS_DB, '2001-01-15 12:34:56', 'TS_DB to TS_DB' ],
+                       [
+                               '2001-01-15 12:34:56',
+                               TS_ISO_8601_BASIC,
+                               '20010115T123456Z',
+                               'TS_DB to TS_ISO_8601_BASIC'
+                       ],
+
+                       # rfc2822 section 3.3
+                       [ '20010115123456', TS_RFC2822, 'Mon, 15 Jan 2001 12:34:56 GMT', 'TS_MW to TS_RFC2822' ],
+                       [ 'Mon, 15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ],
+                       [
+                               ' Mon, 15 Jan 2001 12:34:56 GMT',
+                               TS_MW,
+                               '20010115123456',
+                               'TS_RFC2822 with leading space to TS_MW'
+                       ],
+                       [
+                               '15 Jan 2001 12:34:56 GMT',
+                               TS_MW,
+                               '20010115123456',
+                               'TS_RFC2822 without optional day-of-week to TS_MW'
+                       ],
+
+                       # FWS = ([*WSP CRLF] 1*WSP) / obs-FWS ; Folding white space
+                       # obs-FWS = 1*WSP *(CRLF 1*WSP) ; Section 4.2
+                       [ 'Mon, 15         Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ],
+
+                       # WSP = SP / HTAB ; rfc2234
+                       [
+                               "Mon, 15 Jan\x092001 12:34:56 GMT",
+                               TS_MW,
+                               '20010115123456',
+                               'TS_RFC2822 with HTAB to TS_MW'
+                       ],
+                       [
+                               "Mon, 15 Jan\x09 \x09  2001 12:34:56 GMT",
+                               TS_MW,
+                               '20010115123456',
+                               'TS_RFC2822 with HTAB and SP to TS_MW'
+                       ],
+                       [
+                               'Sun, 6 Nov 94 08:49:37 GMT',
+                               TS_MW,
+                               '19941106084937',
+                               'TS_RFC2822 with obsolete year to TS_MW'
+                       ],
+               ];
+       }
+
+       /**
+        * This test checks wfTimestamp() with values outside.
+        * It needs PHP 64 bits or PHP > 5.1.
+        * See r74778 and T27451
+        * @dataProvider provideOldTimestamps
+        */
+       public function testOldTimestamps( $input, $outputType, $output, $message ) {
+               $timestamp = wfTimestamp( $outputType, $input );
+               if ( substr( $output, 0, 1 ) === '/' ) {
+                       // T66946: Day of the week calculations for very old
+                       // timestamps varies from system to system.
+                       $this->assertRegExp( $output, $timestamp, $message );
+               } else {
+                       $this->assertEquals( $output, $timestamp, $message );
+               }
+       }
+
+       public static function provideOldTimestamps() {
+               return [
+                       [
+                               '19011213204554',
+                               TS_RFC2822,
+                               'Fri, 13 Dec 1901 20:45:54 GMT',
+                               'Earliest time according to PHP documentation'
+                       ],
+                       [ '20380119031407', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:07 GMT', 'Latest 32 bit time' ],
+                       [ '19011213204552', TS_UNIX, '-2147483648', 'Earliest 32 bit unix time' ],
+                       [ '20380119031407', TS_UNIX, '2147483647', 'Latest 32 bit unix time' ],
+                       [ '19011213204552', TS_RFC2822, 'Fri, 13 Dec 1901 20:45:52 GMT', 'Earliest 32 bit time' ],
+                       [
+                               '19011213204551',
+                               TS_RFC2822,
+                               'Fri, 13 Dec 1901 20:45:51 GMT', 'Earliest 32 bit time - 1'
+                       ],
+                       [ '20380119031408', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:08 GMT', 'Latest 32 bit time + 1' ],
+                       [ '19011212000000', TS_MW, '19011212000000', 'Convert to itself r74778#c10645' ],
+                       [ '19011213204551', TS_UNIX, '-2147483649', 'Earliest 32 bit unix time - 1' ],
+                       [ '20380119031408', TS_UNIX, '2147483648', 'Latest 32 bit unix time + 1' ],
+                       [ '-2147483649', TS_MW, '19011213204551', '1901 negative unix time to MediaWiki' ],
+                       [ '-5331871504', TS_MW, '18010115123456', '1801 negative unix time to MediaWiki' ],
+                       [
+                               '0117-08-09 12:34:56',
+                               TS_RFC2822,
+                               '/, 09 Aug 0117 12:34:56 GMT$/',
+                               'Death of Roman Emperor [[Trajan]]'
+                       ],
+
+                       /* @todo FIXME: 00 to 101 years are taken as being in [1970-2069] */
+                       [ '-58979923200', TS_RFC2822, '/, 01 Jan 0101 00:00:00 GMT$/', '1/1/101' ],
+                       [ '-62135596800', TS_RFC2822, 'Mon, 01 Jan 0001 00:00:00 GMT', 'Year 1' ],
+
+                       /* It is not clear if we should generate a year 0 or not
+                        * We are completely off RFC2822 requirement of year being
+                        * 1900 or later.
+                        */
+                       [
+                               '-62142076800',
+                               TS_RFC2822,
+                               'Wed, 18 Oct 0000 00:00:00 GMT',
+                               'ISO 8601:2004 [[year 0]], also called [[1 BC]]'
+                       ],
+               ];
+       }
+
+       /**
+        * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
+        * @dataProvider provideHttpDates
+        */
+       public function testHttpDate( $input, $output, $desc ) {
+               $this->assertEquals( $output, wfTimestamp( TS_MW, $input ), $desc );
+       }
+
+       public static function provideHttpDates() {
+               return [
+                       [ 'Sun, 06 Nov 1994 08:49:37 GMT', '19941106084937', 'RFC 822 date' ],
+                       [ 'Sunday, 06-Nov-94 08:49:37 GMT', '19941106084937', 'RFC 850 date' ],
+                       [ 'Sun Nov  6 08:49:37 1994', '19941106084937', "ANSI C's asctime() format" ],
+                       // See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html and r77171
+                       [
+                               'Mon, 22 Nov 2010 14:12:42 GMT; length=52626',
+                               '20101122141242',
+                               'Netscape extension to HTTP/1.0'
+                       ],
+               ];
+       }
+
+       /**
+        * There are a number of assumptions in our codebase where wfTimestamp()
+        * should give the current date but it is not given a 0 there. See r71751 CR
+        */
+       public function testTimestampParameter() {
+               $now = wfTimestamp( TS_UNIX );
+               // We check that wfTimestamp doesn't return false (error) and use a LessThan assert
+               // for the cases where the test is run in a second boundary.
+
+               $zero = wfTimestamp( TS_UNIX, 0 );
+               $this->assertNotEquals( false, $zero );
+               $this->assertLessThan( 5, $zero - $now );
+
+               $empty = wfTimestamp( TS_UNIX, '' );
+               $this->assertNotEquals( false, $empty );
+               $this->assertLessThan( 5, $empty - $now );
+
+               $null = wfTimestamp( TS_UNIX, null );
+               $this->assertNotEquals( false, $null );
+               $this->assertLessThan( 5, $null - $now );
+       }
+}
diff --git a/tests/phpunit/unit/includes/GlobalFunctions/wfUrlencodeTest.php b/tests/phpunit/unit/includes/GlobalFunctions/wfUrlencodeTest.php
new file mode 100644 (file)
index 0000000..2fc038c
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * The function only need a string parameter and might react to IIS7.0
+ *
+ * @group GlobalFunctions
+ * @covers ::wfUrlencode
+ */
+class WfUrlencodeTest extends MediaWikiUnitTestCase {
+       # ### TESTS ##############################################################
+
+       /**
+        * @dataProvider provideURLS
+        */
+       public function testEncodingUrlWith( $input, $expected ) {
+               $this->verifyEncodingFor( 'Apache', $input, $expected );
+       }
+
+       /**
+        * @dataProvider provideURLS
+        */
+       public function testEncodingUrlWithMicrosoftIis7( $input, $expected ) {
+               $this->verifyEncodingFor( 'Microsoft-IIS/7', $input, $expected );
+       }
+
+       # ### HELPERS #############################################################
+
+       /**
+        * Internal helper that actually run the test.
+        * Called by the public methods testEncodingUrlWith...()
+        */
+       private function verifyEncodingFor( $server, $input, $expectations ) {
+               $expected = $this->extractExpect( $server, $expectations );
+
+               // save up global
+               $old = $_SERVER['SERVER_SOFTWARE'] ?? null;
+               $_SERVER['SERVER_SOFTWARE'] = $server;
+               wfUrlencode( null );
+
+               // do the requested test
+               $this->assertEquals(
+                       $expected,
+                       wfUrlencode( $input ),
+                       "Encoding '$input' for server '$server' should be '$expected'"
+               );
+
+               // restore global
+               if ( $old === null ) {
+                       unset( $_SERVER['SERVER_SOFTWARE'] );
+               } else {
+                       $_SERVER['SERVER_SOFTWARE'] = $old;
+               }
+               wfUrlencode( null );
+       }
+
+       /**
+        * Interprets the provider array. Return expected value depending
+        * the HTTP server name.
+        */
+       private function extractExpect( $server, $expectations ) {
+               if ( is_string( $expectations ) ) {
+                       return $expectations;
+               } elseif ( is_array( $expectations ) ) {
+                       if ( !array_key_exists( $server, $expectations ) ) {
+                               throw new MWException( __METHOD__ . " expectation does not have any "
+                                       . "value for server name $server. Check the provider array.\n" );
+                       } else {
+                               return $expectations[$server];
+                       }
+               } else {
+                       throw new MWException( __METHOD__ . " given invalid expectation for "
+                               . "'$server'. Should be a string or an array [ <http server name> => <string> ].\n" );
+               }
+       }
+
+       # ### PROVIDERS ###########################################################
+
+       /**
+        * Format is either:
+        *   [ 'input', 'expected' ];
+        * Or:
+        *   [ 'input',
+        *       [ 'Apache', 'expected' ],
+        *       [ 'Microsoft-IIS/7', 'expected' ],
+        *   ],
+        * If you want to add other HTTP server name, you will have to add a new
+        * testing method much like the testEncodingUrlWith() method above.
+        */
+       public static function provideURLS() {
+               return [
+                       # ## RFC 1738 chars
+                       // + is not safe
+                       [ '+', '%2B' ],
+                       // & and = not safe in queries
+                       [ '&', '%26' ],
+                       [ '=', '%3D' ],
+
+                       [ ':', [
+                               'Apache' => ':',
+                               'Microsoft-IIS/7' => '%3A',
+                       ] ],
+
+                       // remaining chars do not need encoding
+                       [
+                               ';@$-_.!*',
+                               ';@$-_.!*',
+                       ],
+
+                       # ## Other tests
+                       // slash remain unchanged. %2F seems to break things
+                       [ '/', '/' ],
+                       // T105265
+                       [ '~', '~' ],
+
+                       // Other 'funnies' chars
+                       [ '[]', '%5B%5D' ],
+                       [ '<>', '%3C%3E' ],
+
+                       // Apostrophe is encoded
+                       [ '\'', '%27' ],
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/PathRouterTest.php b/tests/phpunit/unit/includes/PathRouterTest.php
new file mode 100644 (file)
index 0000000..fb23706
--- /dev/null
@@ -0,0 +1,325 @@
+<?php
+
+/**
+ * Tests for the PathRouter parsing.
+ *
+ * @covers PathRouter
+ */
+class PathRouterTest extends MediaWikiUnitTestCase {
+
+       /**
+        * @var PathRouter
+        */
+       protected $basicRouter;
+
+       protected function setUp() {
+               parent::setUp();
+               $router = new PathRouter;
+               $router->add( "/wiki/$1" );
+               $this->basicRouter = $router;
+       }
+
+       public static function provideParse() {
+               $tests = [
+                       // Basic path parsing
+                       'Basic path parsing' => [
+                               "/wiki/$1",
+                               "/wiki/Foo",
+                               [ 'title' => "Foo" ]
+                       ],
+                       //
+                       'Loose path auto-$1: /$1' => [
+                               "/",
+                               "/Foo",
+                               [ 'title' => "Foo" ]
+                       ],
+                       'Loose path auto-$1: /wiki' => [
+                               "/wiki",
+                               "/wiki/Foo",
+                               [ 'title' => "Foo" ]
+                       ],
+                       'Loose path auto-$1: /wiki/' => [
+                               "/wiki/",
+                               "/wiki/Foo",
+                               [ 'title' => "Foo" ]
+                       ],
+                       // Ensure that path is based on specificity, not order
+                       'Order, /$1 added first' => [
+                               [ "/$1", "/a/$1", "/b/$1" ],
+                               "/a/Foo",
+                               [ 'title' => "Foo" ]
+                       ],
+                       'Order, /$1 added last' => [
+                               [ "/b/$1", "/a/$1", "/$1" ],
+                               "/a/Foo",
+                               [ 'title' => "Foo" ]
+                       ],
+                       // Handling of key based arrays with a url parameter
+                       'Key based array' => [
+                               [ [
+                                       'path' => [ 'edit' => "/edit/$1" ],
+                                       'params' => [ 'action' => '$key' ],
+                               ] ],
+                               "/edit/Foo",
+                               [ 'title' => "Foo", 'action' => 'edit' ]
+                       ],
+                       // Additional parameter
+                       'Basic $2' => [
+                               [ [
+                                       'path' => '/$2/$1',
+                                       'params' => [ 'test' => '$2' ]
+                               ] ],
+                               "/asdf/Foo",
+                               [ 'title' => "Foo", 'test' => 'asdf' ]
+                       ],
+               ];
+               // Shared patterns for restricted value parameter tests
+               $restrictedPatterns = [
+                       [
+                               'path' => '/$2/$1',
+                               'params' => [ 'test' => '$2' ],
+                               'options' => [ '$2' => [ 'a', 'b' ] ]
+                       ],
+                       [
+                               'path' => '/$2/$1',
+                               'params' => [ 'test2' => '$2' ],
+                               'options' => [ '$2' => 'c' ]
+                       ],
+                       '/$1'
+               ];
+               $tests += [
+                       // Restricted value parameter tests
+                       'Restricted 1' => [
+                               $restrictedPatterns,
+                               "/asdf/Foo",
+                               [ 'title' => "asdf/Foo" ]
+                       ],
+                       'Restricted 2' => [
+                               $restrictedPatterns,
+                               "/a/Foo",
+                               [ 'title' => "Foo", 'test' => 'a' ]
+                       ],
+                       'Restricted 3' => [
+                               $restrictedPatterns,
+                               "/c/Foo",
+                               [ 'title' => "Foo", 'test2' => 'c' ]
+                       ],
+
+                       // Callback test
+                       'Callback' => [
+                               [ [
+                                       'path' => "/$1",
+                                       'params' => [ 'a' => 'b', 'data:foo' => 'bar' ],
+                                       'options' => [ 'callback' => [ __CLASS__, 'callbackForTest' ] ]
+                               ] ],
+                               '/Foo',
+                               [
+                                       'title' => "Foo",
+                                       'x' => 'Foo',
+                                       'a' => 'b',
+                                       'foo' => 'bar'
+                               ]
+                       ],
+
+                       // Test to ensure that matches are not made if a parameter expects nonexistent input
+                       'Fail' => [
+                               [ [
+                                       'path' => "/wiki/$1",
+                                       'params' => [ 'title' => "$1$2" ],
+                               ] ],
+                               "/wiki/A",
+                               []
+                       ],
+
+                       // Make sure the router handles titles like Special:Recentchanges correctly
+                       'Special title' => [
+                               "/wiki/$1",
+                               "/wiki/Special:Recentchanges",
+                               [ 'title' => "Special:Recentchanges" ]
+                       ],
+
+                       // Make sure the router decodes urlencoding properly
+                       'URL encoding' => [
+                               "/wiki/$1",
+                               "/wiki/Title_With%20Space",
+                               [ 'title' => "Title_With Space" ]
+                       ],
+
+                       // Double slash and dot expansion
+                       'Double slash in prefix' => [
+                               '/wiki/$1',
+                               '//wiki/Foo',
+                               [ 'title' => 'Foo' ]
+                       ],
+                       'Double slash at start of $1' => [
+                               '/wiki/$1',
+                               '/wiki//Foo',
+                               [ 'title' => '/Foo' ]
+                       ],
+                       'Double slash in middle of $1' => [
+                               '/wiki/$1',
+                               '/wiki/.hack//SIGN',
+                               [ 'title' => '.hack//SIGN' ]
+                       ],
+                       'Dots removed 1' => [
+                               '/wiki/$1',
+                               '/x/../wiki/Foo',
+                               [ 'title' => 'Foo' ]
+                       ],
+                       'Dots removed 2' => [
+                               '/wiki/$1',
+                               '/./wiki/Foo',
+                               [ 'title' => 'Foo' ]
+                       ],
+                       'Dots retained 1' => [
+                               '/wiki/$1',
+                               '/wiki/../wiki/Foo',
+                               [ 'title' => '../wiki/Foo' ]
+                       ],
+                       'Dots retained 2' => [
+                               '/wiki/$1',
+                               '/wiki/./Foo',
+                               [ 'title' => './Foo' ]
+                       ],
+                       'Triple slash' => [
+                               '/wiki/$1',
+                               '///wiki/Foo',
+                               [ 'title' => 'Foo' ]
+                       ],
+                       // '..' only traverses one slash, see e.g. RFC 3986
+                       'Dots traversing double slash 1' => [
+                               '/wiki/$1',
+                               '/a//b/../../wiki/Foo',
+                               []
+                       ],
+                       'Dots traversing double slash 2' => [
+                               '/wiki/$1',
+                               '/a//b/../../../wiki/Foo',
+                               [ 'title' => 'Foo' ]
+                       ],
+               ];
+
+               // Make sure the router doesn't break on special characters like $ used in regexp replacements
+               foreach ( [ "$", "$1", "\\", "\\$1" ] as $char ) {
+                       $tests["Regexp character $char"] = [
+                               "/wiki/$1",
+                               "/wiki/$char",
+                               [ 'title' => "$char" ]
+                       ];
+               }
+
+               $tests += [
+                       // Make sure the router handles characters like +&() properly
+                       "Special characters" => [
+                               "/wiki/$1",
+                               "/wiki/Plus+And&Dollar\\Stuff();[]{}*",
+                               [ 'title' => "Plus+And&Dollar\\Stuff();[]{}*" ],
+                       ],
+
+                       // Make sure the router handles unicode characters correctly
+                       "Unicode 1" => [
+                               "/wiki/$1",
+                               "/wiki/Spécial:Modifications_récentes" ,
+                               [ 'title' => "Spécial:Modifications_récentes" ],
+                       ],
+
+                       "Unicode 2" => [
+                               "/wiki/$1",
+                               "/wiki/Sp%C3%A9cial:Modifications_r%C3%A9centes",
+                               [ 'title' => "Spécial:Modifications_récentes" ],
+                       ]
+               ];
+
+               // Ensure the router doesn't choke on long paths.
+               $lorem = "Lorem_ipsum_dolor_sit_amet,_consectetur_adipisicing_elit,_sed_do_eiusmod_" .
+                       "tempor_incididunt_ut_labore_et_dolore_magna_aliqua._Ut_enim_ad_minim_veniam,_quis_" .
+                        "nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat._" .
+                        "Duis_aute_irure_dolor_in_reprehenderit_in_voluptate_velit_esse_cillum_dolore_" .
+                        "eu_fugiat_nulla_pariatur._Excepteur_sint_occaecat_cupidatat_non_proident,_sunt_" .
+                        "in_culpa_qui_officia_deserunt_mollit_anim_id_est_laborum.";
+
+               $tests += [
+                       "Long path" => [
+                               "/wiki/$1",
+                               "/wiki/$lorem",
+                               [ 'title' => $lorem ]
+                       ],
+
+                       // Ensure that the php passed site of parameter values are not urldecoded
+                       "Pattern urlencoding" => [
+                               [ [ 'path' => "/wiki/$1", 'params' => [ 'title' => '%20:$1' ] ] ],
+                               "/wiki/Foo",
+                               [ 'title' => '%20:Foo' ]
+                       ],
+
+                       // Ensure that raw parameter values do not have any variable replacements or urldecoding
+                       "Raw param value" => [
+                               [ [ 'path' => "/wiki/$1", 'params' => [ 'title' => [ 'value' => 'bar%20$1' ] ] ] ],
+                               "/wiki/Foo",
+                               [ 'title' => 'bar%20$1' ]
+                       ]
+               ];
+
+               return $tests;
+       }
+
+       /**
+        * Test path parsing
+        * @dataProvider provideParse
+        */
+       public function testParse( $patterns, $path, $expected ) {
+               $patterns = (array)$patterns;
+
+               $router = new PathRouter;
+               foreach ( $patterns as $pattern ) {
+                       if ( is_array( $pattern ) ) {
+                               $router->add( $pattern['path'], $pattern['params'] ?? [],
+                                       $pattern['options'] ?? [] );
+                       } else {
+                               $router->add( $pattern );
+                       }
+               }
+               $matches = $router->parse( $path );
+               $this->assertEquals( $matches, $expected );
+       }
+
+       public static function callbackForTest( &$matches, $data ) {
+               $matches['x'] = $data['$1'];
+               $matches['foo'] = $data['foo'];
+       }
+
+       public static function provideWeight() {
+               return [
+                       [ '/Foo', [ 'title' => 'Foo' ] ],
+                       [ '/Bar', [ 'ping' => 'pong' ] ],
+                       [ '/Baz', [ 'marco' => 'polo' ] ],
+                       [ '/asdf-foo', [ 'title' => 'qwerty-foo' ] ],
+                       [ '/qwerty-bar', [ 'title' => 'asdf-bar' ] ],
+                       [ '/a/Foo', [ 'title' => 'Foo' ] ],
+                       [ '/asdf/Foo', [ 'title' => 'Foo' ] ],
+                       [ '/qwerty/Foo', [ 'title' => 'Foo', 'qwerty' => 'qwerty' ] ],
+                       [ '/baz/Foo', [ 'title' => 'Foo', 'unrestricted' => 'baz' ] ],
+                       [ '/y/Foo', [ 'title' => 'Foo', 'restricted-to-y' => 'y' ] ],
+               ];
+       }
+
+       /**
+        * Test to ensure weight of paths is handled correctly
+        * @dataProvider provideWeight
+        */
+       public function testWeight( $path, $expected ) {
+               $router = new PathRouter;
+               $router->addStrict( "/Bar", [ 'ping' => 'pong' ] );
+               $router->add( "/asdf-$1", [ 'title' => 'qwerty-$1' ] );
+               $router->add( "/$1" );
+               $router->add( "/qwerty-$1", [ 'title' => 'asdf-$1' ] );
+               $router->addStrict( "/Baz", [ 'marco' => 'polo' ] );
+               $router->add( "/a/$1" );
+               $router->add( "/asdf/$1" );
+               $router->add( "/$2/$1", [ 'unrestricted' => '$2' ] );
+               $router->add( [ 'qwerty' => "/qwerty/$1" ], [ 'qwerty' => '$key' ] );
+               $router->add( "/$2/$1", [ 'restricted-to-y' => '$2' ], [ '$2' => 'y' ] );
+
+               $this->assertEquals( $router->parse( $path ), $expected );
+       }
+}
diff --git a/tests/phpunit/unit/includes/Rest/ResponseFactoryTest.php b/tests/phpunit/unit/includes/Rest/ResponseFactoryTest.php
new file mode 100644 (file)
index 0000000..04d54de
--- /dev/null
@@ -0,0 +1,146 @@
+<?php
+
+namespace MediaWiki\Tests\Rest;
+
+use ArrayIterator;
+use MediaWiki\Rest\HttpException;
+use MediaWiki\Rest\ResponseFactory;
+use MediaWikiUnitTestCase;
+
+/** @covers \MediaWiki\Rest\ResponseFactory */
+class ResponseFactoryTest extends MediaWikiUnitTestCase {
+       public static function provideEncodeJson() {
+               return [
+                       [ (object)[], '{}' ],
+                       [ '/', '"/"' ],
+                       [ '£', '"£"' ],
+                       [ [], '[]' ],
+               ];
+       }
+
+       /** @dataProvider provideEncodeJson */
+       public function testEncodeJson( $input, $expected ) {
+               $rf = new ResponseFactory;
+               $this->assertSame( $expected, $rf->encodeJson( $input ) );
+       }
+
+       public function testCreateJson() {
+               $rf = new ResponseFactory;
+               $response = $rf->createJson( [] );
+               $response->getBody()->rewind();
+               $this->assertSame( 'application/json', $response->getHeaderLine( 'Content-Type' ) );
+               $this->assertSame( '[]', $response->getBody()->getContents() );
+               // Make sure getSize() is functional, since testCreateNoContent() depends on it
+               $this->assertSame( 2, $response->getBody()->getSize() );
+       }
+
+       public function testCreateNoContent() {
+               $rf = new ResponseFactory;
+               $response = $rf->createNoContent();
+               $this->assertSame( [], $response->getHeader( 'Content-Type' ) );
+               $this->assertSame( 0, $response->getBody()->getSize() );
+               $this->assertSame( 204, $response->getStatusCode() );
+       }
+
+       public function testCreatePermanentRedirect() {
+               $rf = new ResponseFactory;
+               $response = $rf->createPermanentRedirect( 'http://www.example.com/' );
+               $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
+               $this->assertSame( 301, $response->getStatusCode() );
+       }
+
+       public function testCreateLegacyTemporaryRedirect() {
+               $rf = new ResponseFactory;
+               $response = $rf->createLegacyTemporaryRedirect( 'http://www.example.com/' );
+               $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
+               $this->assertSame( 302, $response->getStatusCode() );
+       }
+
+       public function testCreateTemporaryRedirect() {
+               $rf = new ResponseFactory;
+               $response = $rf->createTemporaryRedirect( 'http://www.example.com/' );
+               $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
+               $this->assertSame( 307, $response->getStatusCode() );
+       }
+
+       public function testCreateSeeOther() {
+               $rf = new ResponseFactory;
+               $response = $rf->createSeeOther( 'http://www.example.com/' );
+               $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
+               $this->assertSame( 303, $response->getStatusCode() );
+       }
+
+       public function testCreateNotModified() {
+               $rf = new ResponseFactory;
+               $response = $rf->createNotModified();
+               $this->assertSame( 0, $response->getBody()->getSize() );
+               $this->assertSame( 304, $response->getStatusCode() );
+       }
+
+       /** @expectedException \InvalidArgumentException */
+       public function testCreateHttpErrorInvalid() {
+               $rf = new ResponseFactory;
+               $rf->createHttpError( 200 );
+       }
+
+       public function testCreateHttpError() {
+               $rf = new ResponseFactory;
+               $response = $rf->createHttpError( 415, [ 'message' => '...' ] );
+               $this->assertSame( 415, $response->getStatusCode() );
+               $body = $response->getBody();
+               $body->rewind();
+               $data = json_decode( $body->getContents(), true );
+               $this->assertSame( 415, $data['httpCode'] );
+               $this->assertSame( '...', $data['message'] );
+       }
+
+       public function testCreateFromExceptionUnlogged() {
+               $rf = new ResponseFactory;
+               $response = $rf->createFromException( new HttpException( 'hello', 415 ) );
+               $this->assertSame( 415, $response->getStatusCode() );
+               $body = $response->getBody();
+               $body->rewind();
+               $data = json_decode( $body->getContents(), true );
+               $this->assertSame( 415, $data['httpCode'] );
+               $this->assertSame( 'hello', $data['message'] );
+       }
+
+       public function testCreateFromExceptionLogged() {
+               $rf = new ResponseFactory;
+               $response = $rf->createFromException( new \Exception( "hello", 415 ) );
+               $this->assertSame( 500, $response->getStatusCode() );
+               $body = $response->getBody();
+               $body->rewind();
+               $data = json_decode( $body->getContents(), true );
+               $this->assertSame( 500, $data['httpCode'] );
+               $this->assertSame( 'Error: exception of type Exception', $data['message'] );
+       }
+
+       public static function provideCreateFromReturnValue() {
+               return [
+                       [ 'hello', '{"value":"hello"}' ],
+                       [ true, '{"value":true}' ],
+                       [ [ 'x' => 'y' ], '{"x":"y"}' ],
+                       [ [ 'x', 'y' ], '["x","y"]' ],
+                       [ [ 'a', 'x' => 'y' ], '{"0":"a","x":"y"}' ],
+                       [ (object)[ 'a', 'x' => 'y' ], '{"0":"a","x":"y"}' ],
+                       [ [], '[]' ],
+                       [ (object)[], '{}' ],
+               ];
+       }
+
+       /** @dataProvider provideCreateFromReturnValue */
+       public function testCreateFromReturnValue( $input, $expected ) {
+               $rf = new ResponseFactory;
+               $response = $rf->createFromReturnValue( $input );
+               $body = $response->getBody();
+               $body->rewind();
+               $this->assertSame( $expected, $body->getContents() );
+       }
+
+       /** @expectedException \InvalidArgumentException */
+       public function testCreateFromReturnValueInvalid() {
+               $rf = new ResponseFactory;
+               $rf->createFromReturnValue( new ArrayIterator );
+       }
+}
diff --git a/tests/phpunit/unit/includes/Revision/MainSlotRoleHandlerTest.php b/tests/phpunit/unit/includes/Revision/MainSlotRoleHandlerTest.php
new file mode 100644 (file)
index 0000000..9dff2cc
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use MediaWiki\Revision\MainSlotRoleHandler;
+use MediaWikiUnitTestCase;
+use PHPUnit\Framework\MockObject\MockObject;
+use Title;
+
+/**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler
+ */
+class MainSlotRoleHandlerTest extends MediaWikiUnitTestCase {
+
+       private function makeTitleObject( $ns ) {
+               /** @var Title|MockObject $title */
+               $title = $this->getMockBuilder( Title::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $title->method( 'getNamespace' )
+                       ->willReturn( $ns );
+
+               return $title;
+       }
+
+       /**
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::__construct
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getRole()
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getNameMessageKey()
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getOutputLayoutHints()
+        */
+       public function testConstruction() {
+               $handler = new MainSlotRoleHandler( [] );
+               $this->assertSame( 'main', $handler->getRole() );
+               $this->assertSame( 'slot-name-main', $handler->getNameMessageKey() );
+
+               $hints = $handler->getOutputLayoutHints();
+               $this->assertArrayHasKey( 'display', $hints );
+               $this->assertArrayHasKey( 'region', $hints );
+               $this->assertArrayHasKey( 'placement', $hints );
+       }
+
+       /**
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getDefaultModel()
+        */
+       public function testFetDefaultModel() {
+               $handler = new MainSlotRoleHandler( [ 100 => CONTENT_MODEL_TEXT ] );
+
+               // For the main handler, the namespace determins the default model
+               $titleMain = $this->makeTitleObject( NS_MAIN );
+               $this->assertSame( CONTENT_MODEL_WIKITEXT, $handler->getDefaultModel( $titleMain ) );
+
+               $title100 = $this->makeTitleObject( 100 );
+               $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title100 ) );
+       }
+
+       /**
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::isAllowedModel()
+        */
+       public function testIsAllowedModel() {
+               $handler = new MainSlotRoleHandler( [] );
+
+               // For the main handler, (nearly) all models are allowed
+               $title = $this->makeTitleObject( NS_MAIN );
+               $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_WIKITEXT, $title ) );
+               $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_TEXT, $title ) );
+       }
+
+       /**
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::supportsArticleCount()
+        */
+       public function testSupportsArticleCount() {
+               $handler = new MainSlotRoleHandler( [] );
+
+               $this->assertTrue( $handler->supportsArticleCount() );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/Revision/SlotRecordTest.php b/tests/phpunit/unit/includes/Revision/SlotRecordTest.php
new file mode 100644 (file)
index 0000000..aab430a
--- /dev/null
@@ -0,0 +1,416 @@
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use InvalidArgumentException;
+use LogicException;
+use MediaWiki\Revision\IncompleteRevisionException;
+use MediaWiki\Revision\SlotRecord;
+use MediaWiki\Revision\SuppressedDataException;
+use MediaWikiUnitTestCase;
+use WikitextContent;
+
+/**
+ * @covers \MediaWiki\Revision\SlotRecord
+ */
+class SlotRecordTest extends MediaWikiUnitTestCase {
+
+       private function makeRow( $data = [] ) {
+               $data = $data + [
+                       'slot_id' => 1234,
+                       'slot_content_id' => 33,
+                       'content_size' => '5',
+                       'content_sha1' => 'someHash',
+                       'content_address' => 'tt:456',
+                       'model_name' => CONTENT_MODEL_WIKITEXT,
+                       'format_name' => CONTENT_FORMAT_WIKITEXT,
+                       'slot_revision_id' => '2',
+                       'slot_origin' => '1',
+                       'role_name' => 'myRole',
+               ];
+               return (object)$data;
+       }
+
+       public function testCompleteConstruction() {
+               $row = $this->makeRow();
+               $record = new SlotRecord( $row, new WikitextContent( 'A' ) );
+
+               $this->assertTrue( $record->hasAddress() );
+               $this->assertTrue( $record->hasContentId() );
+               $this->assertTrue( $record->hasRevision() );
+               $this->assertTrue( $record->isInherited() );
+               $this->assertSame( 'A', $record->getContent()->getText() );
+               $this->assertSame( 5, $record->getSize() );
+               $this->assertSame( 'someHash', $record->getSha1() );
+               $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
+               $this->assertSame( 2, $record->getRevision() );
+               $this->assertSame( 1, $record->getOrigin() );
+               $this->assertSame( 'tt:456', $record->getAddress() );
+               $this->assertSame( 33, $record->getContentId() );
+               $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
+               $this->assertSame( 'myRole', $record->getRole() );
+       }
+
+       public function testConstructionDeferred() {
+               $row = $this->makeRow( [
+                       'content_size' => null, // to be computed
+                       'content_sha1' => null, // to be computed
+                       'format_name' => function () {
+                               return CONTENT_FORMAT_WIKITEXT;
+                       },
+                       'slot_revision_id' => '2',
+                       'slot_origin' => '2',
+                       'slot_content_id' => function () {
+                               return null;
+                       },
+               ] );
+
+               $content = function () {
+                       return new WikitextContent( 'A' );
+               };
+
+               $record = new SlotRecord( $row, $content );
+
+               $this->assertTrue( $record->hasAddress() );
+               $this->assertTrue( $record->hasRevision() );
+               $this->assertFalse( $record->hasContentId() );
+               $this->assertFalse( $record->isInherited() );
+               $this->assertSame( 'A', $record->getContent()->getText() );
+               $this->assertSame( 1, $record->getSize() );
+               $this->assertNotEmpty( $record->getSha1() );
+               $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
+               $this->assertSame( 2, $record->getRevision() );
+               $this->assertSame( 2, $record->getRevision() );
+               $this->assertSame( 'tt:456', $record->getAddress() );
+               $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
+               $this->assertSame( 'myRole', $record->getRole() );
+       }
+
+       public function testNewUnsaved() {
+               $record = SlotRecord::newUnsaved( 'myRole', new WikitextContent( 'A' ) );
+
+               $this->assertFalse( $record->hasAddress() );
+               $this->assertFalse( $record->hasContentId() );
+               $this->assertFalse( $record->hasRevision() );
+               $this->assertFalse( $record->isInherited() );
+               $this->assertFalse( $record->hasOrigin() );
+               $this->assertSame( 'A', $record->getContent()->getText() );
+               $this->assertSame( 1, $record->getSize() );
+               $this->assertNotEmpty( $record->getSha1() );
+               $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
+               $this->assertSame( 'myRole', $record->getRole() );
+       }
+
+       public function provideInvalidConstruction() {
+               yield 'both null' => [ null, null ];
+               yield 'null row' => [ null, new WikitextContent( 'A' ) ];
+               yield 'array row' => [ [], new WikitextContent( 'A' ) ];
+               yield 'empty row' => [ (object)[], new WikitextContent( 'A' ) ];
+               yield 'null content' => [ (object)[], null ];
+       }
+
+       /**
+        * @dataProvider provideInvalidConstruction
+        */
+       public function testInvalidConstruction( $row, $content ) {
+               $this->setExpectedException( InvalidArgumentException::class );
+               new SlotRecord( $row, $content );
+       }
+
+       public function testGetContentId_fails() {
+               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+               $this->setExpectedException( IncompleteRevisionException::class );
+
+               $record->getContentId();
+       }
+
+       public function testGetAddress_fails() {
+               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+               $this->setExpectedException( IncompleteRevisionException::class );
+
+               $record->getAddress();
+       }
+
+       public function provideIncomplete() {
+               $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+               yield 'unsaved' => [ $unsaved ];
+
+               $parent = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
+               $inherited = SlotRecord::newInherited( $parent );
+               yield 'inherited' => [ $inherited ];
+       }
+
+       /**
+        * @dataProvider provideIncomplete
+        */
+       public function testGetRevision_fails( SlotRecord $record ) {
+               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+               $this->setExpectedException( IncompleteRevisionException::class );
+
+               $record->getRevision();
+       }
+
+       /**
+        * @dataProvider provideIncomplete
+        */
+       public function testGetOrigin_fails( SlotRecord $record ) {
+               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+               $this->setExpectedException( IncompleteRevisionException::class );
+
+               $record->getOrigin();
+       }
+
+       public function provideHashStability() {
+               yield [ '', 'phoiac9h4m842xq45sp7s6u21eteeq1' ];
+               yield [ 'Lorem ipsum', 'hcr5u40uxr81d3nx89nvwzclfz6r9c5' ];
+       }
+
+       /**
+        * @dataProvider provideHashStability
+        */
+       public function testHashStability( $text, $hash ) {
+               // Changing the output of the hash function will break things horribly!
+
+               $this->assertSame( $hash, SlotRecord::base36Sha1( $text ) );
+
+               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( $text ) );
+               $this->assertSame( $hash, $record->getSha1() );
+       }
+
+       public function testHashComputed() {
+               $row = $this->makeRow();
+               $row->content_sha1 = '';
+
+               $rec = new SlotRecord( $row, new WikitextContent( 'A' ) );
+               $this->assertNotEmpty( $rec->getSha1() );
+       }
+
+       public function testNewWithSuppressedContent() {
+               $input = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
+               $output = SlotRecord::newWithSuppressedContent( $input );
+
+               $this->setExpectedException( SuppressedDataException::class );
+               $output->getContent();
+       }
+
+       public function testNewInherited() {
+               $row = $this->makeRow( [ 'slot_revision_id' => 7, 'slot_origin' => 7 ] );
+               $parent = new SlotRecord( $row, new WikitextContent( 'A' ) );
+
+               // This would happen while doing an edit, before saving revision meta-data.
+               $inherited = SlotRecord::newInherited( $parent );
+
+               $this->assertSame( $parent->getContentId(), $inherited->getContentId() );
+               $this->assertSame( $parent->getAddress(), $inherited->getAddress() );
+               $this->assertSame( $parent->getContent(), $inherited->getContent() );
+               $this->assertTrue( $inherited->isInherited() );
+               $this->assertTrue( $inherited->hasOrigin() );
+               $this->assertFalse( $inherited->hasRevision() );
+
+               // make sure we didn't mess with the internal state of $parent
+               $this->assertFalse( $parent->isInherited() );
+               $this->assertSame( 7, $parent->getRevision() );
+
+               // This would happen while doing an edit, after saving the revision meta-data
+               // and content meta-data.
+               $saved = SlotRecord::newSaved(
+                       10,
+                       $inherited->getContentId(),
+                       $inherited->getAddress(),
+                       $inherited
+               );
+               $this->assertSame( $parent->getContentId(), $saved->getContentId() );
+               $this->assertSame( $parent->getAddress(), $saved->getAddress() );
+               $this->assertSame( $parent->getContent(), $saved->getContent() );
+               $this->assertTrue( $saved->isInherited() );
+               $this->assertTrue( $saved->hasRevision() );
+               $this->assertSame( 10, $saved->getRevision() );
+
+               // make sure we didn't mess with the internal state of $parent or $inherited
+               $this->assertSame( 7, $parent->getRevision() );
+               $this->assertFalse( $inherited->hasRevision() );
+       }
+
+       public function testNewSaved() {
+               // This would happen while doing an edit, before saving revision meta-data.
+               $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+
+               // This would happen while doing an edit, after saving the revision meta-data
+               // and content meta-data.
+               $saved = SlotRecord::newSaved( 10, 20, 'theNewAddress', $unsaved );
+               $this->assertFalse( $saved->isInherited() );
+               $this->assertTrue( $saved->hasOrigin() );
+               $this->assertTrue( $saved->hasRevision() );
+               $this->assertTrue( $saved->hasAddress() );
+               $this->assertTrue( $saved->hasContentId() );
+               $this->assertSame( 'theNewAddress', $saved->getAddress() );
+               $this->assertSame( 20, $saved->getContentId() );
+               $this->assertSame( 'A', $saved->getContent()->getText() );
+               $this->assertSame( 10, $saved->getRevision() );
+               $this->assertSame( 10, $saved->getOrigin() );
+
+               // make sure we didn't mess with the internal state of $unsaved
+               $this->assertFalse( $unsaved->hasAddress() );
+               $this->assertFalse( $unsaved->hasContentId() );
+               $this->assertFalse( $unsaved->hasRevision() );
+       }
+
+       public function provideNewSaved_LogicException() {
+               $freshRow = $this->makeRow( [
+                       'content_id' => 10,
+                       'content_address' => 'address:1',
+                       'slot_origin' => 1,
+                       'slot_revision_id' => 1,
+               ] );
+
+               $freshSlot = new SlotRecord( $freshRow, new WikitextContent( 'A' ) );
+               yield 'mismatching address' => [ 1, 10, 'address:BAD', $freshSlot ];
+               yield 'mismatching revision' => [ 5, 10, 'address:1', $freshSlot ];
+               yield 'mismatching content ID' => [ 1, 17, 'address:1', $freshSlot ];
+
+               $inheritedRow = $this->makeRow( [
+                       'content_id' => null,
+                       'content_address' => null,
+                       'slot_origin' => 0,
+                       'slot_revision_id' => 1,
+               ] );
+
+               $inheritedSlot = new SlotRecord( $inheritedRow, new WikitextContent( 'A' ) );
+               yield 'inherited, but no address' => [ 1, 10, 'address:2', $inheritedSlot ];
+       }
+
+       /**
+        * @dataProvider provideNewSaved_LogicException
+        */
+       public function testNewSaved_LogicException(
+               $revisionId,
+               $contentId,
+               $contentAddress,
+               SlotRecord $protoSlot
+       ) {
+               $this->setExpectedException( LogicException::class );
+               SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
+       }
+
+       public function provideNewSaved_InvalidArgumentException() {
+               $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+
+               yield 'bad revision id' => [ 'xyzzy', 5, 'address', $unsaved ];
+               yield 'bad content id' => [ 7, 'xyzzy', 'address', $unsaved ];
+               yield 'bad content address' => [ 7, 5, 77, $unsaved ];
+       }
+
+       /**
+        * @dataProvider provideNewSaved_InvalidArgumentException
+        */
+       public function testNewSaved_InvalidArgumentException(
+               $revisionId,
+               $contentId,
+               $contentAddress,
+               SlotRecord $protoSlot
+       ) {
+               $this->setExpectedException( InvalidArgumentException::class );
+               SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
+       }
+
+       public function provideHasSameContent() {
+               $fail = function () {
+                       self::fail( 'There should be no need to actually load the content.' );
+               };
+
+               $a100a1 = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'A',
+                                       'content_size' => 100,
+                                       'content_sha1' => 'hash-a',
+                                       'content_address' => 'xxx:a1',
+                               ]
+                       ),
+                       $fail
+               );
+               $a100a1b = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'A',
+                                       'content_size' => 100,
+                                       'content_sha1' => 'hash-a',
+                                       'content_address' => 'xxx:a1',
+                               ]
+                       ),
+                       $fail
+               );
+               $a100null = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'A',
+                                       'content_size' => 100,
+                                       'content_sha1' => 'hash-a',
+                                       'content_address' => null,
+                               ]
+                       ),
+                       $fail
+               );
+               $a100a2 = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'A',
+                                       'content_size' => 100,
+                                       'content_sha1' => 'hash-a',
+                                       'content_address' => 'xxx:a2',
+                               ]
+                       ),
+                       $fail
+               );
+               $b100a1 = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'B',
+                                       'content_size' => 100,
+                                       'content_sha1' => 'hash-a',
+                                       'content_address' => 'xxx:a1',
+                               ]
+                       ),
+                       $fail
+               );
+               $a200a1 = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'A',
+                                       'content_size' => 200,
+                                       'content_sha1' => 'hash-a',
+                                       'content_address' => 'xxx:a2',
+                               ]
+                       ),
+                       $fail
+               );
+               $a100x1 = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'A',
+                                       'content_size' => 100,
+                                       'content_sha1' => 'hash-x',
+                                       'content_address' => 'xxx:x1',
+                               ]
+                       ),
+                       $fail
+               );
+
+               yield 'same instance' => [ $a100a1, $a100a1, true ];
+               yield 'no address' => [ $a100a1, $a100null, true ];
+               yield 'same address' => [ $a100a1, $a100a1b, true ];
+               yield 'different address' => [ $a100a1, $a100a2, true ];
+               yield 'different model' => [ $a100a1, $b100a1, false ];
+               yield 'different size' => [ $a100a1, $a200a1, false ];
+               yield 'different hash' => [ $a100a1, $a100x1, false ];
+       }
+
+       /**
+        * @dataProvider provideHasSameContent
+        */
+       public function testHasSameContent( SlotRecord $a, SlotRecord $b, $sameContent ) {
+               $this->assertSame( $sameContent, $a->hasSameContent( $b ) );
+               $this->assertSame( $sameContent, $b->hasSameContent( $a ) );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/TitleArrayFromResultTest.php b/tests/phpunit/unit/includes/TitleArrayFromResultTest.php
new file mode 100644 (file)
index 0000000..10b7d36
--- /dev/null
@@ -0,0 +1,115 @@
+<?php
+
+/**
+ * @author Addshore
+ * @covers TitleArrayFromResult
+ */
+class TitleArrayFromResultTest extends MediaWikiUnitTestCase {
+
+       private function getMockResultWrapper( $row = null, $numRows = 1 ) {
+               $resultWrapper = $this->getMockBuilder( Wikimedia\Rdbms\ResultWrapper::class )
+                       ->disableOriginalConstructor();
+
+               $resultWrapper = $resultWrapper->getMock();
+               $resultWrapper->expects( $this->atLeastOnce() )
+                       ->method( 'current' )
+                       ->will( $this->returnValue( $row ) );
+               $resultWrapper->expects( $this->any() )
+                       ->method( 'numRows' )
+                       ->will( $this->returnValue( $numRows ) );
+
+               return $resultWrapper;
+       }
+
+       private function getRowWithTitle( $namespace = 3, $title = 'foo' ) {
+               $row = new stdClass();
+               $row->page_namespace = $namespace;
+               $row->page_title = $title;
+               return $row;
+       }
+
+       /**
+        * @covers TitleArrayFromResult::__construct
+        */
+       public function testConstructionWithFalseRow() {
+               $row = false;
+               $resultWrapper = $this->getMockResultWrapper( $row );
+
+               $object = new TitleArrayFromResult( $resultWrapper );
+
+               $this->assertEquals( $resultWrapper, $object->res );
+               $this->assertSame( 0, $object->key );
+               $this->assertEquals( $row, $object->current );
+       }
+
+       /**
+        * @covers TitleArrayFromResult::__construct
+        */
+       public function testConstructionWithRow() {
+               $namespace = 0;
+               $title = 'foo';
+               $row = $this->getRowWithTitle( $namespace, $title );
+               $resultWrapper = $this->getMockResultWrapper( $row );
+
+               $object = new TitleArrayFromResult( $resultWrapper );
+
+               $this->assertEquals( $resultWrapper, $object->res );
+               $this->assertSame( 0, $object->key );
+               $this->assertInstanceOf( Title::class, $object->current );
+               $this->assertEquals( $namespace, $object->current->mNamespace );
+               $this->assertEquals( $title, $object->current->mTextform );
+       }
+
+       public static function provideNumberOfRows() {
+               return [
+                       [ 0 ],
+                       [ 1 ],
+                       [ 122 ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideNumberOfRows
+        * @covers TitleArrayFromResult::count
+        */
+       public function testCountWithVaryingValues( $numRows ) {
+               $object = new TitleArrayFromResult( $this->getMockResultWrapper(
+                       $this->getRowWithTitle(),
+                       $numRows
+               ) );
+               $this->assertEquals( $numRows, $object->count() );
+       }
+
+       /**
+        * @covers TitleArrayFromResult::current
+        */
+       public function testCurrentAfterConstruction() {
+               $namespace = 0;
+               $title = 'foo';
+               $row = $this->getRowWithTitle( $namespace, $title );
+               $object = new TitleArrayFromResult( $this->getMockResultWrapper( $row ) );
+               $this->assertInstanceOf( Title::class, $object->current() );
+               $this->assertEquals( $namespace, $object->current->mNamespace );
+               $this->assertEquals( $title, $object->current->mTextform );
+       }
+
+       public function provideTestValid() {
+               return [
+                       [ $this->getRowWithTitle(), true ],
+                       [ false, false ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideTestValid
+        * @covers TitleArrayFromResult::valid
+        */
+       public function testValid( $input, $expected ) {
+               $object = new TitleArrayFromResult( $this->getMockResultWrapper( $input ) );
+               $this->assertEquals( $expected, $object->valid() );
+       }
+
+       // @todo unit test for key()
+       // @todo unit test for next()
+       // @todo unit test for rewind()
+}
diff --git a/tests/phpunit/unit/includes/WikiReferenceTest.php b/tests/phpunit/unit/includes/WikiReferenceTest.php
new file mode 100644 (file)
index 0000000..a4aae86
--- /dev/null
@@ -0,0 +1,164 @@
+<?php
+
+/**
+ * @covers WikiReference
+ */
+class WikiReferenceTest extends MediaWikiUnitTestCase {
+
+       public function provideGetDisplayName() {
+               return [
+                       'http' => [ 'foo.bar', 'http://foo.bar' ],
+                       'https' => [ 'foo.bar', 'http://foo.bar' ],
+
+                       // apparently, this is the expected behavior
+                       'invalid' => [ 'purple kittens', 'purple kittens' ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideGetDisplayName
+        */
+       public function testGetDisplayName( $expected, $canonicalServer ) {
+               $reference = new WikiReference( $canonicalServer, '/wiki/$1' );
+               $this->assertEquals( $expected, $reference->getDisplayName() );
+       }
+
+       public function testGetCanonicalServer() {
+               $reference = new WikiReference( 'https://acme.com', '/wiki/$1', '//acme.com' );
+               $this->assertEquals( 'https://acme.com', $reference->getCanonicalServer() );
+       }
+
+       public function provideGetCanonicalUrl() {
+               return [
+                       'no fragment' => [
+                               'https://acme.com/wiki/Foo',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               null
+                       ],
+                       'empty fragment' => [
+                               'https://acme.com/wiki/Foo',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               ''
+                       ],
+                       'fragment' => [
+                               'https://acme.com/wiki/Foo#Bar',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               'Bar'
+                       ],
+                       'double fragment' => [
+                               'https://acme.com/wiki/Foo#Bar%23Xus',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               'Bar#Xus'
+                       ],
+                       'escaped fragment' => [
+                               'https://acme.com/wiki/Foo%23Bar',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo#Bar',
+                               null
+                       ],
+                       'empty path' => [
+                               'https://acme.com/Foo',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/$1',
+                               'Foo',
+                               null
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideGetCanonicalUrl
+        */
+       public function testGetCanonicalUrl(
+               $expected, $canonicalServer, $server, $path, $page, $fragmentId
+       ) {
+               $reference = new WikiReference( $canonicalServer, $path, $server );
+               $this->assertEquals( $expected, $reference->getCanonicalUrl( $page, $fragmentId ) );
+       }
+
+       /**
+        * @dataProvider provideGetCanonicalUrl
+        * @note getUrl is an alias for getCanonicalUrl
+        */
+       public function testGetUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
+               $reference = new WikiReference( $canonicalServer, $path, $server );
+               $this->assertEquals( $expected, $reference->getUrl( $page, $fragmentId ) );
+       }
+
+       public function provideGetFullUrl() {
+               return [
+                       'no fragment' => [
+                               '//acme.com/wiki/Foo',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               null
+                       ],
+                       'empty fragment' => [
+                               '//acme.com/wiki/Foo',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               ''
+                       ],
+                       'fragment' => [
+                               '//acme.com/wiki/Foo#Bar',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               'Bar'
+                       ],
+                       'double fragment' => [
+                               '//acme.com/wiki/Foo#Bar%23Xus',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               'Bar#Xus'
+                       ],
+                       'escaped fragment' => [
+                               '//acme.com/wiki/Foo%23Bar',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo#Bar',
+                               null
+                       ],
+                       'empty path' => [
+                               '//acme.com/Foo',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/$1',
+                               'Foo',
+                               null
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideGetFullUrl
+        */
+       public function testGetFullUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
+               $reference = new WikiReference( $canonicalServer, $path, $server );
+               $this->assertEquals( $expected, $reference->getFullUrl( $page, $fragmentId ) );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/debug/logger/monolog/CeeFormatterTest.php b/tests/phpunit/unit/includes/debug/logger/monolog/CeeFormatterTest.php
new file mode 100644 (file)
index 0000000..946a1a2
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+namespace MediaWiki\Logger\Monolog;
+
+/**
+ * Flay per https://phabricator.wikimedia.org/T218688.
+ *
+ * @group Broken
+ * @covers \MediaWiki\Logger\Monolog\CeeFormatter
+ */
+class CeeFormatterTest extends \MediaWikiUnitTestCase {
+       public function testV1() {
+               $ls_formatter = new LogstashFormatter( 'app', 'system', null, 'ctx_', LogstashFormatter::V1 );
+               $cee_formatter = new CeeFormatter( 'app', 'system', null, 'ctx_', LogstashFormatter::V1 );
+               $record = [ 'extra' => [ 'url' => 1 ], 'context' => [ 'url' => 2 ] ];
+               $this->assertSame(
+                       $cee_formatter->format( $record ),
+                       "@cee: " . $ls_formatter->format( $record ) );
+       }
+}
diff --git a/tests/phpunit/unit/includes/diff/DifferenceEngineSlotDiffRendererTest.php b/tests/phpunit/unit/includes/diff/DifferenceEngineSlotDiffRendererTest.php
new file mode 100644 (file)
index 0000000..1a8b585
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @covers DifferenceEngineSlotDiffRenderer
+ */
+class DifferenceEngineSlotDiffRendererTest extends \MediaWikiUnitTestCase {
+
+       public function testGetDiff() {
+               $differenceEngine = new CustomDifferenceEngine();
+               $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+               $oldContent = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT );
+               $newContent = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT );
+
+               $diff = $slotDiffRenderer->getDiff( $oldContent, $newContent );
+               $this->assertEquals( 'xxx|yyy', $diff );
+
+               $diff = $slotDiffRenderer->getDiff( null, $newContent );
+               $this->assertEquals( '|yyy', $diff );
+
+               $diff = $slotDiffRenderer->getDiff( $oldContent, null );
+               $this->assertEquals( 'xxx|', $diff );
+       }
+
+       public function testAddModules() {
+               $output = $this->getMockBuilder( OutputPage::class )
+                       ->disableOriginalConstructor()
+                       ->setMethods( [ 'addModules' ] )
+                       ->getMock();
+               $output->expects( $this->once() )
+                       ->method( 'addModules' )
+                       ->with( 'foo' );
+               $differenceEngine = new CustomDifferenceEngine();
+               $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+               $slotDiffRenderer->addModules( $output );
+       }
+
+       public function testGetExtraCacheKeys() {
+               $differenceEngine = new CustomDifferenceEngine();
+               $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+               $extraCacheKeys = $slotDiffRenderer->getExtraCacheKeys();
+               $this->assertSame( [ 'foo' ], $extraCacheKeys );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/diff/SlotDiffRendererTest.php b/tests/phpunit/unit/includes/diff/SlotDiffRendererTest.php
new file mode 100644 (file)
index 0000000..f778115
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+
+use Wikimedia\Assert\ParameterTypeException;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @covers SlotDiffRenderer
+ */
+class SlotDiffRendererTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @dataProvider provideNormalizeContents
+        */
+       public function testNormalizeContents(
+               $oldContent, $newContent, $allowedClasses,
+               $expectedOldContent, $expectedNewContent, $expectedExceptionClass
+       ) {
+               $slotDiffRenderer = $this->getMockBuilder( SlotDiffRenderer::class )
+                       ->getMock();
+               try {
+                       // __call needs help deciding which parameter to take by reference
+                       call_user_func_array( [ TestingAccessWrapper::newFromObject( $slotDiffRenderer ),
+                               'normalizeContents' ], [ &$oldContent, &$newContent, $allowedClasses ] );
+                       $this->assertEquals( $expectedOldContent, $oldContent );
+                       $this->assertEquals( $expectedNewContent, $newContent );
+               } catch ( Exception $e ) {
+                       if ( !$expectedExceptionClass ) {
+                               throw $e;
+                       }
+                       $this->assertInstanceOf( $expectedExceptionClass, $e );
+               }
+       }
+
+       public function provideNormalizeContents() {
+               return [
+                       'both null' => [ null, null, null, null, null, InvalidArgumentException::class ],
+                       'left null' => [
+                               null, new WikitextContent( 'abc' ), null,
+                               new WikitextContent( '' ), new WikitextContent( 'abc' ), null,
+                       ],
+                       'right null' => [
+                               new WikitextContent( 'def' ), null, null,
+                               new WikitextContent( 'def' ), new WikitextContent( '' ), null,
+                       ],
+                       'type filter' => [
+                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
+                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
+                       ],
+                       'type filter (subclass)' => [
+                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), TextContent::class,
+                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
+                       ],
+                       'type filter (null)' => [
+                               new WikitextContent( 'abc' ), null, TextContent::class,
+                               new WikitextContent( 'abc' ), new WikitextContent( '' ), null,
+                       ],
+                       'type filter failure (left)' => [
+                               new TextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
+                               null, null, ParameterTypeException::class,
+                       ],
+                       'type filter failure (right)' => [
+                               new WikitextContent( 'abc' ), new TextContent( 'def' ), WikitextContent::class,
+                               null, null, ParameterTypeException::class,
+                       ],
+                       'type filter (array syntax)' => [
+                               new WikitextContent( 'abc' ), new JsonContent( 'def' ),
+                               [ JsonContent::class, WikitextContent::class ],
+                               new WikitextContent( 'abc' ), new JsonContent( 'def' ), null,
+                       ],
+                       'type filter failure (array syntax)' => [
+                               new WikitextContent( 'abc' ), new CssContent( 'def' ),
+                               [ JsonContent::class, WikitextContent::class ],
+                               null, null, ParameterTypeException::class,
+                       ],
+               ];
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/filerepo/FileBackendDBRepoWrapperTest.php b/tests/phpunit/unit/includes/filerepo/FileBackendDBRepoWrapperTest.php
new file mode 100644 (file)
index 0000000..6084601
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+
+class FileBackendDBRepoWrapperTest extends MediaWikiUnitTestCase {
+       protected $backendName = 'foo-backend';
+       protected $repoName = 'pureTestRepo';
+
+       /**
+        * @dataProvider getBackendPathsProvider
+        * @covers FileBackendDBRepoWrapper::getBackendPaths
+        */
+       public function testGetBackendPaths(
+               $mocks,
+               $latest,
+               $dbReadsExpected,
+               $dbReturnValue,
+               $originalPath,
+               $expectedBackendPath,
+               $message ) {
+               list( $dbMock, $backendMock, $wrapperMock ) = $mocks;
+
+               $dbMock->expects( $dbReadsExpected )
+                       ->method( 'selectField' )
+                       ->will( $this->returnValue( $dbReturnValue ) );
+
+               $newPaths = $wrapperMock->getBackendPaths( [ $originalPath ], $latest );
+
+               $this->assertEquals(
+                       $expectedBackendPath,
+                       $newPaths[0],
+                       $message );
+       }
+
+       public function getBackendPathsProvider() {
+               $prefix = 'mwstore://' . $this->backendName . '/' . $this->repoName;
+               $mocksForCaching = $this->getMocks();
+
+               return [
+                       [
+                               $mocksForCaching,
+                               false,
+                               $this->once(),
+                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               $prefix . '-public/f/o/foobar.jpg',
+                               $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               'Public path translated correctly',
+                       ],
+                       [
+                               $mocksForCaching,
+                               false,
+                               $this->never(),
+                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               $prefix . '-public/f/o/foobar.jpg',
+                               $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               'LRU cache leveraged',
+                       ],
+                       [
+                               $this->getMocks(),
+                               true,
+                               $this->once(),
+                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               $prefix . '-public/f/o/foobar.jpg',
+                               $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               'Latest obtained',
+                       ],
+                       [
+                               $this->getMocks(),
+                               true,
+                               $this->never(),
+                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               $prefix . '-deleted/f/o/foobar.jpg',
+                               $prefix . '-original/f/o/o/foobar',
+                               'Deleted path translated correctly',
+                       ],
+                       [
+                               $this->getMocks(),
+                               true,
+                               $this->once(),
+                               null,
+                               $prefix . '-public/b/a/baz.jpg',
+                               $prefix . '-public/b/a/baz.jpg',
+                               'Path left untouched if no sha1 can be found',
+                       ],
+               ];
+       }
+
+       /**
+        * @covers FileBackendDBRepoWrapper::getFileContentsMulti
+        */
+       public function testGetFileContentsMulti() {
+               list( $dbMock, $backendMock, $wrapperMock ) = $this->getMocks();
+
+               $sha1Path = 'mwstore://' . $this->backendName . '/' . $this->repoName
+                       . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9';
+               $filenamePath = 'mwstore://' . $this->backendName . '/' . $this->repoName
+                       . '-public/f/o/foobar.jpg';
+
+               $dbMock->expects( $this->once() )
+                       ->method( 'selectField' )
+                       ->will( $this->returnValue( '96246614d75ba1703bdfd5d7660bb57407aaf5d9' ) );
+
+               $backendMock->expects( $this->once() )
+                       ->method( 'getFileContentsMulti' )
+                       ->will( $this->returnValue( [ $sha1Path => 'foo' ] ) );
+
+               $result = $wrapperMock->getFileContentsMulti( [ 'srcs' => [ $filenamePath ] ] );
+
+               $this->assertEquals(
+                       [ $filenamePath => 'foo' ],
+                       $result,
+                       'File contents paths translated properly'
+               );
+       }
+
+       protected function getMocks() {
+               $dbMock = $this->getMockBuilder( Wikimedia\Rdbms\IDatabase::class )
+                       ->disableOriginalClone()
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $backendMock = $this->getMockBuilder( FSFileBackend::class )
+                       ->setConstructorArgs( [ [
+                                       'name' => $this->backendName,
+                                       'wikiId' => wfWikiID()
+                               ] ] )
+                       ->getMock();
+
+               $wrapperMock = $this->getMockBuilder( FileBackendDBRepoWrapper::class )
+                       ->setMethods( [ 'getDB' ] )
+                       ->setConstructorArgs( [ [
+                                       'backend' => $backendMock,
+                                       'repoName' => $this->repoName,
+                                       'dbHandleFactory' => null
+                               ] ] )
+                       ->getMock();
+
+               $wrapperMock->expects( $this->any() )->method( 'getDB' )->will( $this->returnValue( $dbMock ) );
+
+               return [ $dbMock, $backendMock, $wrapperMock ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/filerepo/file/ForeignDBFileTest.php b/tests/phpunit/unit/includes/filerepo/file/ForeignDBFileTest.php
new file mode 100644 (file)
index 0000000..32624aa
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+/** @covers ForeignDBFile */
+class ForeignDBFileTest extends \MediaWikiUnitTestCase {
+
+       public function testShouldConstructCorrectInstanceFromTitle() {
+               $title = Title::makeTitle( NS_FILE, 'Awesome_file' );
+               $repoMock = $this->createMock( LocalRepo::class );
+
+               $file = ForeignDBFile::newFromTitle( $title, $repoMock );
+
+               $this->assertInstanceOf( ForeignDBFile::class, $file );
+       }
+}
diff --git a/tests/phpunit/unit/includes/htmlform/HTMLCheckMatrixTest.php b/tests/phpunit/unit/includes/htmlform/HTMLCheckMatrixTest.php
new file mode 100644 (file)
index 0000000..659d48d
--- /dev/null
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @covers HTMLCheckMatrix
+ */
+class HTMLCheckMatrixTest extends MediaWikiUnitTestCase {
+       private static $defaultOptions = [
+               'rows' => [ 'r1', 'r2' ],
+               'columns' => [ 'c1', 'c2' ],
+               'fieldname' => 'test',
+       ];
+
+       public function testPlainInstantiation() {
+               try {
+                       new HTMLCheckMatrix( [] );
+               } catch ( MWException $e ) {
+                       $this->assertInstanceOf( HTMLFormFieldRequiredOptionsException::class, $e );
+                       return;
+               }
+
+               $this->fail( 'Expected MWException indicating missing parameters but none was thrown.' );
+       }
+
+       public function testInstantiationWithMinimumRequiredParameters() {
+               new HTMLCheckMatrix( self::$defaultOptions );
+               $this->assertTrue( true ); // form instantiation must throw exception on failure
+       }
+
+       public function testValidateCallsUserDefinedValidationCallback() {
+               $called = false;
+               $field = new HTMLCheckMatrix( self::$defaultOptions + [
+                       'validation-callback' => function () use ( &$called ) {
+                               $called = true;
+
+                               return false;
+                       },
+               ] );
+               $this->assertEquals( false, $this->validate( $field, [] ) );
+               $this->assertTrue( $called );
+       }
+
+       public function testValidateRequiresArrayInput() {
+               $field = new HTMLCheckMatrix( self::$defaultOptions );
+               $this->assertEquals( false, $this->validate( $field, null ) );
+               $this->assertEquals( false, $this->validate( $field, true ) );
+               $this->assertEquals( false, $this->validate( $field, 'abc' ) );
+               $this->assertEquals( false, $this->validate( $field, new stdClass ) );
+               $this->assertEquals( true, $this->validate( $field, [] ) );
+       }
+
+       public function testValidateAllowsOnlyKnownTags() {
+               $field = new HTMLCheckMatrix( self::$defaultOptions );
+               $this->assertInstanceOf( Message::class, $this->validate( $field, [ 'foo' ] ) );
+       }
+
+       public function testValidateAcceptsPartialTagList() {
+               $field = new HTMLCheckMatrix( self::$defaultOptions );
+               $this->assertTrue( $this->validate( $field, [] ) );
+               $this->assertTrue( $this->validate( $field, [ 'c1-r1' ] ) );
+               $this->assertTrue( $this->validate( $field, [ 'c1-r1', 'c1-r2', 'c2-r1', 'c2-r2' ] ) );
+       }
+
+       /**
+        * This form object actually has no visibility into what happens later on, but essentially
+        * if the data submitted by the user passes validate the following is run:
+        * foreach ( $field->filterDataForSubmit( $data ) as $k => $v ) {
+        *     $user->setOption( $k, $v );
+        * }
+        */
+       public function testValuesForcedOnRemainOn() {
+               $field = new HTMLCheckMatrix( self::$defaultOptions + [
+                               'force-options-on' => [ 'c2-r1' ],
+                       ] );
+               $expected = [
+                       'c1-r1' => false,
+                       'c1-r2' => false,
+                       'c2-r1' => true,
+                       'c2-r2' => false,
+               ];
+               $this->assertEquals( $expected, $field->filterDataForSubmit( [] ) );
+       }
+
+       public function testValuesForcedOffRemainOff() {
+               $field = new HTMLCheckMatrix( self::$defaultOptions + [
+                               'force-options-off' => [ 'c1-r2', 'c2-r2' ],
+                       ] );
+               $expected = [
+                       'c1-r1' => true,
+                       'c1-r2' => false,
+                       'c2-r1' => true,
+                       'c2-r2' => false,
+               ];
+               // array_keys on the result simulates submitting all fields checked
+               $this->assertEquals( $expected, $field->filterDataForSubmit( array_keys( $expected ) ) );
+       }
+
+       protected function validate( HTMLFormField $field, $submitted ) {
+               return $field->validate(
+                       $submitted,
+                       [ self::$defaultOptions['fieldname'] => $submitted ]
+               );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/json/FormatJsonTest.php b/tests/phpunit/unit/includes/json/FormatJsonTest.php
new file mode 100644 (file)
index 0000000..94c7d91
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * @covers FormatJson
+ */
+class FormatJsonTest extends MediaWikiUnitTestCase {
+
+       /**
+        * Test data for testParseTryFixing.
+        *
+        * Some PHP interpreters use json-c rather than the JSON.org canonical
+        * parser to avoid being encumbered by the "shall be used for Good, not
+        * Evil" clause of the JSON.org parser's license. By default, json-c
+        * parses in a non-strict mode which allows trailing commas for array and
+        * object delarations among other things, so our JSON_ERROR_SYNTAX rescue
+        * block is not always triggered. It however isn't lenient in exactly the
+        * same ways as our TRY_FIXING mode, so the assertions in this test are
+        * a bit more complicated than they ideally would be:
+        *
+        * Optional third argument: true if json-c parses the value without
+        * intervention, false otherwise. Defaults to true.
+        *
+        * Optional fourth argument: expected cannonical JSON serialization of
+        * json-c parsed result. Defaults to the second argument's value.
+        */
+       public static function provideParseTryFixing() {
+               return [
+                       [ "[,]", '[]', false ],
+                       [ "[ , ]", '[]', false ],
+                       [ "[ , }", false ],
+                       [ '[1],', false, true, '[1]' ],
+                       [ "[1,]", '[1]' ],
+                       [ "[1\n,]", '[1]' ],
+                       [ "[1,\n]", '[1]' ],
+                       [ "[1,]\n", '[1]' ],
+                       [ "[1\n,\n]\n", '[1]' ],
+                       [ '["a,",]', '["a,"]' ],
+                       [ "[[1,]\n,[2,\n],[3\n,]]", '[[1],[2],[3]]' ],
+                       // I wish we could parse this, but would need quote parsing
+                       [ '[[1,],[2,],[3,]]', false, true, '[[1],[2],[3]]' ],
+                       [ '[1,,]', false, false, '[1]' ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideParseTryFixing
+        * @param string $value
+        * @param string|bool $expected Expected result with strict parser
+        * @param bool $jsoncParses Will json-c parse this value without TRY_FIXING?
+        * @param string|bool $expectedJsonc Expected result with lenient parser
+        * if different from the strict expectation
+        */
+       public function testParseTryFixing(
+               $value, $expected,
+               $jsoncParses = true, $expectedJsonc = null
+       ) {
+               // PHP5 results are always expected to have isGood() === false
+               $expectedGoodStatus = false;
+
+               // Check to see if json parser allows trailing commas
+               if ( json_decode( '[1,]' ) !== null ) {
+                       // Use json-c specific expected result if provided
+                       $expected = ( $expectedJsonc === null ) ? $expected : $expectedJsonc;
+                       // If json-c parses the value natively, expect isGood() === true
+                       $expectedGoodStatus = $jsoncParses;
+               }
+
+               $st = FormatJson::parse( $value, FormatJson::TRY_FIXING );
+               $this->assertInstanceOf( Status::class, $st );
+               if ( $expected === false ) {
+                       $this->assertFalse( $st->isOK(), 'Expected isOK() == false' );
+               } else {
+                       $this->assertSame( $expectedGoodStatus, $st->isGood(),
+                               'Expected isGood() == ' . ( $expectedGoodStatus ? 'true' : 'false' )
+                       );
+                       $this->assertTrue( $st->isOK(), 'Expected isOK == true' );
+                       $val = FormatJson::encode( $st->getValue(), false, FormatJson::ALL_OK );
+                       $this->assertEquals( $expected, $val );
+               }
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/media/JpegMetadataExtractorTest.php b/tests/phpunit/unit/includes/media/JpegMetadataExtractorTest.php
new file mode 100644 (file)
index 0000000..365c140
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+/**
+ * @todo Could use a test of extended XMP segments. Hard to find programs that
+ * create example files, and creating my own in vim propbably wouldn't
+ * serve as a very good "test". (Adobe photoshop probably creates such files
+ * but it costs money). The implementation of it currently in MediaWiki is based
+ * solely on reading the standard, without any real world test files.
+ *
+ * @group Media
+ * @covers JpegMetadataExtractor
+ */
+class JpegMetadataExtractorTest extends MediaWikiUnitTestCase {
+
+       protected $filePath;
+
+       protected function setUp() {
+               parent::setUp();
+
+               $this->filePath = __DIR__ . '/../../../data/media/';
+       }
+
+       /**
+        * We also use this test to test padding bytes don't
+        * screw stuff up
+        *
+        * @param string $file Filename
+        *
+        * @dataProvider provideUtf8Comment
+        */
+       public function testUtf8Comment( $file ) {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . $file );
+               $this->assertEquals( [ 'UTF-8 JPEG Comment — ¼' ], $res['COM'] );
+       }
+
+       public static function provideUtf8Comment() {
+               return [
+                       [ 'jpeg-comment-utf.jpg' ],
+                       [ 'jpeg-padding-even.jpg' ],
+                       [ 'jpeg-padding-odd.jpg' ],
+               ];
+       }
+
+       /** The file is iso-8859-1, but it should get auto converted */
+       public function testIso88591Comment() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-iso8859-1.jpg' );
+               $this->assertEquals( [ 'ISO-8859-1 JPEG Comment - ¼' ], $res['COM'] );
+       }
+
+       /** Comment values that are non-textual (random binary junk) should not be shown.
+        * The example test file has a comment with a 0x5 byte in it which is a control character
+        * and considered binary junk for our purposes.
+        */
+       public function testBinaryCommentStripped() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-binary.jpg' );
+               $this->assertEmpty( $res['COM'] );
+       }
+
+       /* Very rarely a file can have multiple comments.
+        *   Order of comments is based on order inside the file.
+        */
+       public function testMultipleComment() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-multiple.jpg' );
+               $this->assertEquals( [ 'foo', 'bar' ], $res['COM'] );
+       }
+
+       public function testXMPExtraction() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+               $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
+               $this->assertEquals( $expected, $res['XMP'] );
+       }
+
+       public function testPSIRExtraction() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+               $expected = '50686f746f73686f7020332e30003842494d04040000000'
+                       . '000181c02190004746573741c02190003666f6f1c020000020004';
+               $this->assertEquals( $expected, bin2hex( $res['PSIR'][0] ) );
+       }
+
+       public function testXMPExtractionAltAppId() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-alt.jpg' );
+               $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
+               $this->assertEquals( $expected, $res['XMP'] );
+       }
+
+       public function testIPTCHashComparisionNoHash() {
+               $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+               $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+               $this->assertEquals( 'iptc-no-hash', $res );
+       }
+
+       public function testIPTCHashComparisionBadHash() {
+               $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' );
+               $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+               $this->assertEquals( 'iptc-bad-hash', $res );
+       }
+
+       public function testIPTCHashComparisionGoodHash() {
+               $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-good-hash.jpg' );
+               $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+               $this->assertEquals( 'iptc-good-hash', $res );
+       }
+
+       public function testExifByteOrder() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'exif-user-comment.jpg' );
+               $expected = 'BE';
+               $this->assertEquals( $expected, $res['byteOrder'] );
+       }
+
+       public function testInfiniteRead() {
+               // test file truncated right after a segment, which previously
+               // caused an infinite loop looking for the next segment byte.
+               // Should get past infinite loop and throw in wfUnpack()
+               $this->setExpectedException( 'MWException' );
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop1.jpg' );
+       }
+
+       public function testInfiniteRead2() {
+               // test file truncated after a segment's marker and size, which
+               // would cause a seek past end of file. Seek past end of file
+               // doesn't actually fail, but prevents further reading and was
+               // devolving into the previous case (testInfiniteRead).
+               $this->setExpectedException( 'MWException' );
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop2.jpg' );
+       }
+}
diff --git a/tests/phpunit/unit/includes/page/ArticleTest.php b/tests/phpunit/unit/includes/page/ArticleTest.php
new file mode 100644 (file)
index 0000000..9995793
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+
+class ArticleTest extends MediaWikiUnitTestCase {
+
+       /**
+        * @var Title
+        */
+       private $title;
+       /**
+        * @var Article
+        */
+       private $article;
+
+       /** creates a title object and its article object */
+       protected function setUp() {
+               parent::setUp();
+               $this->title = Title::makeTitle( NS_MAIN, 'SomePage' );
+               $this->article = new Article( $this->title );
+       }
+
+       /** cleanup title object and its article object */
+       protected function tearDown() {
+               parent::tearDown();
+               $this->title = null;
+               $this->article = null;
+       }
+
+       /**
+        * @covers Article::__get
+        */
+       public function testImplementsGetMagic() {
+               $this->assertEquals( false, $this->article->mLatest, "Article __get magic" );
+       }
+
+       /**
+        * @depends testImplementsGetMagic
+        * @covers Article::__set
+        */
+       public function testImplementsSetMagic() {
+               $this->article->mLatest = 2;
+               $this->assertEquals( 2, $this->article->mLatest, "Article __set magic" );
+       }
+
+       /**
+        * @covers Article::__get
+        * @covers Article::__set
+        */
+       public function testGetOrSetOnNewProperty() {
+               $this->article->ext_someNewProperty = 12;
+               $this->assertEquals( 12, $this->article->ext_someNewProperty,
+                       "Article get/set magic on new field" );
+
+               $this->article->ext_someNewProperty = -8;
+               $this->assertEquals( -8, $this->article->ext_someNewProperty,
+                       "Article get/set magic on update to new field" );
+       }
+}
diff --git a/tests/phpunit/unit/includes/session/TokenTest.php b/tests/phpunit/unit/includes/session/TokenTest.php
new file mode 100644 (file)
index 0000000..5546603
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+
+namespace MediaWiki\Session;
+
+use MediaWikiUnitTestCase;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @group Session
+ * @covers MediaWiki\Session\Token
+ */
+class TokenTest extends MediaWikiUnitTestCase {
+
+       public function testBasics() {
+               $token = $this->getMockBuilder( Token::class )
+                       ->setMethods( [ 'toStringAtTimestamp' ] )
+                       ->setConstructorArgs( [ 'sekret', 'salty', true ] )
+                       ->getMock();
+               $token->expects( $this->any() )->method( 'toStringAtTimestamp' )
+                       ->will( $this->returnValue( 'faketoken+\\' ) );
+
+               $this->assertSame( 'faketoken+\\', $token->toString() );
+               $this->assertSame( 'faketoken+\\', (string)$token );
+               $this->assertTrue( $token->wasNew() );
+
+               $token = new Token( 'sekret', 'salty', false );
+               $this->assertFalse( $token->wasNew() );
+       }
+
+       public function testToStringAtTimestamp() {
+               $token = TestingAccessWrapper::newFromObject( new Token( 'sekret', 'salty', false ) );
+
+               $this->assertSame(
+                       'd9ade0c7d4349e9df9094e61c33a5a0d5644fde2+\\',
+                       $token->toStringAtTimestamp( 1447362018 )
+               );
+               $this->assertSame(
+                       'ee2f7a2488dea9176c224cfb400d43be5644fdea+\\',
+                       $token->toStringAtTimestamp( 1447362026 )
+               );
+       }
+
+       public function testGetTimestamp() {
+               $this->assertSame(
+                       1447362018, Token::getTimestamp( 'd9ade0c7d4349e9df9094e61c33a5a0d5644fde2+\\' )
+               );
+               $this->assertSame(
+                       1447362026, Token::getTimestamp( 'ee2f7a2488dea9176c224cfb400d43be5644fdea+\\' )
+               );
+               $this->assertNull( Token::getTimestamp( 'ee2f7a2488dea9176c224cfb400d43be5644fdea-\\' ) );
+               $this->assertNull( Token::getTimestamp( 'ee2f7a2488dea9176c224cfb400d43be+\\' ) );
+
+               $this->assertNull( Token::getTimestamp( 'ee2f7a2488dea9x76c224cfb400d43be5644fdea+\\' ) );
+       }
+
+       public function testMatch() {
+               $token = TestingAccessWrapper::newFromObject( new Token( 'sekret', 'salty', false ) );
+
+               $test = $token->toStringAtTimestamp( time() - 10 );
+               $this->assertTrue( $token->match( $test ) );
+               $this->assertTrue( $token->match( $test, 12 ) );
+               $this->assertFalse( $token->match( $test, 8 ) );
+
+               $this->assertFalse( $token->match( 'ee2f7a2488dea9176c224cfb400d43be5644fdea-\\' ) );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/shell/FirejailCommandTest.php b/tests/phpunit/unit/includes/shell/FirejailCommandTest.php
new file mode 100644 (file)
index 0000000..e3a3b86
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * Copyright (C) 2017 Kunal Mehta <legoktm@member.fsf.org>
+ *
+ * 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.
+ *
+ */
+
+use MediaWiki\Shell\FirejailCommand;
+use MediaWiki\Shell\Shell;
+use Wikimedia\TestingAccessWrapper;
+
+class FirejailCommandTest extends MediaWikiUnitTestCase {
+
+       public function provideBuildFinalCommand() {
+               global $IP;
+               // phpcs:ignore Generic.Files.LineLength
+               $env = "'MW_INCLUDE_STDERR=;MW_CPU_LIMIT=180; MW_CGROUP='\'''\''; MW_MEM_LIMIT=307200; MW_FILE_SIZE_LIMIT=102400; MW_WALL_CLOCK_LIMIT=180; MW_USE_LOG_PIPE=yes'";
+               $limit = "/bin/bash '$IP/includes/shell/limit.sh'";
+               $profile = "--profile=$IP/includes/shell/firejail.profile";
+               $blacklist = '--blacklist=' . realpath( MW_CONFIG_FILE );
+               $default = "$blacklist --noroot --seccomp --private-dev";
+               return [
+                       [
+                               'No restrictions',
+                               'ls', 0, "$limit ''\''ls'\''' $env"
+                       ],
+                       [
+                               'default restriction',
+                               'ls', Shell::RESTRICT_DEFAULT,
+                               "$limit 'firejail --quiet $profile $default -- '\''ls'\''' $env"
+                       ],
+                       [
+                               'no network',
+                               'ls', Shell::NO_NETWORK,
+                               "$limit 'firejail --quiet $profile --net=none -- '\''ls'\''' $env"
+                       ],
+                       [
+                               'default restriction & no network',
+                               'ls', Shell::RESTRICT_DEFAULT | Shell::NO_NETWORK,
+                               "$limit 'firejail --quiet $profile $default --net=none -- '\''ls'\''' $env"
+                       ],
+                       [
+                               'seccomp',
+                               'ls', Shell::SECCOMP,
+                               "$limit 'firejail --quiet $profile --seccomp -- '\''ls'\''' $env"
+                       ],
+                       [
+                               'seccomp & no execve',
+                               'ls', Shell::SECCOMP | Shell::NO_EXECVE,
+                               "$limit 'firejail --quiet $profile --shell=none --seccomp=execve -- '\''ls'\''' $env"
+                       ],
+               ];
+       }
+
+       /**
+        * @covers \MediaWiki\Shell\FirejailCommand::buildFinalCommand()
+        * @dataProvider provideBuildFinalCommand
+        */
+       public function testBuildFinalCommand( $desc, $params, $flags, $expected ) {
+               $command = new FirejailCommand( 'firejail' );
+               $command
+                       ->params( $params )
+                       ->restrict( $flags );
+               $wrapper = TestingAccessWrapper::newFromObject( $command );
+               $output = $wrapper->buildFinalCommand( $wrapper->command );
+               $this->assertEquals( $expected, $output[0], $desc );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/site/MediaWikiPageNameNormalizerTest.php b/tests/phpunit/unit/includes/site/MediaWikiPageNameNormalizerTest.php
new file mode 100644 (file)
index 0000000..d426306
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+
+use MediaWiki\Site\MediaWikiPageNameNormalizer;
+
+/**
+ * @covers MediaWiki\Site\MediaWikiPageNameNormalizer
+ *
+ * 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
+ *
+ * @since 1.27
+ *
+ * @group Site
+ * @group medium
+ *
+ * @author Marius Hoch
+ */
+class MediaWikiPageNameNormalizerTest extends MediaWikiUnitTestCase {
+
+       /**
+        * @dataProvider normalizePageTitleProvider
+        */
+       public function testNormalizePageTitle( $expected, $pageName, $getResponse ) {
+               MediaWikiPageNameNormalizerTestMockHttp::$response = $getResponse;
+
+               $normalizer = new MediaWikiPageNameNormalizer(
+                       new MediaWikiPageNameNormalizerTestMockHttp()
+               );
+
+               $this->assertSame(
+                       $expected,
+                       $normalizer->normalizePageName( $pageName, 'https://www.wikidata.org/w/api.php' )
+               );
+       }
+
+       public function normalizePageTitleProvider() {
+               // Response are taken from wikidata and kkwiki using the following API request
+               // api.php?action=query&prop=info&redirects=1&converttitles=1&format=json&titles=…
+               return [
+                       'universe (Q1)' => [
+                               'Q1',
+                               'Q1',
+                               '{"batchcomplete":"","query":{"pages":{"129":{"pageid":129,"ns":0,'
+                               . '"title":"Q1","contentmodel":"wikibase-item","pagelanguage":"en",'
+                               . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
+                               . '"touched":"2016-06-23T05:11:21Z","lastrevid":350004448,"length":58001}}}}'
+                       ],
+                       'Q404 redirects to Q395' => [
+                               'Q395',
+                               'Q404',
+                               '{"batchcomplete":"","query":{"redirects":[{"from":"Q404","to":"Q395"}],"pages"'
+                               . ':{"601":{"pageid":601,"ns":0,"title":"Q395","contentmodel":"wikibase-item",'
+                               . '"pagelanguage":"en","pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
+                               . '"touched":"2016-06-23T08:00:20Z","lastrevid":350021914,"length":60108}}}}'
+                       ],
+                       'D converted to Д (Latin to Cyrillic) (taken from kkwiki)' => [
+                               'Д',
+                               'D',
+                               '{"batchcomplete":"","query":{"converted":[{"from":"D","to":"\u0414"}],'
+                               . '"pages":{"510541":{"pageid":510541,"ns":0,"title":"\u0414",'
+                               . '"contentmodel":"wikitext","pagelanguage":"kk","pagelanguagehtmlcode":"kk",'
+                               . '"pagelanguagedir":"ltr","touched":"2015-11-22T09:16:18Z",'
+                               . '"lastrevid":2373618,"length":3501}}}}'
+                       ],
+                       'there is no Q0' => [
+                               false,
+                               'Q0',
+                               '{"batchcomplete":"","query":{"pages":{"-1":{"ns":0,"title":"Q0",'
+                               . '"missing":"","contentmodel":"wikibase-item","pagelanguage":"en",'
+                               . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr"}}}}'
+                       ],
+                       'invalid title' => [
+                               false,
+                               '{{',
+                               '{"batchcomplete":"","query":{"pages":{"-1":{"title":"{{",'
+                               . '"invalidreason":"The requested page title contains invalid '
+                               . 'characters: \"{\".","invalid":""}}}}'
+                       ],
+                       'error on get' => [ false, 'ABC', false ]
+               ];
+       }
+
+}
+
+/**
+ * @private
+ * @see Http
+ */
+class MediaWikiPageNameNormalizerTestMockHttp extends Http {
+
+       /**
+        * @var mixed
+        */
+       public static $response;
+
+       public static function get( $url, array $options = [], $caller = __METHOD__ ) {
+               PHPUnit_Framework_Assert::assertInternalType( 'string', $url );
+               PHPUnit_Framework_Assert::assertInternalType( 'string', $caller );
+
+               return self::$response;
+       }
+}
diff --git a/tests/phpunit/unit/includes/utils/ZipDirectoryReaderTest.php b/tests/phpunit/unit/includes/utils/ZipDirectoryReaderTest.php
new file mode 100644 (file)
index 0000000..be7e224
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @covers ZipDirectoryReader
+ * NOTE: this test is more like an integration test than a unit test
+ */
+class ZipDirectoryReaderTest extends MediaWikiUnitTestCase {
+
+       protected $zipDir;
+       protected $entries;
+
+       protected function setUp() {
+               parent::setUp();
+               $this->zipDir = __DIR__ . '/../../../data/zip';
+       }
+
+       function zipCallback( $entry ) {
+               $this->entries[] = $entry;
+       }
+
+       function readZipAssertError( $file, $error, $assertMessage ) {
+               $this->entries = [];
+               $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
+               $this->assertTrue( $status->hasMessage( $error ), $assertMessage );
+       }
+
+       function readZipAssertSuccess( $file, $assertMessage ) {
+               $this->entries = [];
+               $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
+               $this->assertTrue( $status->isOK(), $assertMessage );
+       }
+
+       public function testEmpty() {
+               $this->readZipAssertSuccess( 'empty.zip', 'Empty zip' );
+       }
+
+       public function testMultiDisk0() {
+               $this->readZipAssertError( 'split.zip', 'zip-unsupported',
+                       'Split zip error' );
+       }
+
+       public function testNoSignature() {
+               $this->readZipAssertError( 'nosig.zip', 'zip-wrong-format',
+                       'No signature should give "wrong format" error' );
+       }
+
+       public function testSimple() {
+               $this->readZipAssertSuccess( 'class.zip', 'Simple ZIP' );
+               $this->assertEquals( $this->entries, [ [
+                       'name' => 'Class.class',
+                       'mtime' => '20010115000000',
+                       'size' => 1,
+               ] ] );
+       }
+
+       public function testBadCentralEntrySignature() {
+               $this->readZipAssertError( 'wrong-central-entry-sig.zip', 'zip-bad',
+                       'Bad central entry error' );
+       }
+
+       public function testTrailingBytes() {
+               // Due to T40432 this is now zip-wrong-format instead of zip-bad
+               $this->readZipAssertError( 'trail.zip', 'zip-wrong-format',
+                       'Trailing bytes error' );
+       }
+
+       public function testWrongCDStart() {
+               $this->readZipAssertError( 'wrong-cd-start-disk.zip', 'zip-unsupported',
+                       'Wrong CD start disk error' );
+       }
+
+       public function testCentralDirectoryGap() {
+               $this->readZipAssertError( 'cd-gap.zip', 'zip-bad',
+                       'CD gap error' );
+       }
+
+       public function testCentralDirectoryTruncated() {
+               $this->readZipAssertError( 'cd-truncated.zip', 'zip-bad',
+                       'CD truncated error (should hit unpack() overrun)' );
+       }
+
+       public function testLooksLikeZip64() {
+               $this->readZipAssertError( 'looks-like-zip64.zip', 'zip-unsupported',
+                       'A file which looks like ZIP64 but isn\'t, should give error' );
+       }
+}