From: Máté Szabó Date: Sat, 1 Jun 2019 14:10:15 +0000 (+0200) Subject: Separate MediaWiki unit and integration tests X-Git-Tag: 1.34.0-rc.0~1418^2~1 X-Git-Url: http://git.cyclocoop.org/?a=commitdiff_plain;h=0a2b996278e57a8b8c5377cd3a3eaa54f993d4a9;p=lhc%2Fweb%2Fwiklou.git Separate MediaWiki unit and integration tests This changeset implements T89432 and related tickets and is based on exploration done at the Prague Hackathon. The goal is to identify tests in MediaWiki core that can be run without having to install & configure MediaWiki and its dependencies, and provide a way to execute these tests via the standard phpunit entry point, allowing for faster development and integration with existing tooling like IDEs. The initial set of tests that met these criteria were identified using the work Amir did in I88822667693d9e00ac3d4639c87bc24e5083e5e8. These tests were then moved into a new subdirectory under phpunit/ and organized into a separate test suite. The environment for this suite is set up via a PHPUnit bootstrap file without a custom entry point. You can execute these tests by running: $ vendor/bin/phpunit -d memory_limit=512M -c tests/phpunit/unit-tests.xml Bug: T89432 Bug: T87781 Bug: T84948 Change-Id: Iad01033a0548afd4d2a6f2c1ef6fcc9debf72c0d --- diff --git a/.phpcs.xml b/.phpcs.xml index 9ccf5657b7..1d5ce0b12c 100644 --- a/.phpcs.xml +++ b/.phpcs.xml @@ -179,6 +179,7 @@ */maintenance/storage/trackBlobs\.php */tests/phpunit/includes/GlobalFunctions/*\.php + */tests/phpunit/unit/includes/GlobalFunctions/*\.php */tests/phpunit/maintenance/*\.php diff --git a/tests/common/TestSetup.php b/tests/common/TestSetup.php index e24c4c5442..6d250be096 100644 --- a/tests/common/TestSetup.php +++ b/tests/common/TestSetup.php @@ -18,6 +18,9 @@ class TestSetup { global $wgSessionProviders, $wgSessionPbkdf2Iterations; global $wgJobTypeConf; global $wgAuthManagerConfig; + global $wgSecretKey; + + $wgSecretKey = 'secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret'; // wfWarn should cause tests to fail $wgDevelopmentWarnings = true; diff --git a/tests/common/TestsAutoLoader.php b/tests/common/TestsAutoLoader.php index 861111a775..3eb8c9a721 100644 --- a/tests/common/TestsAutoLoader.php +++ b/tests/common/TestsAutoLoader.php @@ -60,6 +60,7 @@ $wgAutoloadClasses += [ 'MediaWikiPHPUnitResultPrinter' => "$testDir/phpunit/MediaWikiPHPUnitResultPrinter.php", 'MediaWikiPHPUnitTestListener' => "$testDir/phpunit/MediaWikiPHPUnitTestListener.php", 'MediaWikiTestCase' => "$testDir/phpunit/MediaWikiTestCase.php", + 'MediaWikiUnitTestCase' => "$testDir/phpunit/MediaWikiUnitTestCase.php", 'MediaWikiTestResult' => "$testDir/phpunit/MediaWikiTestResult.php", 'MediaWikiTestRunner' => "$testDir/phpunit/MediaWikiTestRunner.php", 'PHPUnit4And6Compat' => "$testDir/phpunit/PHPUnit4And6Compat.php", @@ -177,7 +178,7 @@ $wgAutoloadClasses += [ 'LanguageClassesTestCase' => "$testDir/phpunit/languages/LanguageClassesTestCase.php", # tests/phpunit/includes/libs - 'GenericArrayObjectTest' => "$testDir/phpunit/includes/libs/GenericArrayObjectTest.php", + 'GenericArrayObjectTest' => "$testDir/phpunit/unit/includes/libs/GenericArrayObjectTest.php", # tests/phpunit/maintenance 'MediaWiki\Tests\Maintenance\DumpAsserter' => "$testDir/phpunit/maintenance/DumpAsserter.php", diff --git a/tests/phpunit/MediaWikiUnitTestCase.php b/tests/phpunit/MediaWikiUnitTestCase.php new file mode 100644 index 0000000000..9ecc043187 --- /dev/null +++ b/tests/phpunit/MediaWikiUnitTestCase.php @@ -0,0 +1,36 @@ + $factory ) { + $services->disableService( $serviceName ); + $services->redefineService( $serviceName, $factory ); + } + + $this->mwServicesBackup = MediaWikiServices::forceGlobalInstance( $services ); + } + + protected function tearDown() { + parent::tearDown(); + + if ( $this->mwServicesBackup ) { + MediaWikiServices::forceGlobalInstance( $this->mwServicesBackup ); + } + } +} diff --git a/tests/phpunit/documentation/ReleaseNotesTest.php b/tests/phpunit/documentation/ReleaseNotesTest.php deleted file mode 100644 index d20fcff7b8..0000000000 --- a/tests/phpunit/documentation/ReleaseNotesTest.php +++ /dev/null @@ -1,69 +0,0 @@ -assertGreaterThanOrEqual( - 1, - count( $notesFiles ), - 'Repo has at least one Release Notes file.' - ); - - $versionParts = explode( '.', explode( '-', $wgVersion )[0] ); - $this->assertContains( - "$IP/RELEASE-NOTES-$versionParts[0].$versionParts[1]", - $notesFiles, - 'Repo has a Release Notes file for the current $wgVersion.' - ); - - foreach ( $notesFiles as $index => $fileName ) { - $this->assertFileLength( "Release Notes", $fileName ); - } - - // Also test the README and similar files - $otherFiles = [ - "$IP/COPYING", - "$IP/FAQ", - "$IP/HISTORY", - "$IP/INSTALL", - "$IP/README", - "$IP/SECURITY" - ]; - - foreach ( $otherFiles as $index => $fileName ) { - $this->assertFileLength( "Help", $fileName ); - } - } - - private function assertFileLength( $type, $fileName ) { - $file = file( $fileName, FILE_IGNORE_NEW_LINES ); - - $this->assertFalse( - !$file, - "$type file '$fileName' is inaccessible." - ); - - foreach ( $file as $i => $line ) { - $num = $i + 1; - $this->assertLessThanOrEqual( - // FILE_IGNORE_NEW_LINES drops the \n at the EOL, so max length is 80 not 81. - 80, - mb_strlen( $line ), - "$type file '$fileName' line $num, is longer than 80 chars:\n\t'$line'" - ); - } - } -} diff --git a/tests/phpunit/includes/CommentStoreCommentTest.php b/tests/phpunit/includes/CommentStoreCommentTest.php deleted file mode 100644 index 2dfe03ad6b..0000000000 --- a/tests/phpunit/includes/CommentStoreCommentTest.php +++ /dev/null @@ -1,26 +0,0 @@ -assertSame( $message, $comment->message ); - } - - public function testConstructorWithoutMessage() { - $text = '{{template|param}}'; - $comment = new CommentStoreComment( null, $text ); - - $this->assertSame( $text, $comment->message->text() ); - } - -} diff --git a/tests/phpunit/includes/DerivativeRequestTest.php b/tests/phpunit/includes/DerivativeRequestTest.php deleted file mode 100644 index f33022b352..0000000000 --- a/tests/phpunit/includes/DerivativeRequestTest.php +++ /dev/null @@ -1,21 +0,0 @@ -setIP( '1.2.3.4' ); - $derivative = new DerivativeRequest( $original, [] ); - - $this->assertEquals( '1.2.3.4', $derivative->getIP() ); - - $derivative->setIP( '5.6.7.8' ); - - $this->assertEquals( '5.6.7.8', $derivative->getIP() ); - $this->assertEquals( '1.2.3.4', $original->getIP() ); - } - -} diff --git a/tests/phpunit/includes/FauxRequestTest.php b/tests/phpunit/includes/FauxRequestTest.php deleted file mode 100644 index c054caa06f..0000000000 --- a/tests/phpunit/includes/FauxRequestTest.php +++ /dev/null @@ -1,294 +0,0 @@ -orgWgServer = $GLOBALS['wgServer']; - } - - public function tearDown() { - $GLOBALS['wgServer'] = $this->orgWgServer; - parent::tearDown(); - } - - /** - * @covers FauxRequest::__construct - */ - public function testConstructInvalidData() { - $this->setExpectedException( MWException::class, 'bogus data' ); - $req = new FauxRequest( 'x' ); - } - - /** - * @covers FauxRequest::__construct - */ - public function testConstructInvalidSession() { - $this->setExpectedException( MWException::class, 'bogus session' ); - $req = new FauxRequest( [], false, 'x' ); - } - - /** - * @covers FauxRequest::__construct - */ - public function testConstructWithSession() { - $session = SessionManager::singleton()->getEmptySession( new FauxRequest( [] ) ); - $this->assertInstanceOf( - FauxRequest::class, - new FauxRequest( [], false, $session ) - ); - } - - /** - * @covers FauxRequest::getText - */ - public function testGetText() { - $req = new FauxRequest( [ 'x' => 'Value' ] ); - $this->assertEquals( 'Value', $req->getText( 'x' ) ); - $this->assertEquals( '', $req->getText( 'z' ) ); - } - - /** - * Integration test for parent method - * @covers FauxRequest::getVal - */ - public function testGetVal() { - $req = new FauxRequest( [ 'crlf' => "A\r\nb" ] ); - $this->assertSame( "A\r\nb", $req->getVal( 'crlf' ), 'CRLF' ); - } - - /** - * Integration test for parent method - * @covers FauxRequest::getRawVal - */ - public function testGetRawVal() { - $req = new FauxRequest( [ - 'x' => 'Value', - 'y' => [ 'a' ], - 'crlf' => "A\r\nb" - ] ); - $this->assertSame( 'Value', $req->getRawVal( 'x' ) ); - $this->assertSame( null, $req->getRawVal( 'z' ), 'Not found' ); - $this->assertSame( null, $req->getRawVal( 'y' ), 'Array is ignored' ); - $this->assertSame( "A\r\nb", $req->getRawVal( 'crlf' ), 'CRLF' ); - } - - /** - * @covers FauxRequest::getValues - */ - public function testGetValues() { - $values = [ 'x' => 'Value', 'y' => '' ]; - $req = new FauxRequest( $values ); - $this->assertEquals( $values, $req->getValues() ); - } - - /** - * @covers FauxRequest::getQueryValues - */ - public function testGetQueryValues() { - $values = [ 'x' => 'Value', 'y' => '' ]; - - $req = new FauxRequest( $values ); - $this->assertEquals( $values, $req->getQueryValues() ); - $req = new FauxRequest( $values, /*wasPosted*/ true ); - $this->assertEquals( [], $req->getQueryValues() ); - } - - /** - * @covers FauxRequest::getMethod - */ - public function testGetMethod() { - $req = new FauxRequest( [] ); - $this->assertEquals( 'GET', $req->getMethod() ); - $req = new FauxRequest( [], /*wasPosted*/ true ); - $this->assertEquals( 'POST', $req->getMethod() ); - } - - /** - * @covers FauxRequest::wasPosted - */ - public function testWasPosted() { - $req = new FauxRequest( [] ); - $this->assertFalse( $req->wasPosted() ); - $req = new FauxRequest( [], /*wasPosted*/ true ); - $this->assertTrue( $req->wasPosted() ); - } - - /** - * @covers FauxRequest::getCookie - * @covers FauxRequest::setCookie - * @covers FauxRequest::setCookies - */ - public function testCookies() { - $req = new FauxRequest(); - $this->assertSame( null, $req->getCookie( 'z', '' ) ); - - $req->setCookie( 'x', 'Value', '' ); - $this->assertEquals( 'Value', $req->getCookie( 'x', '' ) ); - - $req->setCookies( [ 'x' => 'One', 'y' => 'Two' ], '' ); - $this->assertEquals( 'One', $req->getCookie( 'x', '' ) ); - $this->assertEquals( 'Two', $req->getCookie( 'y', '' ) ); - } - - /** - * @covers FauxRequest::getCookie - * @covers FauxRequest::setCookie - * @covers FauxRequest::setCookies - */ - public function testCookiesDefaultPrefix() { - global $wgCookiePrefix; - $oldPrefix = $wgCookiePrefix; - $wgCookiePrefix = '_'; - - $req = new FauxRequest(); - $this->assertSame( null, $req->getCookie( 'z' ) ); - - $req->setCookie( 'x', 'Value' ); - $this->assertEquals( 'Value', $req->getCookie( 'x' ) ); - - $wgCookiePrefix = $oldPrefix; - } - - /** - * @covers FauxRequest::getRequestURL - */ - public function testGetRequestURL_disallowed() { - $req = new FauxRequest(); - $this->setExpectedException( MWException::class ); - $req->getRequestURL(); - } - - /** - * @covers FauxRequest::setRequestURL - * @covers FauxRequest::getRequestURL - */ - public function testSetRequestURL() { - $req = new FauxRequest(); - $req->setRequestURL( 'https://example.org' ); - $this->assertEquals( 'https://example.org', $req->getRequestURL() ); - } - - /** - * @covers FauxRequest::getFullRequestURL - */ - public function testGetFullRequestURL_disallowed() { - $GLOBALS['wgServer'] = '//wiki.test'; - $req = new FauxRequest(); - - $this->setExpectedException( MWException::class ); - $req->getFullRequestURL(); - } - - /** - * @covers FauxRequest::getFullRequestURL - */ - public function testGetFullRequestURL_http() { - $GLOBALS['wgServer'] = '//wiki.test'; - $req = new FauxRequest(); - $req->setRequestURL( '/path' ); - - $this->assertSame( - 'http://wiki.test/path', - $req->getFullRequestURL() - ); - } - - /** - * @covers FauxRequest::getFullRequestURL - */ - public function testGetFullRequestURL_https() { - $GLOBALS['wgServer'] = '//wiki.test'; - $req = new FauxRequest( [], false, null, 'https' ); - $req->setRequestURL( '/path' ); - - $this->assertSame( - 'https://wiki.test/path', - $req->getFullRequestURL() - ); - } - - /** - * @covers FauxRequest::__construct - * @covers FauxRequest::getProtocol - */ - public function testProtocol() { - $req = new FauxRequest(); - $this->assertEquals( 'http', $req->getProtocol() ); - $req = new FauxRequest( [], false, null, 'http' ); - $this->assertEquals( 'http', $req->getProtocol() ); - $req = new FauxRequest( [], false, null, 'https' ); - $this->assertEquals( 'https', $req->getProtocol() ); - } - - /** - * @covers FauxRequest::setHeader - * @covers FauxRequest::setHeaders - * @covers FauxRequest::getHeader - */ - public function testGetSetHeader() { - $value = 'text/plain, text/html'; - - $request = new FauxRequest(); - $request->setHeader( 'Accept', $value ); - - $this->assertEquals( $request->getHeader( 'Nonexistent' ), false ); - $this->assertEquals( $request->getHeader( 'Accept' ), $value ); - $this->assertEquals( $request->getHeader( 'ACCEPT' ), $value ); - $this->assertEquals( $request->getHeader( 'accept' ), $value ); - $this->assertEquals( - $request->getHeader( 'Accept', WebRequest::GETHEADER_LIST ), - [ 'text/plain', 'text/html' ] - ); - } - - /** - * @covers FauxRequest::initHeaders - */ - public function testGetAllHeaders() { - $_SERVER['HTTP_TEST'] = 'Example'; - - $request = new FauxRequest(); - - $this->assertEquals( - [], - $request->getAllHeaders() - ); - - $this->assertEquals( - false, - $request->getHeader( 'test' ) - ); - } - - /** - * @covers FauxRequest::__construct - * @covers FauxRequest::getSessionArray - */ - public function testSessionData() { - $values = [ 'x' => 'Value', 'y' => '' ]; - - $req = new FauxRequest( [], false, /*session*/ $values ); - $this->assertEquals( $values, $req->getSessionArray() ); - - $req = new FauxRequest(); - $this->assertSame( null, $req->getSessionArray() ); - } - - /** - * @covers FauxRequest::getRawQueryString - * @covers FauxRequest::getRawPostString - * @covers FauxRequest::getRawInput - */ - public function testDummies() { - $req = new FauxRequest(); - $this->assertEquals( '', $req->getRawQueryString() ); - $this->assertEquals( '', $req->getRawPostString() ); - $this->assertEquals( '', $req->getRawInput() ); - } -} diff --git a/tests/phpunit/includes/FauxResponseTest.php b/tests/phpunit/includes/FauxResponseTest.php deleted file mode 100644 index 8085bc710c..0000000000 --- a/tests/phpunit/includes/FauxResponseTest.php +++ /dev/null @@ -1,146 +0,0 @@ -response = new FauxResponse; - } - - /** - * @covers FauxResponse::setCookie - * @covers FauxResponse::getCookie - * @covers FauxResponse::getCookieData - * @covers FauxResponse::getCookies - */ - public function testCookie() { - $expire = time() + 100; - $cookie = [ - 'value' => 'val', - 'path' => '/path', - 'domain' => 'domain', - 'secure' => true, - 'httpOnly' => false, - 'raw' => false, - 'expire' => $expire, - ]; - - $this->assertEquals( null, $this->response->getCookie( 'xkey' ), 'Non-existing cookie' ); - $this->response->setCookie( 'key', 'val', $expire, [ - 'prefix' => 'x', - 'path' => '/path', - 'domain' => 'domain', - 'secure' => 1, - 'httpOnly' => 0, - ] ); - $this->assertEquals( 'val', $this->response->getCookie( 'xkey' ), 'Existing cookie' ); - $this->assertEquals( $cookie, $this->response->getCookieData( 'xkey' ), - 'Existing cookie (data)' ); - $this->assertEquals( [ 'xkey' => $cookie ], $this->response->getCookies(), - 'Existing cookies' ); - } - - /** - * @covers FauxResponse::getheader - * @covers FauxResponse::header - */ - public function testHeader() { - $this->assertEquals( null, $this->response->getHeader( 'Location' ), 'Non-existing header' ); - - $this->response->header( 'Location: http://localhost/' ); - $this->assertEquals( - 'http://localhost/', - $this->response->getHeader( 'Location' ), - 'Set header' - ); - - $this->response->header( 'Location: http://127.0.0.1/' ); - $this->assertEquals( - 'http://127.0.0.1/', - $this->response->getHeader( 'Location' ), - 'Same header' - ); - - $this->response->header( 'Location: http://127.0.0.2/', false ); - $this->assertEquals( - 'http://127.0.0.1/', - $this->response->getHeader( 'Location' ), - 'Same header with override disabled' - ); - - $this->response->header( 'Location: http://localhost/' ); - $this->assertEquals( - 'http://localhost/', - $this->response->getHeader( 'LOCATION' ), - 'Get header case insensitive' - ); - } - - /** - * @covers FauxResponse::getStatusCode - */ - public function testResponseCode() { - $this->response->header( 'HTTP/1.1 200' ); - $this->assertEquals( 200, $this->response->getStatusCode(), 'Header with no message' ); - - $this->response->header( 'HTTP/1.x 201' ); - $this->assertEquals( - 201, - $this->response->getStatusCode(), - 'Header with no message and protocol 1.x' - ); - - $this->response->header( 'HTTP/1.1 202 OK' ); - $this->assertEquals( 202, $this->response->getStatusCode(), 'Normal header' ); - - $this->response->header( 'HTTP/1.x 203 OK' ); - $this->assertEquals( - 203, - $this->response->getStatusCode(), - 'Normal header with no message and protocol 1.x' - ); - - $this->response->header( 'HTTP/1.x 204 OK', false, 205 ); - $this->assertEquals( - 205, - $this->response->getStatusCode(), - 'Third parameter overrides the HTTP/... header' - ); - - $this->response->statusHeader( 210 ); - $this->assertEquals( - 210, - $this->response->getStatusCode(), - 'Handle statusHeader method' - ); - - $this->response->header( 'Location: http://localhost/', false, 206 ); - $this->assertEquals( - 206, - $this->response->getStatusCode(), - 'Third parameter with another header' - ); - } -} diff --git a/tests/phpunit/includes/FormOptionsInitializationTest.php b/tests/phpunit/includes/FormOptionsInitializationTest.php deleted file mode 100644 index 2c78618aa1..0000000000 --- a/tests/phpunit/includes/FormOptionsInitializationTest.php +++ /dev/null @@ -1,70 +0,0 @@ -object = TestingAccessWrapper::newFromObject( new FormOptions() ); - } - - /** - * @covers FormOptions::add - */ - public function testAddStringOption() { - $this->object->add( 'foo', 'string value' ); - $this->assertEquals( - [ - 'foo' => [ - 'default' => 'string value', - 'consumed' => false, - 'type' => FormOptions::STRING, - 'value' => null, - ] - ], - $this->object->options - ); - } - - /** - * @covers FormOptions::add - */ - public function testAddIntegers() { - $this->object->add( 'one', 1 ); - $this->object->add( 'negone', -1 ); - $this->assertEquals( - [ - 'negone' => [ - 'default' => -1, - 'value' => null, - 'consumed' => false, - 'type' => FormOptions::INT, - ], - 'one' => [ - 'default' => 1, - 'value' => null, - 'consumed' => false, - 'type' => FormOptions::INT, - ] - ], - $this->object->options - ); - } -} diff --git a/tests/phpunit/includes/FormOptionsTest.php b/tests/phpunit/includes/FormOptionsTest.php deleted file mode 100644 index da08670f57..0000000000 --- a/tests/phpunit/includes/FormOptionsTest.php +++ /dev/null @@ -1,105 +0,0 @@ -object = new FormOptions; - $this->object->add( 'string1', 'string one' ); - $this->object->add( 'string2', 'string two' ); - $this->object->add( 'integer', 0 ); - $this->object->add( 'float', 0.0 ); - $this->object->add( 'intnull', 0, FormOptions::INTNULL ); - } - - /** Helpers for testGuessType() */ - /* @{ */ - private function assertGuessBoolean( $data ) { - $this->guess( FormOptions::BOOL, $data ); - } - - private function assertGuessInt( $data ) { - $this->guess( FormOptions::INT, $data ); - } - - private function assertGuessFloat( $data ) { - $this->guess( FormOptions::FLOAT, $data ); - } - - private function assertGuessString( $data ) { - $this->guess( FormOptions::STRING, $data ); - } - - private function assertGuessArray( $data ) { - $this->guess( FormOptions::ARR, $data ); - } - - /** Generic helper */ - private function guess( $expected, $data ) { - $this->assertEquals( - $expected, - FormOptions::guessType( $data ) - ); - } - - /* @} */ - - /** - * Reuse helpers above assertGuessBoolean assertGuessInt assertGuessString - * @covers FormOptions::guessType - */ - public function testGuessTypeDetection() { - $this->assertGuessBoolean( true ); - $this->assertGuessBoolean( false ); - - $this->assertGuessInt( 0 ); - $this->assertGuessInt( -5 ); - $this->assertGuessInt( 5 ); - $this->assertGuessInt( 0x0F ); - - $this->assertGuessFloat( 0.0 ); - $this->assertGuessFloat( 1.5 ); - $this->assertGuessFloat( 1e3 ); - - $this->assertGuessString( 'true' ); - $this->assertGuessString( 'false' ); - $this->assertGuessString( '5' ); - $this->assertGuessString( '0' ); - $this->assertGuessString( '1.5' ); - - $this->assertGuessArray( [ 'foo' ] ); - } - - /** - * @expectedException MWException - * @covers FormOptions::guessType - */ - public function testGuessTypeOnNullThrowException() { - $this->object->guessType( null ); - } -} diff --git a/tests/phpunit/includes/GlobalFunctions/wfAppendQueryTest.php b/tests/phpunit/includes/GlobalFunctions/wfAppendQueryTest.php deleted file mode 100644 index bb71610b64..0000000000 --- a/tests/phpunit/includes/GlobalFunctions/wfAppendQueryTest.php +++ /dev/null @@ -1,79 +0,0 @@ -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 index 65b56ef4b3..0000000000 --- a/tests/phpunit/includes/GlobalFunctions/wfArrayPlus2dTest.php +++ /dev/null @@ -1,94 +0,0 @@ -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 index 7ddad369bd..0000000000 --- a/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php +++ /dev/null @@ -1,112 +0,0 @@ -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 index 78e09e60ba..0000000000 --- a/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php +++ /dev/null @@ -1,40 +0,0 @@ -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 index 7402054ea4..0000000000 --- a/tests/phpunit/includes/GlobalFunctions/wfEscapeShellArgTest.php +++ /dev/null @@ -1,43 +0,0 @@ -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 index 8a7bfa5a8c..0000000000 --- a/tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php +++ /dev/null @@ -1,46 +0,0 @@ -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 index eae5588b94..0000000000 --- a/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php +++ /dev/null @@ -1,93 +0,0 @@ -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/wfShellExecTest.php b/tests/phpunit/includes/GlobalFunctions/wfShellExecTest.php deleted file mode 100644 index 6279cf6eee..0000000000 --- a/tests/phpunit/includes/GlobalFunctions/wfShellExecTest.php +++ /dev/null @@ -1,20 +0,0 @@ -assertEquals( 333333, strlen( $output ) ); - } - } -} diff --git a/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php b/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php deleted file mode 100644 index 40b2e636c9..0000000000 --- a/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php +++ /dev/null @@ -1,31 +0,0 @@ -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 index 7f56b60529..0000000000 --- a/tests/phpunit/includes/GlobalFunctions/wfStringToBoolTest.php +++ /dev/null @@ -1,51 +0,0 @@ -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 index a70f136a1c..0000000000 --- a/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php +++ /dev/null @@ -1,194 +0,0 @@ -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 index 5d9f63daa7..0000000000 --- a/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php +++ /dev/null @@ -1,123 +0,0 @@ -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( => ).\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/HooksTest.php b/tests/phpunit/includes/HooksTest.php deleted file mode 100644 index c66b712b83..0000000000 --- a/tests/phpunit/includes/HooksTest.php +++ /dev/null @@ -1,332 +0,0 @@ -assertSame( $expectedFoo, $foo, $msg ); - $this->assertSame( $expectedBar, $bar, $msg ); - } - - /** - * @covers Hooks::getHandlers - */ - public function testGetHandlers() { - global $wgHooks; - - $this->assertSame( - [], - Hooks::getHandlers( 'MediaWikiHooksTest001' ), - 'No hooks registered' - ); - - $a = new NothingClass(); - $b = new NothingClass(); - - $wgHooks['MediaWikiHooksTest001'][] = $a; - - $this->assertSame( - [ $a ], - Hooks::getHandlers( 'MediaWikiHooksTest001' ), - 'Hook registered by $wgHooks' - ); - - Hooks::register( 'MediaWikiHooksTest001', $b ); - $this->assertSame( - [ $b, $a ], - Hooks::getHandlers( 'MediaWikiHooksTest001' ), - 'Hooks::getHandlers() should return hooks registered via wgHooks as well as Hooks::register' - ); - - Hooks::clear( 'MediaWikiHooksTest001' ); - unset( $wgHooks['MediaWikiHooksTest001'] ); - - Hooks::register( 'MediaWikiHooksTest001', $b ); - $this->assertSame( - [ $b ], - Hooks::getHandlers( 'MediaWikiHooksTest001' ), - 'Hook registered by Hook::register' - ); - } - - /** - * @covers Hooks::isRegistered - * @covers Hooks::register - * @covers Hooks::run - * @covers Hooks::callHook - */ - public function testNewStyleHookInteraction() { - global $wgHooks; - - $a = new NothingClass(); - $b = new NothingClass(); - - $wgHooks['MediaWikiHooksTest001'][] = $a; - $this->assertTrue( - Hooks::isRegistered( 'MediaWikiHooksTest001' ), - 'Hook registered via $wgHooks should be noticed by Hooks::isRegistered' - ); - - Hooks::register( 'MediaWikiHooksTest001', $b ); - $this->assertEquals( - 2, - count( Hooks::getHandlers( 'MediaWikiHooksTest001' ) ), - 'Hooks::getHandlers() should return hooks registered via wgHooks as well as Hooks::register' - ); - - $foo = 'quux'; - $bar = 'qaax'; - - Hooks::run( 'MediaWikiHooksTest001', [ &$foo, &$bar ] ); - $this->assertEquals( - 1, - $a->calls, - 'Hooks::run() should run hooks registered via wgHooks as well as Hooks::register' - ); - $this->assertEquals( - 1, - $b->calls, - 'Hooks::run() should run hooks registered via wgHooks as well as Hooks::register' - ); - } - - /** - * @expectedException MWException - * @covers Hooks::run - * @covers Hooks::callHook - */ - public function testUncallableFunction() { - Hooks::register( 'MediaWikiHooksTest001', 'ThisFunctionDoesntExist' ); - Hooks::run( 'MediaWikiHooksTest001', [] ); - } - - /** - * @covers Hooks::run - * @covers Hooks::callHook - */ - public function testFalseReturn() { - Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) { - return false; - } ); - Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) { - $foo = 'test'; - - return true; - } ); - $foo = 'original'; - Hooks::run( 'MediaWikiHooksTest001', [ &$foo ] ); - $this->assertSame( 'original', $foo, 'Hooks abort after a false return.' ); - } - - /** - * @covers Hooks::run - */ - public function testNullReturn() { - Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) { - return; - } ); - Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) { - $foo = 'test'; - - return true; - } ); - $foo = 'original'; - Hooks::run( 'MediaWikiHooksTest001', [ &$foo ] ); - $this->assertSame( 'test', $foo, 'Hooks continue after a null return.' ); - } - - /** - * @covers Hooks::callHook - */ - public function testCallHook_FalseHook() { - Hooks::register( 'MediaWikiHooksTest001', false ); - Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) { - $foo = 'test'; - - return true; - } ); - $foo = 'original'; - Hooks::run( 'MediaWikiHooksTest001', [ &$foo ] ); - $this->assertSame( 'test', $foo, 'Hooks that are falsey are skipped.' ); - } - - /** - * @covers Hooks::callHook - * @expectedException MWException - */ - public function testCallHook_UnknownDatatype() { - Hooks::register( 'MediaWikiHooksTest001', 12345 ); - Hooks::run( 'MediaWikiHooksTest001' ); - } - - /** - * @covers Hooks::callHook - * @expectedException PHPUnit_Framework_Error_Deprecated - */ - public function testCallHook_Deprecated() { - Hooks::register( 'MediaWikiHooksTest001', 'NothingClass::someStatic' ); - Hooks::run( 'MediaWikiHooksTest001', [], '1.31' ); - } - - /** - * @covers Hooks::runWithoutAbort - * @covers Hooks::callHook - */ - public function testRunWithoutAbort() { - $list = []; - Hooks::register( 'MediaWikiHooksTest001', function ( &$list ) { - $list[] = 1; - return true; // Explicit true - } ); - Hooks::register( 'MediaWikiHooksTest001', function ( &$list ) { - $list[] = 2; - return; // Implicit null - } ); - Hooks::register( 'MediaWikiHooksTest001', function ( &$list ) { - $list[] = 3; - // No return - } ); - - Hooks::runWithoutAbort( 'MediaWikiHooksTest001', [ &$list ] ); - $this->assertSame( [ 1, 2, 3 ], $list, 'All hooks ran.' ); - } - - /** - * @covers Hooks::runWithoutAbort - * @covers Hooks::callHook - */ - public function testRunWithoutAbortWarning() { - Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) { - return false; - } ); - Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) { - $foo = 'test'; - return true; - } ); - $foo = 'original'; - - $this->setExpectedException( - UnexpectedValueException::class, - 'Invalid return from hook-MediaWikiHooksTest001-closure for ' . - 'unabortable MediaWikiHooksTest001' - ); - Hooks::runWithoutAbort( 'MediaWikiHooksTest001', [ &$foo ] ); - } - - /** - * @expectedException FatalError - * @covers Hooks::run - */ - public function testFatalError() { - Hooks::register( 'MediaWikiHooksTest001', function () { - return 'test'; - } ); - Hooks::run( 'MediaWikiHooksTest001', [] ); - } -} - -function NothingFunction( &$foo, &$bar ) { - $foo = 'changed-func'; - - return true; -} - -function NothingFunctionData( $data, &$foo, &$bar ) { - $foo = $data; - - return true; -} - -class NothingClass { - public $calls = 0; - - public static function someStatic( &$foo, &$bar ) { - $foo = 'changed-static'; - - return true; - } - - public function someNonStatic( &$foo, &$bar ) { - $this->calls++; - $foo = 'changed-nonstatic'; - $bar = 'changed-nonstatic'; - - return true; - } - - public function onMediaWikiHooksTest001( &$foo, &$bar ) { - $this->calls++; - $foo = 'changed-onevent'; - - return true; - } - - public function someNonStaticWithData( $data, &$foo, &$bar ) { - $this->calls++; - $foo = $data; - - return true; - } -} diff --git a/tests/phpunit/includes/LicensesTest.php b/tests/phpunit/includes/LicensesTest.php deleted file mode 100644 index 0e96bf44ee..0000000000 --- a/tests/phpunit/includes/LicensesTest.php +++ /dev/null @@ -1,25 +0,0 @@ - 'FooField', - 'type' => 'select', - 'section' => 'description', - 'id' => 'wpLicense', - 'label' => 'A label text', # Note can't test label-message because $wgOut is not defined - 'name' => 'AnotherName', - 'licenses' => $str, - ] ); - $this->assertThat( $lc, $this->isInstanceOf( Licenses::class ) ); - } -} diff --git a/tests/phpunit/includes/ListToggleTest.php b/tests/phpunit/includes/ListToggleTest.php deleted file mode 100644 index 3574545e45..0000000000 --- a/tests/phpunit/includes/ListToggleTest.php +++ /dev/null @@ -1,49 +0,0 @@ -getMockBuilder( OutputPage::class ) - ->setMethods( null ) - ->disableOriginalConstructor() - ->getMock(); - - $listToggle = new ListToggle( $output ); - - $this->assertInstanceOf( ListToggle::class, $listToggle ); - $this->assertContains( 'mediawiki.checkboxtoggle', $output->getModules() ); - $this->assertContains( 'mediawiki.checkboxtoggle.styles', $output->getModuleStyles() ); - } - - /** - * @covers ListToggle::getHTML - */ - public function testGetHTML() { - $output = $this->createMock( OutputPage::class ); - $output->expects( $this->any() ) - ->method( 'msg' ) - ->will( $this->returnCallback( function ( $key ) { - return wfMessage( $key )->inLanguage( 'qqx' ); - } ) ); - $output->expects( $this->once() ) - ->method( 'getLanguage' ) - ->will( $this->returnValue( Language::factory( 'qqx' ) ) ); - - $listToggle = new ListToggle( $output ); - - $html = $listToggle->getHTML(); - $this->assertEquals( '
' . - '(checkbox-select: (checkbox-all)(comma-separator)' . - '' . - '(checkbox-none)(comma-separator)(checkbox-invert))
', - $html ); - } -} diff --git a/tests/phpunit/includes/MagicWordFactoryTest.php b/tests/phpunit/includes/MagicWordFactoryTest.php deleted file mode 100644 index 065024bd73..0000000000 --- a/tests/phpunit/includes/MagicWordFactoryTest.php +++ /dev/null @@ -1,84 +0,0 @@ -makeMagicWordFactory( $contLang ); - $magicWordContLang = $magicWordFactory->getContentLanguage(); - - $this->assertSame( $contLang, $magicWordContLang ); - } - - public function testGetMagicWord() { - $magicWordIdValid = 'pageid'; - $magicWordFactory = $this->makeMagicWordFactory(); - $mwActual = $magicWordFactory->get( $magicWordIdValid ); - $contLang = $magicWordFactory->getContentLanguage(); - $expected = new MagicWord( $magicWordIdValid, [ 'PAGEID' ], false, $contLang ); - - $this->assertEquals( $expected, $mwActual ); - } - - public function testGetInvalidMagicWord() { - $magicWordFactory = $this->makeMagicWordFactory(); - - $this->setExpectedException( MWException::class ); - \Wikimedia\suppressWarnings(); - try { - $magicWordFactory->get( 'invalid magic word' ); - } finally { - \Wikimedia\restoreWarnings(); - } - } - - public function testGetVariableIDs() { - $magicWordFactory = $this->makeMagicWordFactory(); - $varIds = $magicWordFactory->getVariableIDs(); - - $this->assertInternalType( 'array', $varIds ); - $this->assertNotEmpty( $varIds ); - $this->assertContainsOnly( 'string', $varIds ); - } - - public function testGetSubstIDs() { - $magicWordFactory = $this->makeMagicWordFactory(); - $substIds = $magicWordFactory->getSubstIDs(); - - $this->assertInternalType( 'array', $substIds ); - $this->assertNotEmpty( $substIds ); - $this->assertContainsOnly( 'string', $substIds ); - } - - /** - * Test both valid and invalid caching hints paths - */ - public function testGetCacheTTL() { - $magicWordFactory = $this->makeMagicWordFactory(); - $actual = $magicWordFactory->getCacheTTL( 'localday' ); - - $this->assertSame( 3600, $actual ); - - $actual = $magicWordFactory->getCacheTTL( 'currentmonth' ); - $this->assertSame( 86400, $actual ); - - $actual = $magicWordFactory->getCacheTTL( 'invalid' ); - $this->assertSame( -1, $actual ); - } - - public function testGetDoubleUnderscoreArray() { - $magicWordFactory = $this->makeMagicWordFactory(); - $actual = $magicWordFactory->getDoubleUnderscoreArray(); - - $this->assertInstanceOf( MagicWordArray::class, $actual ); - } -} diff --git a/tests/phpunit/includes/MediaWikiServicesTest.php b/tests/phpunit/includes/MediaWikiServicesTest.php deleted file mode 100644 index 8fa0cd60ec..0000000000 --- a/tests/phpunit/includes/MediaWikiServicesTest.php +++ /dev/null @@ -1,372 +0,0 @@ -set( 'ServiceWiringFiles', $globalConfig->get( 'ServiceWiringFiles' ) ); - $testConfig->set( 'ConfigRegistry', $globalConfig->get( 'ConfigRegistry' ) ); - - return $testConfig; - } - - /** - * @return MediaWikiServices - */ - private function newMediaWikiServices( Config $config = null ) { - if ( $config === null ) { - $config = $this->newTestConfig(); - } - - $instance = new MediaWikiServices( $config ); - - // Load the default wiring from the specified files. - $wiringFiles = $config->get( 'ServiceWiringFiles' ); - $instance->loadWiringFiles( $wiringFiles ); - - return $instance; - } - - public function testGetInstance() { - $services = MediaWikiServices::getInstance(); - $this->assertInstanceOf( MediaWikiServices::class, $services ); - } - - public function testForceGlobalInstance() { - $newServices = $this->newMediaWikiServices(); - $oldServices = MediaWikiServices::forceGlobalInstance( $newServices ); - - $this->assertInstanceOf( MediaWikiServices::class, $oldServices ); - $this->assertNotSame( $oldServices, $newServices ); - - $theServices = MediaWikiServices::getInstance(); - $this->assertSame( $theServices, $newServices ); - - MediaWikiServices::forceGlobalInstance( $oldServices ); - - $theServices = MediaWikiServices::getInstance(); - $this->assertSame( $theServices, $oldServices ); - } - - public function testResetGlobalInstance() { - $newServices = $this->newMediaWikiServices(); - $oldServices = MediaWikiServices::forceGlobalInstance( $newServices ); - - $service1 = $this->createMock( SalvageableService::class ); - $service1->expects( $this->never() ) - ->method( 'salvage' ); - - $newServices->defineService( - 'Test', - function () use ( $service1 ) { - return $service1; - } - ); - - // force instantiation - $newServices->getService( 'Test' ); - - MediaWikiServices::resetGlobalInstance( $this->newTestConfig() ); - $theServices = MediaWikiServices::getInstance(); - - $this->assertSame( - $service1, - $theServices->getService( 'Test' ), - 'service definition should survive reset' - ); - - $this->assertNotSame( $theServices, $newServices ); - $this->assertNotSame( $theServices, $oldServices ); - - MediaWikiServices::forceGlobalInstance( $oldServices ); - } - - public function testResetGlobalInstance_quick() { - $newServices = $this->newMediaWikiServices(); - $oldServices = MediaWikiServices::forceGlobalInstance( $newServices ); - - $service1 = $this->createMock( SalvageableService::class ); - $service1->expects( $this->never() ) - ->method( 'salvage' ); - - $service2 = $this->createMock( SalvageableService::class ); - $service2->expects( $this->once() ) - ->method( 'salvage' ) - ->with( $service1 ); - - // sequence of values the instantiator will return - $instantiatorReturnValues = [ - $service1, - $service2, - ]; - - $newServices->defineService( - 'Test', - function () use ( &$instantiatorReturnValues ) { - return array_shift( $instantiatorReturnValues ); - } - ); - - // force instantiation - $newServices->getService( 'Test' ); - - MediaWikiServices::resetGlobalInstance( $this->newTestConfig(), 'quick' ); - $theServices = MediaWikiServices::getInstance(); - - $this->assertSame( $service2, $theServices->getService( 'Test' ) ); - - $this->assertNotSame( $theServices, $newServices ); - $this->assertNotSame( $theServices, $oldServices ); - - MediaWikiServices::forceGlobalInstance( $oldServices ); - } - - public function testDisableStorageBackend() { - $newServices = $this->newMediaWikiServices(); - $oldServices = MediaWikiServices::forceGlobalInstance( $newServices ); - - $lbFactory = $this->getMockBuilder( \Wikimedia\Rdbms\LBFactorySimple::class ) - ->disableOriginalConstructor() - ->getMock(); - - $newServices->redefineService( - 'DBLoadBalancerFactory', - function () use ( $lbFactory ) { - return $lbFactory; - } - ); - - // force the service to become active, so we can check that it does get destroyed - $newServices->getService( 'DBLoadBalancerFactory' ); - - MediaWikiServices::disableStorageBackend(); // should destroy DBLoadBalancerFactory - - try { - MediaWikiServices::getInstance()->getService( 'DBLoadBalancerFactory' ); - $this->fail( 'DBLoadBalancerFactory should have been disabled' ); - } - catch ( ServiceDisabledException $ex ) { - // ok, as expected - } catch ( Throwable $ex ) { - $this->fail( 'ServiceDisabledException expected, caught ' . get_class( $ex ) ); - } - - MediaWikiServices::forceGlobalInstance( $oldServices ); - $newServices->destroy(); - - // No exception was thrown, avoid being risky - $this->assertTrue( true ); - } - - public function testResetChildProcessServices() { - $newServices = $this->newMediaWikiServices(); - $oldServices = MediaWikiServices::forceGlobalInstance( $newServices ); - - $service1 = $this->createMock( DestructibleService::class ); - $service1->expects( $this->once() ) - ->method( 'destroy' ); - - $service2 = $this->createMock( DestructibleService::class ); - $service2->expects( $this->never() ) - ->method( 'destroy' ); - - // sequence of values the instantiator will return - $instantiatorReturnValues = [ - $service1, - $service2, - ]; - - $newServices->defineService( - 'Test', - function () use ( &$instantiatorReturnValues ) { - return array_shift( $instantiatorReturnValues ); - } - ); - - // force the service to become active, so we can check that it does get destroyed - $oldTestService = $newServices->getService( 'Test' ); - - MediaWikiServices::resetChildProcessServices(); - $finalServices = MediaWikiServices::getInstance(); - - $newTestService = $finalServices->getService( 'Test' ); - $this->assertNotSame( $oldTestService, $newTestService ); - - MediaWikiServices::forceGlobalInstance( $oldServices ); - } - - public function testResetServiceForTesting() { - $services = $this->newMediaWikiServices(); - $serviceCounter = 0; - - $services->defineService( - 'Test', - function () use ( &$serviceCounter ) { - $serviceCounter++; - $service = $this->createMock( Wikimedia\Services\DestructibleService::class ); - $service->expects( $this->once() )->method( 'destroy' ); - return $service; - } - ); - - // This should do nothing. In particular, it should not create a service instance. - $services->resetServiceForTesting( 'Test' ); - $this->assertEquals( 0, $serviceCounter, 'No service instance should be created yet.' ); - - $oldInstance = $services->getService( 'Test' ); - $this->assertEquals( 1, $serviceCounter, 'A service instance should exit now.' ); - - // The old instance should be detached, and destroy() called. - $services->resetServiceForTesting( 'Test' ); - $newInstance = $services->getService( 'Test' ); - - $this->assertNotSame( $oldInstance, $newInstance ); - - // Satisfy the expectation that destroy() is called also for the second service instance. - $newInstance->destroy(); - } - - public function testResetServiceForTesting_noDestroy() { - $services = $this->newMediaWikiServices(); - - $services->defineService( - 'Test', - function () { - $service = $this->createMock( Wikimedia\Services\DestructibleService::class ); - $service->expects( $this->never() )->method( 'destroy' ); - return $service; - } - ); - - $oldInstance = $services->getService( 'Test' ); - - // The old instance should be detached, but destroy() not called. - $services->resetServiceForTesting( 'Test', false ); - $newInstance = $services->getService( 'Test' ); - - $this->assertNotSame( $oldInstance, $newInstance ); - } - - public function provideGetters() { - $getServiceCases = $this->provideGetService(); - $getterCases = []; - - // All getters should be named just like the service, with "get" added. - foreach ( $getServiceCases as $name => $case ) { - if ( $name[0] === '_' ) { - // Internal service, no getter - continue; - } - list( $service, $class ) = $case; - $getterCases[$name] = [ - 'get' . $service, - $class, - in_array( $service, $this->deprecatedServices ) - ]; - } - - return $getterCases; - } - - /** - * @dataProvider provideGetters - */ - public function testGetters( $getter, $type, $isDeprecated = false ) { - if ( $isDeprecated ) { - $this->hideDeprecated( MediaWikiServices::class . "::$getter" ); - } - - // Test against the default instance, since the dummy will not know the default services. - $services = MediaWikiServices::getInstance(); - $service = $services->$getter(); - $this->assertInstanceOf( $type, $service ); - } - - public function provideGetService() { - global $IP; - $serviceList = require "$IP/includes/ServiceWiring.php"; - $ret = []; - foreach ( $serviceList as $name => $callback ) { - $fun = new ReflectionFunction( $callback ); - if ( !$fun->hasReturnType() ) { - throw new MWException( 'All service callbacks must have a return type defined, ' . - "none found for $name" ); - } - $ret[$name] = [ $name, $fun->getReturnType()->__toString() ]; - } - return $ret; - } - - /** - * @dataProvider provideGetService - */ - public function testGetService( $name, $type ) { - // Test against the default instance, since the dummy will not know the default services. - $services = MediaWikiServices::getInstance(); - - $service = $services->getService( $name ); - $this->assertInstanceOf( $type, $service ); - } - - public function testDefaultServiceInstantiation() { - // Check all services in the default instance, not a dummy instance! - // Note that we instantiate all services here, including any that - // were registered by extensions. - $services = MediaWikiServices::getInstance(); - $names = $services->getServiceNames(); - - foreach ( $names as $name ) { - $this->assertTrue( $services->hasService( $name ) ); - $service = $services->getService( $name ); - $this->assertInternalType( 'object', $service ); - } - } - - public function testDefaultServiceWiringServicesHaveTests() { - global $IP; - $testedServices = array_keys( $this->provideGetService() ); - $allServices = array_keys( require "$IP/includes/ServiceWiring.php" ); - $this->assertEquals( - [], - array_diff( $allServices, $testedServices ), - 'The following services have not been added to MediaWikiServicesTest::provideGetService' - ); - } - - public function testGettersAreSorted() { - $methods = ( new ReflectionClass( MediaWikiServices::class ) ) - ->getMethods( ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC ); - - $names = array_map( function ( $method ) { - return $method->getName(); - }, $methods ); - $serviceNames = array_map( function ( $name ) { - return "get$name"; - }, array_keys( $this->provideGetService() ) ); - $names = array_values( array_filter( $names, function ( $name ) use ( $serviceNames ) { - return in_array( $name, $serviceNames ); - } ) ); - - $sortedNames = $names; - natcasesort( $sortedNames ); - - $this->assertSame( $sortedNames, $names, - 'Please keep service getters sorted alphabetically' ); - } -} diff --git a/tests/phpunit/includes/MediaWikiVersionFetcherTest.php b/tests/phpunit/includes/MediaWikiVersionFetcherTest.php deleted file mode 100644 index 9803081869..0000000000 --- a/tests/phpunit/includes/MediaWikiVersionFetcherTest.php +++ /dev/null @@ -1,21 +0,0 @@ - - */ -class MediaWikiVersionFetcherTest extends MediaWikiTestCase { - - public function testReturnsResult() { - global $wgVersion; - $versionFetcher = new MediaWikiVersionFetcher(); - $this->assertSame( $wgVersion, $versionFetcher->fetchVersion() ); - } - -} diff --git a/tests/phpunit/includes/PathRouterTest.php b/tests/phpunit/includes/PathRouterTest.php deleted file mode 100644 index d8916751c0..0000000000 --- a/tests/phpunit/includes/PathRouterTest.php +++ /dev/null @@ -1,325 +0,0 @@ -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/Revision/FallbackSlotRoleHandlerTest.php b/tests/phpunit/includes/Revision/FallbackSlotRoleHandlerTest.php deleted file mode 100644 index aedf292eca..0000000000 --- a/tests/phpunit/includes/Revision/FallbackSlotRoleHandlerTest.php +++ /dev/null @@ -1,75 +0,0 @@ -getMockBuilder( Title::class ) - ->disableOriginalConstructor() - ->getMock(); - - return $title; - } - - /** - * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::__construct - * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getRole() - * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getNameMessageKey() - * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getDefaultModel() - * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getOutputLayoutHints() - */ - public function testConstruction() { - $handler = new FallbackSlotRoleHandler( 'foo' ); - $this->assertSame( 'foo', $handler->getRole() ); - $this->assertSame( 'slot-name-foo', $handler->getNameMessageKey() ); - - $title = $this->makeBlankTitleObject(); - $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title ) ); - - $hints = $handler->getOutputLayoutHints(); - $this->assertArrayHasKey( 'display', $hints ); - $this->assertArrayHasKey( 'region', $hints ); - $this->assertArrayHasKey( 'placement', $hints ); - } - - /** - * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::isAllowedModel() - */ - public function testIsAllowedModel() { - $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' ); - - // For the fallback handler, no models are allowed - $title = $this->makeBlankTitleObject(); - $this->assertFalse( $handler->isAllowedModel( 'FooModel', $title ) ); - $this->assertFalse( $handler->isAllowedModel( 'QuaxModel', $title ) ); - } - - /** - * @covers \MediaWiki\Revision\SlotRoleHandler::isAllowedModel() - */ - public function testIsAllowedOn() { - $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' ); - - $title = $this->makeBlankTitleObject(); - $this->assertFalse( $handler->isAllowedOn( $title ) ); - } - - /** - * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::supportsArticleCount() - */ - public function testSupportsArticleCount() { - $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' ); - - $this->assertFalse( $handler->supportsArticleCount() ); - } - -} diff --git a/tests/phpunit/includes/Revision/MainSlotRoleHandlerTest.php b/tests/phpunit/includes/Revision/MainSlotRoleHandlerTest.php deleted file mode 100644 index 5e32574d40..0000000000 --- a/tests/phpunit/includes/Revision/MainSlotRoleHandlerTest.php +++ /dev/null @@ -1,79 +0,0 @@ -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/RevisionStoreFactoryTest.php b/tests/phpunit/includes/Revision/RevisionStoreFactoryTest.php deleted file mode 100644 index 138d6bcba1..0000000000 --- a/tests/phpunit/includes/Revision/RevisionStoreFactoryTest.php +++ /dev/null @@ -1,197 +0,0 @@ -getMockLoadBalancerFactory(), - $this->getMockBlobStoreFactory(), - $this->getNameTableStoreFactory(), - $this->getMockSlotRoleRegistry(), - $this->getHashWANObjectCache(), - $this->getMockCommentStore(), - ActorMigration::newMigration(), - MIGRATION_OLD, - $this->getMockLoggerSpi(), - true - ); - $this->assertTrue( true ); - } - - public function provideWikiIds() { - yield [ true ]; - yield [ false ]; - yield [ 'somewiki' ]; - yield [ 'somewiki', MIGRATION_OLD , false ]; - yield [ 'somewiki', MIGRATION_NEW , true ]; - } - - /** - * @dataProvider provideWikiIds - * @covers \MediaWiki\Revision\RevisionStoreFactory::getRevisionStore - */ - public function testGetRevisionStore( - $wikiId, - $mcrMigrationStage = MIGRATION_OLD, - $contentHandlerUseDb = true - ) { - $lbFactory = $this->getMockLoadBalancerFactory(); - $blobStoreFactory = $this->getMockBlobStoreFactory(); - $nameTableStoreFactory = $this->getNameTableStoreFactory(); - $slotRoleRegistry = $this->getMockSlotRoleRegistry(); - $cache = $this->getHashWANObjectCache(); - $commentStore = $this->getMockCommentStore(); - $actorMigration = ActorMigration::newMigration(); - $loggerProvider = $this->getMockLoggerSpi(); - - $factory = new RevisionStoreFactory( - $lbFactory, - $blobStoreFactory, - $nameTableStoreFactory, - $slotRoleRegistry, - $cache, - $commentStore, - $actorMigration, - $mcrMigrationStage, - $loggerProvider, - $contentHandlerUseDb - ); - - $store = $factory->getRevisionStore( $wikiId ); - $wrapper = TestingAccessWrapper::newFromObject( $store ); - - // ensure the correct object type is returned - $this->assertInstanceOf( RevisionStore::class, $store ); - - // ensure the RevisionStore is for the given wikiId - $this->assertSame( $wikiId, $wrapper->wikiId ); - - // ensure all other required services are correctly set - $this->assertSame( $cache, $wrapper->cache ); - $this->assertSame( $commentStore, $wrapper->commentStore ); - $this->assertSame( $mcrMigrationStage, $wrapper->mcrMigrationStage ); - $this->assertSame( $actorMigration, $wrapper->actorMigration ); - $this->assertSame( $contentHandlerUseDb, $store->getContentHandlerUseDB() ); - - $this->assertInstanceOf( ILoadBalancer::class, $wrapper->loadBalancer ); - $this->assertInstanceOf( BlobStore::class, $wrapper->blobStore ); - $this->assertInstanceOf( NameTableStore::class, $wrapper->contentModelStore ); - $this->assertInstanceOf( NameTableStore::class, $wrapper->slotRoleStore ); - $this->assertInstanceOf( LoggerInterface::class, $wrapper->logger ); - } - - /** - * @return \PHPUnit_Framework_MockObject_MockObject|ILoadBalancer - */ - private function getMockLoadBalancer() { - return $this->getMockBuilder( ILoadBalancer::class ) - ->disableOriginalConstructor()->getMock(); - } - - /** - * @return \PHPUnit_Framework_MockObject_MockObject|ILBFactory - */ - private function getMockLoadBalancerFactory() { - $mock = $this->getMockBuilder( ILBFactory::class ) - ->disableOriginalConstructor()->getMock(); - - $mock->method( 'getMainLB' ) - ->willReturnCallback( function () { - return $this->getMockLoadBalancer(); - } ); - - return $mock; - } - - /** - * @return \PHPUnit_Framework_MockObject_MockObject|SqlBlobStore - */ - private function getMockSqlBlobStore() { - return $this->getMockBuilder( SqlBlobStore::class ) - ->disableOriginalConstructor()->getMock(); - } - - /** - * @return \PHPUnit_Framework_MockObject_MockObject|BlobStoreFactory - */ - private function getMockBlobStoreFactory() { - $mock = $this->getMockBuilder( BlobStoreFactory::class ) - ->disableOriginalConstructor()->getMock(); - - $mock->method( 'newSqlBlobStore' ) - ->willReturnCallback( function () { - return $this->getMockSqlBlobStore(); - } ); - - return $mock; - } - - /** - * @return \PHPUnit_Framework_MockObject_MockObject|SlotRoleRegistry - */ - private function getMockSlotRoleRegistry() { - $mock = $this->getMockBuilder( SlotRoleRegistry::class ) - ->disableOriginalConstructor()->getMock(); - - return $mock; - } - - /** - * @return NameTableStoreFactory - */ - private function getNameTableStoreFactory() { - return new NameTableStoreFactory( - $this->getMockLoadBalancerFactory(), - $this->getHashWANObjectCache(), - new NullLogger() ); - } - - /** - * @return \PHPUnit_Framework_MockObject_MockObject|CommentStore - */ - private function getMockCommentStore() { - return $this->getMockBuilder( CommentStore::class ) - ->disableOriginalConstructor()->getMock(); - } - - private function getHashWANObjectCache() { - return new WANObjectCache( [ 'cache' => new \HashBagOStuff() ] ); - } - - /** - * @return \PHPUnit_Framework_MockObject_MockObject|LoggerSpi - */ - private function getMockLoggerSpi() { - $mock = $this->getMock( LoggerSpi::class ); - - $mock->method( 'getLogger' ) - ->willReturn( new NullLogger() ); - - return $mock; - } - -} diff --git a/tests/phpunit/includes/Revision/SlotRecordTest.php b/tests/phpunit/includes/Revision/SlotRecordTest.php deleted file mode 100644 index 1b6ff2aace..0000000000 --- a/tests/phpunit/includes/Revision/SlotRecordTest.php +++ /dev/null @@ -1,408 +0,0 @@ - 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->assertNotNull( $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->assertNotNull( $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 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/Revision/SlotRoleHandlerTest.php b/tests/phpunit/includes/Revision/SlotRoleHandlerTest.php deleted file mode 100644 index 67e9464f33..0000000000 --- a/tests/phpunit/includes/Revision/SlotRoleHandlerTest.php +++ /dev/null @@ -1,67 +0,0 @@ -getMockBuilder( Title::class ) - ->disableOriginalConstructor() - ->getMock(); - - return $title; - } - - /** - * @covers \MediaWiki\Revision\SlotRoleHandler::__construct - * @covers \MediaWiki\Revision\SlotRoleHandler::getRole() - * @covers \MediaWiki\Revision\SlotRoleHandler::getNameMessageKey() - * @covers \MediaWiki\Revision\SlotRoleHandler::getDefaultModel() - * @covers \MediaWiki\Revision\SlotRoleHandler::getOutputLayoutHints() - */ - public function testConstruction() { - $handler = new SlotRoleHandler( 'foo', 'FooModel', [ 'frob' => 'niz' ] ); - $this->assertSame( 'foo', $handler->getRole() ); - $this->assertSame( 'slot-name-foo', $handler->getNameMessageKey() ); - - $title = $this->makeBlankTitleObject(); - $this->assertSame( 'FooModel', $handler->getDefaultModel( $title ) ); - - $hints = $handler->getOutputLayoutHints(); - $this->assertArrayHasKey( 'frob', $hints ); - $this->assertSame( 'niz', $hints['frob'] ); - - $this->assertArrayHasKey( 'display', $hints ); - $this->assertArrayHasKey( 'region', $hints ); - $this->assertArrayHasKey( 'placement', $hints ); - } - - /** - * @covers \MediaWiki\Revision\SlotRoleHandler::isAllowedModel() - */ - public function testIsAllowedModel() { - $handler = new SlotRoleHandler( 'foo', 'FooModel' ); - - $title = $this->makeBlankTitleObject(); - $this->assertTrue( $handler->isAllowedModel( 'FooModel', $title ) ); - $this->assertFalse( $handler->isAllowedModel( 'QuaxModel', $title ) ); - } - - /** - * @covers \MediaWiki\Revision\SlotRoleHandler::supportsArticleCount() - */ - public function testSupportsArticleCount() { - $handler = new SlotRoleHandler( 'foo', 'FooModel' ); - - $this->assertFalse( $handler->supportsArticleCount() ); - } - -} diff --git a/tests/phpunit/includes/SanitizerValidateEmailTest.php b/tests/phpunit/includes/SanitizerValidateEmailTest.php deleted file mode 100644 index c4e430848b..0000000000 --- a/tests/phpunit/includes/SanitizerValidateEmailTest.php +++ /dev/null @@ -1,105 +0,0 @@ -assertEquals( - $expected, - Sanitizer::validateEmail( $addr ), - $msg - ); - } - - private function valid( $addr, $msg = '' ) { - $this->checkEmail( $addr, true, $msg ); - } - - private function invalid( $addr, $msg = '' ) { - $this->checkEmail( $addr, false, $msg ); - } - - public function testEmailWellKnownUserAtHostDotTldAreValid() { - $this->valid( 'user@example.com' ); - $this->valid( 'user@example.museum' ); - } - - public function testEmailWithUpperCaseCharactersAreValid() { - $this->valid( 'USER@example.com' ); - $this->valid( 'user@EXAMPLE.COM' ); - $this->valid( 'user@Example.com' ); - $this->valid( 'USER@eXAMPLE.com' ); - } - - public function testEmailWithAPlusInUserName() { - $this->valid( 'user+sub@example.com' ); - $this->valid( 'user+@example.com' ); - } - - public function testEmailDoesNotNeedATopLevelDomain() { - $this->valid( "user@localhost" ); - $this->valid( "FooBar@localdomain" ); - $this->valid( "nobody@mycompany" ); - } - - public function testEmailWithWhiteSpacesBeforeOrAfterAreInvalids() { - $this->invalid( " user@host.com" ); - $this->invalid( "user@host.com " ); - $this->invalid( "\tuser@host.com" ); - $this->invalid( "user@host.com\t" ); - } - - public function testEmailWithWhiteSpacesAreInvalids() { - $this->invalid( "User user@host" ); - $this->invalid( "first last@mycompany" ); - $this->invalid( "firstlast@my company" ); - } - - /** - * T28948 : comma were matched by an incorrect regexp range - */ - public function testEmailWithCommasAreInvalids() { - $this->invalid( "user,foo@example.org" ); - $this->invalid( "userfoo@ex,ample.org" ); - } - - public function testEmailWithHyphens() { - $this->valid( "user-foo@example.org" ); - $this->valid( "userfoo@ex-ample.org" ); - } - - public function testEmailDomainCanNotBeginWithDot() { - $this->invalid( "user@." ); - $this->invalid( "user@.localdomain" ); - $this->invalid( "user@localdomain." ); - $this->valid( "user.@localdomain" ); - $this->valid( ".@localdomain" ); - $this->invalid( ".@a............" ); - } - - public function testEmailWithFunnyCharacters() { - $this->valid( "\$user!ex{this}@123.com" ); - } - - public function testEmailTopLevelDomainCanBeNumerical() { - $this->valid( "user@example.1234" ); - } - - public function testEmailWithoutAtSignIsInvalid() { - $this->invalid( 'useràexample.com' ); - } - - public function testEmailWithOneCharacterDomainIsValid() { - $this->valid( 'user@a' ); - } -} diff --git a/tests/phpunit/includes/ServiceWiringTest.php b/tests/phpunit/includes/ServiceWiringTest.php deleted file mode 100644 index 02e06f8dda..0000000000 --- a/tests/phpunit/includes/ServiceWiringTest.php +++ /dev/null @@ -1,16 +0,0 @@ -assertSame( $sortedServices, $services, - 'Please keep services sorted alphabetically' ); - } -} diff --git a/tests/phpunit/includes/SiteConfigurationTest.php b/tests/phpunit/includes/SiteConfigurationTest.php deleted file mode 100644 index 3b7226245f..0000000000 --- a/tests/phpunit/includes/SiteConfigurationTest.php +++ /dev/null @@ -1,379 +0,0 @@ -mConf = new SiteConfiguration; - - $this->mConf->suffixes = [ 'wikipedia' => 'wiki' ]; - $this->mConf->wikis = [ 'enwiki', 'dewiki', 'frwiki' ]; - $this->mConf->settings = [ - 'SimpleKey' => [ - 'wiki' => 'wiki', - 'tag' => 'tag', - 'enwiki' => 'enwiki', - 'dewiki' => 'dewiki', - 'frwiki' => 'frwiki', - ], - - 'Fallback' => [ - 'default' => 'default', - 'wiki' => 'wiki', - 'tag' => 'tag', - 'frwiki' => 'frwiki', - 'null_wiki' => null, - ], - - 'WithParams' => [ - 'default' => '$lang $site $wiki', - ], - - '+SomeGlobal' => [ - 'wiki' => [ - 'wiki' => 'wiki', - ], - 'tag' => [ - 'tag' => 'tag', - ], - 'enwiki' => [ - 'enwiki' => 'enwiki', - ], - 'dewiki' => [ - 'dewiki' => 'dewiki', - ], - 'frwiki' => [ - 'frwiki' => 'frwiki', - ], - ], - - 'MergeIt' => [ - '+wiki' => [ - 'wiki' => 'wiki', - ], - '+tag' => [ - 'tag' => 'tag', - ], - 'default' => [ - 'default' => 'default', - ], - '+enwiki' => [ - 'enwiki' => 'enwiki', - ], - '+dewiki' => [ - 'dewiki' => 'dewiki', - ], - '+frwiki' => [ - 'frwiki' => 'frwiki', - ], - ], - ]; - - $GLOBALS['SomeGlobal'] = [ 'SomeGlobal' => 'SomeGlobal' ]; - } - - /** - * This function is used as a callback within the tests below - */ - public static function getSiteParamsCallback( $conf, $wiki ) { - $site = null; - $lang = null; - foreach ( $conf->suffixes as $suffix ) { - if ( substr( $wiki, -strlen( $suffix ) ) == $suffix ) { - $site = $suffix; - $lang = substr( $wiki, 0, -strlen( $suffix ) ); - break; - } - } - - return [ - 'suffix' => $site, - 'lang' => $lang, - 'params' => [ - 'lang' => $lang, - 'site' => $site, - 'wiki' => $wiki, - ], - 'tags' => [ 'tag' ], - ]; - } - - /** - * @covers SiteConfiguration::siteFromDB - */ - public function testSiteFromDb() { - $this->assertEquals( - [ 'wikipedia', 'en' ], - $this->mConf->siteFromDB( 'enwiki' ), - 'siteFromDB()' - ); - $this->assertEquals( - [ 'wikipedia', '' ], - $this->mConf->siteFromDB( 'wiki' ), - 'siteFromDB() on a suffix' - ); - $this->assertEquals( - [ null, null ], - $this->mConf->siteFromDB( 'wikien' ), - 'siteFromDB() on a non-existing wiki' - ); - - $this->mConf->suffixes = [ 'wiki', '' ]; - $this->assertEquals( - [ '', 'wikien' ], - $this->mConf->siteFromDB( 'wikien' ), - 'siteFromDB() on a non-existing wiki (2)' - ); - } - - /** - * @covers SiteConfiguration::getLocalDatabases - */ - public function testGetLocalDatabases() { - $this->assertEquals( - [ 'enwiki', 'dewiki', 'frwiki' ], - $this->mConf->getLocalDatabases(), - 'getLocalDatabases()' - ); - } - - /** - * @covers SiteConfiguration::get - */ - public function testGetConfVariables() { - // Simple - $this->assertEquals( - 'enwiki', - $this->mConf->get( 'SimpleKey', 'enwiki', 'wiki' ), - 'get(): simple setting on an existing wiki' - ); - $this->assertEquals( - 'dewiki', - $this->mConf->get( 'SimpleKey', 'dewiki', 'wiki' ), - 'get(): simple setting on an existing wiki (2)' - ); - $this->assertEquals( - 'frwiki', - $this->mConf->get( 'SimpleKey', 'frwiki', 'wiki' ), - 'get(): simple setting on an existing wiki (3)' - ); - $this->assertEquals( - 'wiki', - $this->mConf->get( 'SimpleKey', 'wiki', 'wiki' ), - 'get(): simple setting on an suffix' - ); - $this->assertEquals( - 'wiki', - $this->mConf->get( 'SimpleKey', 'eswiki', 'wiki' ), - 'get(): simple setting on an non-existing wiki' - ); - - // Fallback - $this->assertEquals( - 'wiki', - $this->mConf->get( 'Fallback', 'enwiki', 'wiki' ), - 'get(): fallback setting on an existing wiki' - ); - $this->assertEquals( - 'tag', - $this->mConf->get( 'Fallback', 'dewiki', 'wiki', [], [ 'tag' ] ), - 'get(): fallback setting on an existing wiki (with wiki tag)' - ); - $this->assertEquals( - 'frwiki', - $this->mConf->get( 'Fallback', 'frwiki', 'wiki', [], [ 'tag' ] ), - 'get(): no fallback if wiki has its own setting (matching tag)' - ); - $this->assertSame( - // Potential regression test for T192855 - null, - $this->mConf->get( 'Fallback', 'null_wiki', 'wiki', [], [ 'tag' ] ), - 'get(): no fallback if wiki has its own setting (matching tag and uses null)' - ); - $this->assertEquals( - 'wiki', - $this->mConf->get( 'Fallback', 'wiki', 'wiki' ), - 'get(): fallback setting on an suffix' - ); - $this->assertEquals( - 'wiki', - $this->mConf->get( 'Fallback', 'wiki', 'wiki', [], [ 'tag' ] ), - 'get(): fallback setting on an suffix (with wiki tag)' - ); - $this->assertEquals( - 'wiki', - $this->mConf->get( 'Fallback', 'eswiki', 'wiki' ), - 'get(): fallback setting on an non-existing wiki' - ); - $this->assertEquals( - 'tag', - $this->mConf->get( 'Fallback', 'eswiki', 'wiki', [], [ 'tag' ] ), - 'get(): fallback setting on an non-existing wiki (with wiki tag)' - ); - - // Merging - $common = [ 'wiki' => 'wiki', 'default' => 'default' ]; - $commonTag = [ 'tag' => 'tag', 'wiki' => 'wiki', 'default' => 'default' ]; - $this->assertEquals( - [ 'enwiki' => 'enwiki' ] + $common, - $this->mConf->get( 'MergeIt', 'enwiki', 'wiki' ), - 'get(): merging setting on an existing wiki' - ); - $this->assertEquals( - [ 'enwiki' => 'enwiki' ] + $commonTag, - $this->mConf->get( 'MergeIt', 'enwiki', 'wiki', [], [ 'tag' ] ), - 'get(): merging setting on an existing wiki (with tag)' - ); - $this->assertEquals( - [ 'dewiki' => 'dewiki' ] + $common, - $this->mConf->get( 'MergeIt', 'dewiki', 'wiki' ), - 'get(): merging setting on an existing wiki (2)' - ); - $this->assertEquals( - [ 'dewiki' => 'dewiki' ] + $commonTag, - $this->mConf->get( 'MergeIt', 'dewiki', 'wiki', [], [ 'tag' ] ), - 'get(): merging setting on an existing wiki (2) (with tag)' - ); - $this->assertEquals( - [ 'frwiki' => 'frwiki' ] + $common, - $this->mConf->get( 'MergeIt', 'frwiki', 'wiki' ), - 'get(): merging setting on an existing wiki (3)' - ); - $this->assertEquals( - [ 'frwiki' => 'frwiki' ] + $commonTag, - $this->mConf->get( 'MergeIt', 'frwiki', 'wiki', [], [ 'tag' ] ), - 'get(): merging setting on an existing wiki (3) (with tag)' - ); - $this->assertEquals( - [ 'wiki' => 'wiki' ] + $common, - $this->mConf->get( 'MergeIt', 'wiki', 'wiki' ), - 'get(): merging setting on an suffix' - ); - $this->assertEquals( - [ 'wiki' => 'wiki' ] + $commonTag, - $this->mConf->get( 'MergeIt', 'wiki', 'wiki', [], [ 'tag' ] ), - 'get(): merging setting on an suffix (with tag)' - ); - $this->assertEquals( - $common, - $this->mConf->get( 'MergeIt', 'eswiki', 'wiki' ), - 'get(): merging setting on an non-existing wiki' - ); - $this->assertEquals( - $commonTag, - $this->mConf->get( 'MergeIt', 'eswiki', 'wiki', [], [ 'tag' ] ), - 'get(): merging setting on an non-existing wiki (with tag)' - ); - } - - /** - * @covers SiteConfiguration::siteFromDB - */ - public function testSiteFromDbWithCallback() { - $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback'; - - $this->assertEquals( - [ 'wiki', 'en' ], - $this->mConf->siteFromDB( 'enwiki' ), - 'siteFromDB() with callback' - ); - $this->assertEquals( - [ 'wiki', '' ], - $this->mConf->siteFromDB( 'wiki' ), - 'siteFromDB() with callback on a suffix' - ); - $this->assertEquals( - [ null, null ], - $this->mConf->siteFromDB( 'wikien' ), - 'siteFromDB() with callback on a non-existing wiki' - ); - } - - /** - * @covers SiteConfiguration::get - */ - public function testParameterReplacement() { - $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback'; - - $this->assertEquals( - 'en wiki enwiki', - $this->mConf->get( 'WithParams', 'enwiki', 'wiki' ), - 'get(): parameter replacement on an existing wiki' - ); - $this->assertEquals( - 'de wiki dewiki', - $this->mConf->get( 'WithParams', 'dewiki', 'wiki' ), - 'get(): parameter replacement on an existing wiki (2)' - ); - $this->assertEquals( - 'fr wiki frwiki', - $this->mConf->get( 'WithParams', 'frwiki', 'wiki' ), - 'get(): parameter replacement on an existing wiki (3)' - ); - $this->assertEquals( - ' wiki wiki', - $this->mConf->get( 'WithParams', 'wiki', 'wiki' ), - 'get(): parameter replacement on an suffix' - ); - $this->assertEquals( - 'es wiki eswiki', - $this->mConf->get( 'WithParams', 'eswiki', 'wiki' ), - 'get(): parameter replacement on an non-existing wiki' - ); - } - - /** - * @covers SiteConfiguration::getAll - */ - public function testGetAllGlobals() { - $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback'; - - $getall = [ - 'SimpleKey' => 'enwiki', - 'Fallback' => 'tag', - 'WithParams' => 'en wiki enwiki', - 'SomeGlobal' => [ 'enwiki' => 'enwiki' ] + $GLOBALS['SomeGlobal'], - 'MergeIt' => [ - 'enwiki' => 'enwiki', - 'tag' => 'tag', - 'wiki' => 'wiki', - 'default' => 'default' - ], - ]; - $this->assertEquals( $getall, $this->mConf->getAll( 'enwiki' ), 'getAll()' ); - - $this->mConf->extractAllGlobals( 'enwiki', 'wiki' ); - - $this->assertEquals( - $getall['SimpleKey'], - $GLOBALS['SimpleKey'], - 'extractAllGlobals(): simple setting' - ); - $this->assertEquals( - $getall['Fallback'], - $GLOBALS['Fallback'], - 'extractAllGlobals(): fallback setting' - ); - $this->assertEquals( - $getall['WithParams'], - $GLOBALS['WithParams'], - 'extractAllGlobals(): parameter replacement' - ); - $this->assertEquals( - $getall['SomeGlobal'], - $GLOBALS['SomeGlobal'], - 'extractAllGlobals(): merging with global' - ); - $this->assertEquals( - $getall['MergeIt'], - $GLOBALS['MergeIt'], - 'extractAllGlobals(): merging setting' - ); - } -} diff --git a/tests/phpunit/includes/Storage/BlobStoreFactoryTest.php b/tests/phpunit/includes/Storage/BlobStoreFactoryTest.php deleted file mode 100644 index 252c657867..0000000000 --- a/tests/phpunit/includes/Storage/BlobStoreFactoryTest.php +++ /dev/null @@ -1,46 +0,0 @@ -getBlobStoreFactory(); - $store = $factory->newBlobStore( $wikiId ); - $this->assertInstanceOf( BlobStore::class, $store ); - - // This only works as we currently know this is a SqlBlobStore object - $wrapper = TestingAccessWrapper::newFromObject( $store ); - $this->assertEquals( $wikiId, $wrapper->wikiId ); - } - - /** - * @dataProvider provideWikiIds - */ - public function testNewSqlBlobStore( $wikiId ) { - $factory = MediaWikiServices::getInstance()->getBlobStoreFactory(); - $store = $factory->newSqlBlobStore( $wikiId ); - $this->assertInstanceOf( SqlBlobStore::class, $store ); - - $wrapper = TestingAccessWrapper::newFromObject( $store ); - $this->assertEquals( $wikiId, $wrapper->wikiId ); - } - -} diff --git a/tests/phpunit/includes/Storage/PreparedEditTest.php b/tests/phpunit/includes/Storage/PreparedEditTest.php deleted file mode 100644 index 29999ee535..0000000000 --- a/tests/phpunit/includes/Storage/PreparedEditTest.php +++ /dev/null @@ -1,22 +0,0 @@ -parserOutputCallback = function () { - return new ParserOutput(); - }; - - $this->assertEquals( $output, $edit->getOutput() ); - $this->assertEquals( $output, $edit->output ); - } -} diff --git a/tests/phpunit/includes/TitleArrayFromResultTest.php b/tests/phpunit/includes/TitleArrayFromResultTest.php deleted file mode 100644 index 32c757101a..0000000000 --- a/tests/phpunit/includes/TitleArrayFromResultTest.php +++ /dev/null @@ -1,117 +0,0 @@ -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 index e4b21ce5ac..0000000000 --- a/tests/phpunit/includes/WikiReferenceTest.php +++ /dev/null @@ -1,166 +0,0 @@ - [ '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/XmlJsTest.php b/tests/phpunit/includes/XmlJsTest.php deleted file mode 100644 index c7975efabc..0000000000 --- a/tests/phpunit/includes/XmlJsTest.php +++ /dev/null @@ -1,26 +0,0 @@ -assertEquals( $value, $obj->value ); - } - - public static function provideConstruction() { - return [ - [ null ], - [ '' ], - ]; - } - -} diff --git a/tests/phpunit/includes/XmlSelectTest.php b/tests/phpunit/includes/XmlSelectTest.php deleted file mode 100644 index 52e20bdb99..0000000000 --- a/tests/phpunit/includes/XmlSelectTest.php +++ /dev/null @@ -1,182 +0,0 @@ -select = new XmlSelect(); - } - - protected function tearDown() { - parent::tearDown(); - $this->select = null; - } - - /** - * @covers XmlSelect::__construct - */ - public function testConstructWithoutParameters() { - $this->assertEquals( '', $this->select->getHTML() ); - } - - /** - * Parameters are $name (false), $id (false), $default (false) - * @dataProvider provideConstructionParameters - * @covers XmlSelect::__construct - */ - public function testConstructParameters( $name, $id, $default, $expected ) { - $this->select = new XmlSelect( $name, $id, $default ); - $this->assertEquals( $expected, $this->select->getHTML() ); - } - - /** - * Provide parameters for testConstructParameters() which use three - * parameters: - * - $name (default: false) - * - $id (default: false) - * - $default (default: false) - * Provides a fourth parameters representing the expected HTML output - */ - public static function provideConstructionParameters() { - return [ - /** - * Values are set following a 3-bit Gray code where two successive - * values differ by only one value. - * See https://en.wikipedia.org/wiki/Gray_code - */ - # $name $id $default - [ false, false, false, '' ], - [ false, false, 'foo', '' ], - [ false, 'id', 'foo', '' ], - [ false, 'id', false, '' ], - [ 'name', 'id', false, '' ], - [ 'name', 'id', 'foo', '' ], - [ 'name', false, 'foo', '' ], - [ 'name', false, false, '' ], - ]; - } - - /** - * @covers XmlSelect::addOption - */ - public function testAddOption() { - $this->select->addOption( 'foo' ); - $this->assertEquals( - '', - $this->select->getHTML() - ); - } - - /** - * @covers XmlSelect::addOption - */ - public function testAddOptionWithDefault() { - $this->select->addOption( 'foo', true ); - $this->assertEquals( - '', - $this->select->getHTML() - ); - } - - /** - * @covers XmlSelect::addOption - */ - public function testAddOptionWithFalse() { - $this->select->addOption( 'foo', false ); - $this->assertEquals( - '', - $this->select->getHTML() - ); - } - - /** - * @covers XmlSelect::addOption - */ - public function testAddOptionWithValueZero() { - $this->select->addOption( 'foo', 0 ); - $this->assertEquals( - '', - $this->select->getHTML() - ); - } - - /** - * @covers XmlSelect::setDefault - */ - public function testSetDefault() { - $this->select->setDefault( 'bar1' ); - $this->select->addOption( 'foo1' ); - $this->select->addOption( 'bar1' ); - $this->select->addOption( 'foo2' ); - $this->assertEquals( - '', $this->select->getHTML() ); - } - - /** - * Adding default later on should set the correct selection or - * raise an exception. - * To handle this, we need to render the options in getHtml() - * @covers XmlSelect::setDefault - */ - public function testSetDefaultAfterAddingOptions() { - $this->select->addOption( 'foo1' ); - $this->select->addOption( 'bar1' ); - $this->select->addOption( 'foo2' ); - $this->select->setDefault( 'bar1' ); # setting default after adding options - $this->assertEquals( - '', $this->select->getHTML() ); - } - - /** - * @covers XmlSelect::setAttribute - * @covers XmlSelect::getAttribute - */ - public function testGetAttributes() { - # create some attributes - $this->select->setAttribute( 'dummy', 0x777 ); - $this->select->setAttribute( 'string', 'euro €' ); - $this->select->setAttribute( 1911, 'razor' ); - - # verify we can retrieve them - $this->assertEquals( - $this->select->getAttribute( 'dummy' ), - 0x777 - ); - $this->assertEquals( - $this->select->getAttribute( 'string' ), - 'euro €' - ); - $this->assertEquals( - $this->select->getAttribute( 1911 ), - 'razor' - ); - - # inexistent keys should give us 'null' - $this->assertEquals( - $this->select->getAttribute( 'I DO NOT EXIT' ), - null - ); - - # verify string / integer - $this->assertEquals( - $this->select->getAttribute( '1911' ), - 'razor' - ); - $this->assertEquals( - $this->select->getAttribute( 'dummy' ), - 0x777 - ); - } -} diff --git a/tests/phpunit/includes/actions/ViewActionTest.php b/tests/phpunit/includes/actions/ViewActionTest.php deleted file mode 100644 index 5f659c07a2..0000000000 --- a/tests/phpunit/includes/actions/ViewActionTest.php +++ /dev/null @@ -1,35 +0,0 @@ -makeViewActionClassFactory(); - $actual = $viewAction->getName(); - - $this->assertSame( 'view', $actual ); - } - - public function testOnView() { - $viewAction = $this->makeViewActionClassFactory(); - $actual = $viewAction->onView(); - - $this->assertNull( $actual ); - } -} diff --git a/tests/phpunit/includes/api/ApiBlockInfoTraitTest.php b/tests/phpunit/includes/api/ApiBlockInfoTraitTest.php deleted file mode 100644 index ba5c003776..0000000000 --- a/tests/phpunit/includes/api/ApiBlockInfoTraitTest.php +++ /dev/null @@ -1,43 +0,0 @@ -getMockForTrait( ApiBlockInfoTrait::class ); - $info = TestingAccessWrapper::newFromObject( $mock )->getBlockDetails( $block ); - $subset = array_merge( [ - 'blockid' => null, - 'blockedby' => '', - 'blockedbyid' => 0, - 'blockreason' => '', - 'blockexpiry' => 'infinite', - ], $expectedInfo ); - $this->assertArraySubset( $subset, $info ); - } - - public static function provideGetBlockDetails() { - return [ - 'Sitewide block' => [ - new DatabaseBlock(), - [ 'blockpartial' => false ], - ], - 'Partial block' => [ - new DatabaseBlock( [ 'sitewide' => false ] ), - [ 'blockpartial' => true ], - ], - 'System block' => [ - new SystemBlock( [ 'systemBlock' => 'proxy' ] ), - [ 'systemblocktype' => 'proxy' ] - ], - ]; - } -} diff --git a/tests/phpunit/includes/api/ApiContinuationManagerTest.php b/tests/phpunit/includes/api/ApiContinuationManagerTest.php deleted file mode 100644 index 788d120c6b..0000000000 --- a/tests/phpunit/includes/api/ApiContinuationManagerTest.php +++ /dev/null @@ -1,198 +0,0 @@ -setRequest( new FauxRequest( [ 'continue' => $continue ] ) ); - $main = new ApiMain( $context ); - return new ApiContinuationManager( $main, $allModules, $generatedModules ); - } - - public function testContinuation() { - $allModules = [ - new MockApiQueryBase( 'mock1' ), - new MockApiQueryBase( 'mock2' ), - new MockApiQueryBase( 'mocklist' ), - ]; - $generator = new MockApiQueryBase( 'generator' ); - - $manager = self::getManager( '', $allModules, [ 'mock1', 'mock2' ] ); - $this->assertSame( ApiMain::class, $manager->getSource() ); - $this->assertSame( false, $manager->isGeneratorDone() ); - $this->assertSame( $allModules, $manager->getRunModules() ); - $manager->addContinueParam( $allModules[0], 'm1continue', [ 1, 2 ] ); - $manager->addContinueParam( $allModules[2], 'mlcontinue', 2 ); - $manager->addGeneratorContinueParam( $generator, 'gcontinue', 3 ); - $this->assertSame( [ [ - 'mlcontinue' => 2, - 'm1continue' => '1|2', - 'continue' => '||mock2', - ], false ], $manager->getContinuation() ); - $this->assertSame( [ - 'mock1' => [ 'm1continue' => '1|2' ], - 'mocklist' => [ 'mlcontinue' => 2 ], - 'generator' => [ 'gcontinue' => 3 ], - ], $manager->getRawContinuation() ); - - $result = new ApiResult( 0 ); - $manager->setContinuationIntoResult( $result ); - $this->assertSame( [ - 'mlcontinue' => 2, - 'm1continue' => '1|2', - 'continue' => '||mock2', - ], $result->getResultData( 'continue' ) ); - $this->assertSame( null, $result->getResultData( 'batchcomplete' ) ); - - $manager = self::getManager( '', $allModules, [ 'mock1', 'mock2' ] ); - $this->assertSame( false, $manager->isGeneratorDone() ); - $this->assertSame( $allModules, $manager->getRunModules() ); - $manager->addContinueParam( $allModules[0], 'm1continue', [ 1, 2 ] ); - $manager->addGeneratorContinueParam( $generator, 'gcontinue', [ 3, 4 ] ); - $this->assertSame( [ [ - 'm1continue' => '1|2', - 'continue' => '||mock2|mocklist', - ], false ], $manager->getContinuation() ); - $this->assertSame( [ - 'mock1' => [ 'm1continue' => '1|2' ], - 'generator' => [ 'gcontinue' => '3|4' ], - ], $manager->getRawContinuation() ); - - $manager = self::getManager( '', $allModules, [ 'mock1', 'mock2' ] ); - $this->assertSame( false, $manager->isGeneratorDone() ); - $this->assertSame( $allModules, $manager->getRunModules() ); - $manager->addContinueParam( $allModules[2], 'mlcontinue', 2 ); - $manager->addGeneratorContinueParam( $generator, 'gcontinue', 3 ); - $this->assertSame( [ [ - 'mlcontinue' => 2, - 'gcontinue' => 3, - 'continue' => 'gcontinue||', - ], true ], $manager->getContinuation() ); - $this->assertSame( [ - 'mocklist' => [ 'mlcontinue' => 2 ], - 'generator' => [ 'gcontinue' => 3 ], - ], $manager->getRawContinuation() ); - - $result = new ApiResult( 0 ); - $manager->setContinuationIntoResult( $result ); - $this->assertSame( [ - 'mlcontinue' => 2, - 'gcontinue' => 3, - 'continue' => 'gcontinue||', - ], $result->getResultData( 'continue' ) ); - $this->assertSame( true, $result->getResultData( 'batchcomplete' ) ); - - $manager = self::getManager( '', $allModules, [ 'mock1', 'mock2' ] ); - $this->assertSame( false, $manager->isGeneratorDone() ); - $this->assertSame( $allModules, $manager->getRunModules() ); - $manager->addGeneratorContinueParam( $generator, 'gcontinue', 3 ); - $this->assertSame( [ [ - 'gcontinue' => 3, - 'continue' => 'gcontinue||mocklist', - ], true ], $manager->getContinuation() ); - $this->assertSame( [ - 'generator' => [ 'gcontinue' => 3 ], - ], $manager->getRawContinuation() ); - - $manager = self::getManager( '', $allModules, [ 'mock1', 'mock2' ] ); - $this->assertSame( false, $manager->isGeneratorDone() ); - $this->assertSame( $allModules, $manager->getRunModules() ); - $manager->addContinueParam( $allModules[0], 'm1continue', [ 1, 2 ] ); - $manager->addContinueParam( $allModules[2], 'mlcontinue', 2 ); - $this->assertSame( [ [ - 'mlcontinue' => 2, - 'm1continue' => '1|2', - 'continue' => '||mock2', - ], false ], $manager->getContinuation() ); - $this->assertSame( [ - 'mock1' => [ 'm1continue' => '1|2' ], - 'mocklist' => [ 'mlcontinue' => 2 ], - ], $manager->getRawContinuation() ); - - $manager = self::getManager( '', $allModules, [ 'mock1', 'mock2' ] ); - $this->assertSame( false, $manager->isGeneratorDone() ); - $this->assertSame( $allModules, $manager->getRunModules() ); - $manager->addContinueParam( $allModules[0], 'm1continue', [ 1, 2 ] ); - $this->assertSame( [ [ - 'm1continue' => '1|2', - 'continue' => '||mock2|mocklist', - ], false ], $manager->getContinuation() ); - $this->assertSame( [ - 'mock1' => [ 'm1continue' => '1|2' ], - ], $manager->getRawContinuation() ); - - $manager = self::getManager( '', $allModules, [ 'mock1', 'mock2' ] ); - $this->assertSame( false, $manager->isGeneratorDone() ); - $this->assertSame( $allModules, $manager->getRunModules() ); - $manager->addContinueParam( $allModules[2], 'mlcontinue', 2 ); - $this->assertSame( [ [ - 'mlcontinue' => 2, - 'continue' => '-||mock1|mock2', - ], true ], $manager->getContinuation() ); - $this->assertSame( [ - 'mocklist' => [ 'mlcontinue' => 2 ], - ], $manager->getRawContinuation() ); - - $manager = self::getManager( '', $allModules, [ 'mock1', 'mock2' ] ); - $this->assertSame( false, $manager->isGeneratorDone() ); - $this->assertSame( $allModules, $manager->getRunModules() ); - $this->assertSame( [ [], true ], $manager->getContinuation() ); - $this->assertSame( [], $manager->getRawContinuation() ); - - $manager = self::getManager( '||mock2', $allModules, [ 'mock1', 'mock2' ] ); - $this->assertSame( false, $manager->isGeneratorDone() ); - $this->assertSame( - array_values( array_diff_key( $allModules, [ 1 => 1 ] ) ), - $manager->getRunModules() - ); - - $manager = self::getManager( '-||', $allModules, [ 'mock1', 'mock2' ] ); - $this->assertSame( true, $manager->isGeneratorDone() ); - $this->assertSame( - array_values( array_diff_key( $allModules, [ 0 => 0, 1 => 1 ] ) ), - $manager->getRunModules() - ); - - try { - self::getManager( 'foo', $allModules, [ 'mock1', 'mock2' ] ); - $this->fail( 'Expected exception not thrown' ); - } catch ( ApiUsageException $ex ) { - $this->assertTrue( ApiTestCase::apiExceptionHasCode( $ex, 'badcontinue' ), - 'Expected exception' - ); - } - - $manager = self::getManager( - '||mock2', - array_slice( $allModules, 0, 2 ), - [ 'mock1', 'mock2' ] - ); - try { - $manager->addContinueParam( $allModules[1], 'm2continue', 1 ); - $this->fail( 'Expected exception not thrown' ); - } catch ( UnexpectedValueException $ex ) { - $this->assertSame( - 'Module \'mock2\' was not supposed to have been executed, ' . - 'but it was executed anyway', - $ex->getMessage(), - 'Expected exception' - ); - } - try { - $manager->addContinueParam( $allModules[2], 'mlcontinue', 1 ); - $this->fail( 'Expected exception not thrown' ); - } catch ( UnexpectedValueException $ex ) { - $this->assertSame( - 'Module \'mocklist\' called ApiContinuationManager::addContinueParam ' . - 'but was not passed to ApiContinuationManager::__construct', - $ex->getMessage(), - 'Expected exception' - ); - } - } - -} diff --git a/tests/phpunit/includes/api/ApiMessageTest.php b/tests/phpunit/includes/api/ApiMessageTest.php deleted file mode 100644 index 70114c2593..0000000000 --- a/tests/phpunit/includes/api/ApiMessageTest.php +++ /dev/null @@ -1,196 +0,0 @@ -assertSame( $msg->getKey(), $msg2->getKey(), 'getKey' ); - $this->assertSame( $msg->getKeysToTry(), $msg2->getKeysToTry(), 'getKeysToTry' ); - $this->assertSame( $msg->getParams(), $msg2->getParams(), 'getParams' ); - $this->assertSame( $msg->getLanguage(), $msg2->getLanguage(), 'getLanguage' ); - - $msg = TestingAccessWrapper::newFromObject( $msg ); - $msg2 = TestingAccessWrapper::newFromObject( $msg2 ); - $this->assertSame( $msg->interface, $msg2->interface, 'interface' ); - $this->assertSame( $msg->useDatabase, $msg2->useDatabase, 'useDatabase' ); - $this->assertSame( $msg->format, $msg2->format, 'format' ); - $this->assertSame( - $msg->title ? $msg->title->getFullText() : null, - $msg2->title ? $msg2->title->getFullText() : null, - 'title' - ); - } - - /** - * @covers ApiMessageTrait - */ - public function testCodeDefaults() { - $msg = new ApiMessage( 'foo' ); - $this->assertSame( 'foo', $msg->getApiCode() ); - - $msg = new ApiMessage( 'apierror-bar' ); - $this->assertSame( 'bar', $msg->getApiCode() ); - - $msg = new ApiMessage( 'apiwarn-baz' ); - $this->assertSame( 'baz', $msg->getApiCode() ); - - // Weird "message key" - $msg = new ApiMessage( " bar\nbaz" ); - $this->assertSame( '_foo__bar_baz', $msg->getApiCode() ); - - // BC case - $msg = new ApiMessage( 'actionthrottledtext' ); - $this->assertSame( 'ratelimited', $msg->getApiCode() ); - - $msg = new ApiMessage( [ 'apierror-missingparam', 'param' ] ); - $this->assertSame( 'noparam', $msg->getApiCode() ); - } - - /** - * @covers ApiMessageTrait - * @dataProvider provideInvalidCode - * @param mixed $code - */ - public function testInvalidCode( $code ) { - $msg = new ApiMessage( 'foo' ); - try { - $msg->setApiCode( $code ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertTrue( true ); - } - - try { - new ApiMessage( 'foo', $code ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertTrue( true ); - } - } - - public static function provideInvalidCode() { - return [ - [ '' ], - [ 42 ], - [ 'A bad code' ], - [ 'Project:A_page_title' ], - [ "WTF\nnewlines" ], - ]; - } - - /** - * @covers ApiMessage - * @covers ApiMessageTrait - */ - public function testApiMessage() { - $msg = new Message( [ 'foo', 'bar' ], [ 'baz' ] ); - $msg->inLanguage( 'de' )->title( Title::newMainPage() ); - $msg2 = new ApiMessage( $msg, 'code', [ 'data' ] ); - $this->compareMessages( $msg, $msg2 ); - $this->assertEquals( 'code', $msg2->getApiCode() ); - $this->assertEquals( [ 'data' ], $msg2->getApiData() ); - - $msg2 = unserialize( serialize( $msg2 ) ); - $this->compareMessages( $msg, $msg2 ); - $this->assertEquals( 'code', $msg2->getApiCode() ); - $this->assertEquals( [ 'data' ], $msg2->getApiData() ); - - $msg = new Message( [ 'foo', 'bar' ], [ 'baz' ] ); - $msg2 = new ApiMessage( [ [ 'foo', 'bar' ], 'baz' ], 'code', [ 'data' ] ); - $this->compareMessages( $msg, $msg2 ); - $this->assertEquals( 'code', $msg2->getApiCode() ); - $this->assertEquals( [ 'data' ], $msg2->getApiData() ); - - $msg = new Message( 'foo' ); - $msg2 = new ApiMessage( 'foo' ); - $this->compareMessages( $msg, $msg2 ); - $this->assertEquals( 'foo', $msg2->getApiCode() ); - $this->assertEquals( [], $msg2->getApiData() ); - - $msg2->setApiCode( 'code', [ 'data' ] ); - $this->assertEquals( 'code', $msg2->getApiCode() ); - $this->assertEquals( [ 'data' ], $msg2->getApiData() ); - $msg2->setApiCode( null ); - $this->assertEquals( 'foo', $msg2->getApiCode() ); - $this->assertEquals( [ 'data' ], $msg2->getApiData() ); - $msg2->setApiData( [ 'data2' ] ); - $this->assertEquals( [ 'data2' ], $msg2->getApiData() ); - } - - /** - * @covers ApiRawMessage - * @covers ApiMessageTrait - */ - public function testApiRawMessage() { - $msg = new RawMessage( 'foo', [ 'baz' ] ); - $msg->inLanguage( 'de' )->title( Title::newMainPage() ); - $msg2 = new ApiRawMessage( $msg, 'code', [ 'data' ] ); - $this->compareMessages( $msg, $msg2 ); - $this->assertEquals( 'code', $msg2->getApiCode() ); - $this->assertEquals( [ 'data' ], $msg2->getApiData() ); - - $msg2 = unserialize( serialize( $msg2 ) ); - $this->compareMessages( $msg, $msg2 ); - $this->assertEquals( 'code', $msg2->getApiCode() ); - $this->assertEquals( [ 'data' ], $msg2->getApiData() ); - - $msg = new RawMessage( 'foo', [ 'baz' ] ); - $msg2 = new ApiRawMessage( [ 'foo', 'baz' ], 'code', [ 'data' ] ); - $this->compareMessages( $msg, $msg2 ); - $this->assertEquals( 'code', $msg2->getApiCode() ); - $this->assertEquals( [ 'data' ], $msg2->getApiData() ); - - $msg = new RawMessage( 'foo' ); - $msg2 = new ApiRawMessage( 'foo', 'code', [ 'data' ] ); - $this->compareMessages( $msg, $msg2 ); - $this->assertEquals( 'code', $msg2->getApiCode() ); - $this->assertEquals( [ 'data' ], $msg2->getApiData() ); - - $msg2->setApiCode( 'code', [ 'data' ] ); - $this->assertEquals( 'code', $msg2->getApiCode() ); - $this->assertEquals( [ 'data' ], $msg2->getApiData() ); - $msg2->setApiCode( null ); - $this->assertEquals( 'foo', $msg2->getApiCode() ); - $this->assertEquals( [ 'data' ], $msg2->getApiData() ); - $msg2->setApiData( [ 'data2' ] ); - $this->assertEquals( [ 'data2' ], $msg2->getApiData() ); - } - - /** - * @covers ApiMessage::create - */ - public function testApiMessageCreate() { - $this->assertInstanceOf( ApiMessage::class, ApiMessage::create( new Message( 'mainpage' ) ) ); - $this->assertInstanceOf( - ApiRawMessage::class, ApiMessage::create( new RawMessage( 'mainpage' ) ) - ); - $this->assertInstanceOf( ApiMessage::class, ApiMessage::create( 'mainpage' ) ); - - $msg = new ApiMessage( [ 'parentheses', 'foobar' ] ); - $msg2 = new Message( 'parentheses', [ 'foobar' ] ); - - $this->assertSame( $msg, ApiMessage::create( $msg ) ); - $this->assertEquals( $msg, ApiMessage::create( $msg2 ) ); - $this->assertEquals( $msg, ApiMessage::create( [ 'parentheses', 'foobar' ] ) ); - $this->assertEquals( $msg, - ApiMessage::create( [ 'message' => 'parentheses', 'params' => [ 'foobar' ] ] ) - ); - $this->assertSame( $msg, - ApiMessage::create( [ 'message' => $msg, 'params' => [ 'xxx' ] ] ) - ); - $this->assertEquals( $msg, - ApiMessage::create( [ 'message' => $msg2, 'params' => [ 'xxx' ] ] ) - ); - $this->assertSame( $msg, - ApiMessage::create( [ 'message' => $msg ] ) - ); - - $msg = new ApiRawMessage( [ 'parentheses', 'foobar' ] ); - $this->assertSame( $msg, ApiMessage::create( $msg ) ); - } - -} diff --git a/tests/phpunit/includes/api/ApiResultTest.php b/tests/phpunit/includes/api/ApiResultTest.php deleted file mode 100644 index 98e24fb666..0000000000 --- a/tests/phpunit/includes/api/ApiResultTest.php +++ /dev/null @@ -1,1410 +0,0 @@ -assertSame( [ - 'setValue' => '1', - 'unnamed 1', - 'unnamed 2', - ApiResult::META_CONTENT => 'setContentValue', - 'setContentValue' => '3', - ], $arr ); - - try { - ApiResult::setValue( $arr, 'setValue', '99' ); - $this->fail( 'Expected exception not thrown' ); - } catch ( RuntimeException $ex ) { - $this->assertSame( - 'Attempting to add element setValue=99, existing value is 1', - $ex->getMessage(), - 'Expected exception' - ); - } - - try { - ApiResult::setContentValue( $arr, 'setContentValue2', '99' ); - $this->fail( 'Expected exception not thrown' ); - } catch ( RuntimeException $ex ) { - $this->assertSame( - 'Attempting to set content element as setContentValue2 when setContentValue ' . - 'is already set as the content element', - $ex->getMessage(), - 'Expected exception' - ); - } - - ApiResult::setValue( $arr, 'setValue', '99', ApiResult::OVERRIDE ); - $this->assertSame( '99', $arr['setValue'] ); - - ApiResult::setContentValue( $arr, 'setContentValue2', '99', ApiResult::OVERRIDE ); - $this->assertSame( 'setContentValue2', $arr[ApiResult::META_CONTENT] ); - - $arr = [ 'foo' => 1, 'bar' => 1 ]; - ApiResult::setValue( $arr, 'top', '2', ApiResult::ADD_ON_TOP ); - ApiResult::setValue( $arr, null, '2', ApiResult::ADD_ON_TOP ); - ApiResult::setValue( $arr, 'bottom', '2' ); - ApiResult::setValue( $arr, 'foo', '2', ApiResult::OVERRIDE ); - ApiResult::setValue( $arr, 'bar', '2', ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP ); - $this->assertSame( [ 0, 'top', 'foo', 'bar', 'bottom' ], array_keys( $arr ) ); - - $arr = []; - ApiResult::setValue( $arr, 'sub', [ 'foo' => 1 ] ); - ApiResult::setValue( $arr, 'sub', [ 'bar' => 1 ] ); - $this->assertSame( [ 'sub' => [ 'foo' => 1, 'bar' => 1 ] ], $arr ); - - try { - ApiResult::setValue( $arr, 'sub', [ 'foo' => 2, 'baz' => 2 ] ); - $this->fail( 'Expected exception not thrown' ); - } catch ( RuntimeException $ex ) { - $this->assertSame( - 'Conflicting keys (foo) when attempting to merge element sub', - $ex->getMessage(), - 'Expected exception' - ); - } - - $arr = []; - $title = Title::newFromText( "MediaWiki:Foobar" ); - $obj = new stdClass; - $obj->foo = 1; - $obj->bar = 2; - ApiResult::setValue( $arr, 'title', $title ); - ApiResult::setValue( $arr, 'obj', $obj ); - $this->assertSame( [ - 'title' => (string)$title, - 'obj' => [ 'foo' => 1, 'bar' => 2, ApiResult::META_TYPE => 'assoc' ], - ], $arr ); - - $fh = tmpfile(); - try { - ApiResult::setValue( $arr, 'file', $fh ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertSame( - 'Cannot add resource(stream) to ApiResult', - $ex->getMessage(), - 'Expected exception' - ); - } - try { - ApiResult::setValue( $arr, null, $fh ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertSame( - 'Cannot add resource(stream) to ApiResult', - $ex->getMessage(), - 'Expected exception' - ); - } - try { - $obj->file = $fh; - ApiResult::setValue( $arr, 'sub', $obj ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertSame( - 'Cannot add resource(stream) to ApiResult', - $ex->getMessage(), - 'Expected exception' - ); - } - try { - $obj->file = $fh; - ApiResult::setValue( $arr, null, $obj ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertSame( - 'Cannot add resource(stream) to ApiResult', - $ex->getMessage(), - 'Expected exception' - ); - } - fclose( $fh ); - - try { - ApiResult::setValue( $arr, 'inf', INF ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertSame( - 'Cannot add non-finite floats to ApiResult', - $ex->getMessage(), - 'Expected exception' - ); - } - try { - ApiResult::setValue( $arr, null, INF ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertSame( - 'Cannot add non-finite floats to ApiResult', - $ex->getMessage(), - 'Expected exception' - ); - } - try { - ApiResult::setValue( $arr, 'nan', NAN ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertSame( - 'Cannot add non-finite floats to ApiResult', - $ex->getMessage(), - 'Expected exception' - ); - } - try { - ApiResult::setValue( $arr, null, NAN ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertSame( - 'Cannot add non-finite floats to ApiResult', - $ex->getMessage(), - 'Expected exception' - ); - } - - ApiResult::setValue( $arr, null, NAN, ApiResult::NO_VALIDATE ); - - try { - ApiResult::setValue( $arr, null, NAN, ApiResult::NO_SIZE_CHECK ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertSame( - 'Cannot add non-finite floats to ApiResult', - $ex->getMessage(), - 'Expected exception' - ); - } - - $arr = []; - $result2 = new ApiResult( 8388608 ); - $result2->addValue( null, 'foo', 'bar' ); - ApiResult::setValue( $arr, 'baz', $result2 ); - $this->assertSame( [ - 'baz' => [ - ApiResult::META_TYPE => 'assoc', - 'foo' => 'bar', - ] - ], $arr ); - - $arr = []; - ApiResult::setValue( $arr, 'foo', "foo\x80bar" ); - ApiResult::setValue( $arr, 'bar', "a\xcc\x81" ); - ApiResult::setValue( $arr, 'baz', 74 ); - ApiResult::setValue( $arr, null, "foo\x80bar" ); - ApiResult::setValue( $arr, null, "a\xcc\x81" ); - $this->assertSame( [ - 'foo' => "foo\xef\xbf\xbdbar", - 'bar' => "\xc3\xa1", - 'baz' => 74, - 0 => "foo\xef\xbf\xbdbar", - 1 => "\xc3\xa1", - ], $arr ); - - $obj = new stdClass; - $obj->{'1'} = 'one'; - $arr = []; - ApiResult::setValue( $arr, 'foo', $obj ); - $this->assertSame( [ - 'foo' => [ - 1 => 'one', - ApiResult::META_TYPE => 'assoc', - ] - ], $arr ); - } - - /** - * @covers ApiResult - */ - public function testInstanceDataMethods() { - $result = new ApiResult( 8388608 ); - - $result->addValue( null, 'setValue', '1' ); - - $result->addValue( null, null, 'unnamed 1' ); - $result->addValue( null, null, 'unnamed 2' ); - - $result->addValue( null, 'deleteValue', '2' ); - $result->removeValue( null, 'deleteValue' ); - - $result->addValue( [ 'a', 'b' ], 'deleteValue', '3' ); - $result->removeValue( [ 'a', 'b', 'deleteValue' ], null, '3' ); - - $result->addContentValue( null, 'setContentValue', '3' ); - - $this->assertSame( [ - 'setValue' => '1', - 'unnamed 1', - 'unnamed 2', - 'a' => [ 'b' => [] ], - 'setContentValue' => '3', - ApiResult::META_TYPE => 'assoc', - ApiResult::META_CONTENT => 'setContentValue', - ], $result->getResultData() ); - $this->assertSame( 20, $result->getSize() ); - - try { - $result->addValue( null, 'setValue', '99' ); - $this->fail( 'Expected exception not thrown' ); - } catch ( RuntimeException $ex ) { - $this->assertSame( - 'Attempting to add element setValue=99, existing value is 1', - $ex->getMessage(), - 'Expected exception' - ); - } - - try { - $result->addContentValue( null, 'setContentValue2', '99' ); - $this->fail( 'Expected exception not thrown' ); - } catch ( RuntimeException $ex ) { - $this->assertSame( - 'Attempting to set content element as setContentValue2 when setContentValue ' . - 'is already set as the content element', - $ex->getMessage(), - 'Expected exception' - ); - } - - $result->addValue( null, 'setValue', '99', ApiResult::OVERRIDE ); - $this->assertSame( '99', $result->getResultData( [ 'setValue' ] ) ); - - $result->addContentValue( null, 'setContentValue2', '99', ApiResult::OVERRIDE ); - $this->assertSame( 'setContentValue2', - $result->getResultData( [ ApiResult::META_CONTENT ] ) ); - - $result->reset(); - $this->assertSame( [ - ApiResult::META_TYPE => 'assoc', - ], $result->getResultData() ); - $this->assertSame( 0, $result->getSize() ); - - $result->addValue( null, 'foo', 1 ); - $result->addValue( null, 'bar', 1 ); - $result->addValue( null, 'top', '2', ApiResult::ADD_ON_TOP ); - $result->addValue( null, null, '2', ApiResult::ADD_ON_TOP ); - $result->addValue( null, 'bottom', '2' ); - $result->addValue( null, 'foo', '2', ApiResult::OVERRIDE ); - $result->addValue( null, 'bar', '2', ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP ); - $this->assertSame( [ 0, 'top', 'foo', 'bar', 'bottom', ApiResult::META_TYPE ], - array_keys( $result->getResultData() ) ); - - $result->reset(); - $result->addValue( null, 'foo', [ 'bar' => 1 ] ); - $result->addValue( [ 'foo', 'top' ], 'x', 2, ApiResult::ADD_ON_TOP ); - $result->addValue( [ 'foo', 'bottom' ], 'x', 2 ); - $this->assertSame( [ 'top', 'bar', 'bottom' ], - array_keys( $result->getResultData( [ 'foo' ] ) ) ); - - $result->reset(); - $result->addValue( null, 'sub', [ 'foo' => 1 ] ); - $result->addValue( null, 'sub', [ 'bar' => 1 ] ); - $this->assertSame( [ - 'sub' => [ 'foo' => 1, 'bar' => 1 ], - ApiResult::META_TYPE => 'assoc', - ], $result->getResultData() ); - - try { - $result->addValue( null, 'sub', [ 'foo' => 2, 'baz' => 2 ] ); - $this->fail( 'Expected exception not thrown' ); - } catch ( RuntimeException $ex ) { - $this->assertSame( - 'Conflicting keys (foo) when attempting to merge element sub', - $ex->getMessage(), - 'Expected exception' - ); - } - - $result->reset(); - $title = Title::newFromText( "MediaWiki:Foobar" ); - $obj = new stdClass; - $obj->foo = 1; - $obj->bar = 2; - $result->addValue( null, 'title', $title ); - $result->addValue( null, 'obj', $obj ); - $this->assertSame( [ - 'title' => (string)$title, - 'obj' => [ 'foo' => 1, 'bar' => 2, ApiResult::META_TYPE => 'assoc' ], - ApiResult::META_TYPE => 'assoc', - ], $result->getResultData() ); - - $fh = tmpfile(); - try { - $result->addValue( null, 'file', $fh ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertSame( - 'Cannot add resource(stream) to ApiResult', - $ex->getMessage(), - 'Expected exception' - ); - } - try { - $result->addValue( null, null, $fh ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertSame( - 'Cannot add resource(stream) to ApiResult', - $ex->getMessage(), - 'Expected exception' - ); - } - try { - $obj->file = $fh; - $result->addValue( null, 'sub', $obj ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertSame( - 'Cannot add resource(stream) to ApiResult', - $ex->getMessage(), - 'Expected exception' - ); - } - try { - $obj->file = $fh; - $result->addValue( null, null, $obj ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertSame( - 'Cannot add resource(stream) to ApiResult', - $ex->getMessage(), - 'Expected exception' - ); - } - fclose( $fh ); - - try { - $result->addValue( null, 'inf', INF ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertSame( - 'Cannot add non-finite floats to ApiResult', - $ex->getMessage(), - 'Expected exception' - ); - } - try { - $result->addValue( null, null, INF ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertSame( - 'Cannot add non-finite floats to ApiResult', - $ex->getMessage(), - 'Expected exception' - ); - } - try { - $result->addValue( null, 'nan', NAN ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertSame( - 'Cannot add non-finite floats to ApiResult', - $ex->getMessage(), - 'Expected exception' - ); - } - try { - $result->addValue( null, null, NAN ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertSame( - 'Cannot add non-finite floats to ApiResult', - $ex->getMessage(), - 'Expected exception' - ); - } - - $result->addValue( null, null, NAN, ApiResult::NO_VALIDATE ); - - try { - $result->addValue( null, null, NAN, ApiResult::NO_SIZE_CHECK ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertSame( - 'Cannot add non-finite floats to ApiResult', - $ex->getMessage(), - 'Expected exception' - ); - } - - $result->reset(); - $result->addParsedLimit( 'foo', 12 ); - $this->assertSame( [ - 'limits' => [ 'foo' => 12 ], - ApiResult::META_TYPE => 'assoc', - ], $result->getResultData() ); - $result->addParsedLimit( 'foo', 13 ); - $this->assertSame( [ - 'limits' => [ 'foo' => 13 ], - ApiResult::META_TYPE => 'assoc', - ], $result->getResultData() ); - $this->assertSame( null, $result->getResultData( [ 'foo', 'bar', 'baz' ] ) ); - $this->assertSame( 13, $result->getResultData( [ 'limits', 'foo' ] ) ); - try { - $result->getResultData( [ 'limits', 'foo', 'bar' ] ); - $this->fail( 'Expected exception not thrown' ); - } catch ( InvalidArgumentException $ex ) { - $this->assertSame( - 'Path limits.foo is not an array', - $ex->getMessage(), - 'Expected exception' - ); - } - - // Add two values and some metadata, but ensure metadata is not counted - $result = new ApiResult( 100 ); - $obj = [ 'attr' => '12345' ]; - ApiResult::setContentValue( $obj, 'content', '1234567890' ); - $this->assertTrue( $result->addValue( null, 'foo', $obj ) ); - $this->assertSame( 15, $result->getSize() ); - - $result = new ApiResult( 10 ); - $formatter = new ApiErrorFormatter( $result, Language::factory( 'en' ), 'none', false ); - $result->setErrorFormatter( $formatter ); - $this->assertFalse( $result->addValue( null, 'foo', '12345678901' ) ); - $this->assertTrue( $result->addValue( null, 'foo', '12345678901', ApiResult::NO_SIZE_CHECK ) ); - $this->assertSame( 0, $result->getSize() ); - $result->reset(); - $this->assertTrue( $result->addValue( null, 'foo', '1234567890' ) ); - $this->assertFalse( $result->addValue( null, 'foo', '1' ) ); - $result->removeValue( null, 'foo' ); - $this->assertTrue( $result->addValue( null, 'foo', '1' ) ); - - $result = new ApiResult( 10 ); - $obj = new ApiResultTestSerializableObject( 'ok' ); - $obj->foobar = 'foobaz'; - $this->assertTrue( $result->addValue( null, 'foo', $obj ) ); - $this->assertSame( 2, $result->getSize() ); - - $result = new ApiResult( 8388608 ); - $result2 = new ApiResult( 8388608 ); - $result2->addValue( null, 'foo', 'bar' ); - $result->addValue( null, 'baz', $result2 ); - $this->assertSame( [ - 'baz' => [ - 'foo' => 'bar', - ApiResult::META_TYPE => 'assoc', - ], - ApiResult::META_TYPE => 'assoc', - ], $result->getResultData() ); - - $result = new ApiResult( 8388608 ); - $result->addValue( null, 'foo', "foo\x80bar" ); - $result->addValue( null, 'bar', "a\xcc\x81" ); - $result->addValue( null, 'baz', 74 ); - $result->addValue( null, null, "foo\x80bar" ); - $result->addValue( null, null, "a\xcc\x81" ); - $this->assertSame( [ - 'foo' => "foo\xef\xbf\xbdbar", - 'bar' => "\xc3\xa1", - 'baz' => 74, - 0 => "foo\xef\xbf\xbdbar", - 1 => "\xc3\xa1", - ApiResult::META_TYPE => 'assoc', - ], $result->getResultData() ); - - $result = new ApiResult( 8388608 ); - $obj = new stdClass; - $obj->{'1'} = 'one'; - $arr = []; - $result->addValue( $arr, 'foo', $obj ); - $this->assertSame( [ - 'foo' => [ - 1 => 'one', - ApiResult::META_TYPE => 'assoc', - ], - ApiResult::META_TYPE => 'assoc', - ], $result->getResultData() ); - } - - /** - * @covers ApiResult - */ - public function testMetadata() { - $arr = [ 'foo' => [ 'bar' => [] ] ]; - $result = new ApiResult( 8388608 ); - $result->addValue( null, 'foo', [ 'bar' => [] ] ); - - $expect = [ - 'foo' => [ - 'bar' => [ - ApiResult::META_INDEXED_TAG_NAME => 'ritn', - ApiResult::META_TYPE => 'default', - ], - ApiResult::META_INDEXED_TAG_NAME => 'ritn', - ApiResult::META_TYPE => 'default', - ], - ApiResult::META_SUBELEMENTS => [ 'foo', 'bar' ], - ApiResult::META_INDEXED_TAG_NAME => 'itn', - ApiResult::META_PRESERVE_KEYS => [ 'foo', 'bar' ], - ApiResult::META_TYPE => 'array', - ]; - - ApiResult::setSubelementsList( $arr, 'foo' ); - ApiResult::setSubelementsList( $arr, [ 'bar', 'baz' ] ); - ApiResult::unsetSubelementsList( $arr, 'baz' ); - ApiResult::setIndexedTagNameRecursive( $arr, 'ritn' ); - ApiResult::setIndexedTagName( $arr, 'itn' ); - ApiResult::setPreserveKeysList( $arr, 'foo' ); - ApiResult::setPreserveKeysList( $arr, [ 'bar', 'baz' ] ); - ApiResult::unsetPreserveKeysList( $arr, 'baz' ); - ApiResult::setArrayTypeRecursive( $arr, 'default' ); - ApiResult::setArrayType( $arr, 'array' ); - $this->assertSame( $expect, $arr ); - - $result->addSubelementsList( null, 'foo' ); - $result->addSubelementsList( null, [ 'bar', 'baz' ] ); - $result->removeSubelementsList( null, 'baz' ); - $result->addIndexedTagNameRecursive( null, 'ritn' ); - $result->addIndexedTagName( null, 'itn' ); - $result->addPreserveKeysList( null, 'foo' ); - $result->addPreserveKeysList( null, [ 'bar', 'baz' ] ); - $result->removePreserveKeysList( null, 'baz' ); - $result->addArrayTypeRecursive( null, 'default' ); - $result->addArrayType( null, 'array' ); - $this->assertEquals( $expect, $result->getResultData() ); - - $arr = [ 'foo' => [ 'bar' => [] ] ]; - $expect = [ - 'foo' => [ - 'bar' => [ - ApiResult::META_TYPE => 'kvp', - ApiResult::META_KVP_KEY_NAME => 'key', - ], - ApiResult::META_TYPE => 'kvp', - ApiResult::META_KVP_KEY_NAME => 'key', - ], - ApiResult::META_TYPE => 'BCkvp', - ApiResult::META_KVP_KEY_NAME => 'bc', - ]; - ApiResult::setArrayTypeRecursive( $arr, 'kvp', 'key' ); - ApiResult::setArrayType( $arr, 'BCkvp', 'bc' ); - $this->assertSame( $expect, $arr ); - } - - /** - * @covers ApiResult - */ - public function testUtilityFunctions() { - $arr = [ - 'foo' => [ - 'bar' => [ '_dummy' => 'foobaz' ], - 'bar2' => (object)[ '_dummy' => 'foobaz' ], - 'x' => 'ok', - '_dummy' => 'foobaz', - ], - 'foo2' => (object)[ - 'bar' => [ '_dummy' => 'foobaz' ], - 'bar2' => (object)[ '_dummy' => 'foobaz' ], - 'x' => 'ok', - '_dummy' => 'foobaz', - ], - ApiResult::META_SUBELEMENTS => [ 'foo', 'bar' ], - ApiResult::META_INDEXED_TAG_NAME => 'itn', - ApiResult::META_PRESERVE_KEYS => [ 'foo', 'bar', '_dummy2', 0 ], - ApiResult::META_TYPE => 'array', - '_dummy' => 'foobaz', - '_dummy2' => 'foobaz!', - ]; - $this->assertEquals( [ - 'foo' => [ - 'bar' => [], - 'bar2' => (object)[], - 'x' => 'ok', - ], - 'foo2' => (object)[ - 'bar' => [], - 'bar2' => (object)[], - 'x' => 'ok', - ], - '_dummy2' => 'foobaz!', - ], ApiResult::stripMetadata( $arr ), 'ApiResult::stripMetadata' ); - - $metadata = []; - $data = ApiResult::stripMetadataNonRecursive( $arr, $metadata ); - $this->assertEquals( [ - 'foo' => [ - 'bar' => [ '_dummy' => 'foobaz' ], - 'bar2' => (object)[ '_dummy' => 'foobaz' ], - 'x' => 'ok', - '_dummy' => 'foobaz', - ], - 'foo2' => (object)[ - 'bar' => [ '_dummy' => 'foobaz' ], - 'bar2' => (object)[ '_dummy' => 'foobaz' ], - 'x' => 'ok', - '_dummy' => 'foobaz', - ], - '_dummy2' => 'foobaz!', - ], $data, 'ApiResult::stripMetadataNonRecursive ($data)' ); - $this->assertEquals( [ - ApiResult::META_SUBELEMENTS => [ 'foo', 'bar' ], - ApiResult::META_INDEXED_TAG_NAME => 'itn', - ApiResult::META_PRESERVE_KEYS => [ 'foo', 'bar', '_dummy2', 0 ], - ApiResult::META_TYPE => 'array', - '_dummy' => 'foobaz', - ], $metadata, 'ApiResult::stripMetadataNonRecursive ($metadata)' ); - - $metadata = null; - $data = ApiResult::stripMetadataNonRecursive( (object)$arr, $metadata ); - $this->assertEquals( (object)[ - 'foo' => [ - 'bar' => [ '_dummy' => 'foobaz' ], - 'bar2' => (object)[ '_dummy' => 'foobaz' ], - 'x' => 'ok', - '_dummy' => 'foobaz', - ], - 'foo2' => (object)[ - 'bar' => [ '_dummy' => 'foobaz' ], - 'bar2' => (object)[ '_dummy' => 'foobaz' ], - 'x' => 'ok', - '_dummy' => 'foobaz', - ], - '_dummy2' => 'foobaz!', - ], $data, 'ApiResult::stripMetadataNonRecursive on object ($data)' ); - $this->assertEquals( [ - ApiResult::META_SUBELEMENTS => [ 'foo', 'bar' ], - ApiResult::META_INDEXED_TAG_NAME => 'itn', - ApiResult::META_PRESERVE_KEYS => [ 'foo', 'bar', '_dummy2', 0 ], - ApiResult::META_TYPE => 'array', - '_dummy' => 'foobaz', - ], $metadata, 'ApiResult::stripMetadataNonRecursive on object ($metadata)' ); - } - - /** - * @covers ApiResult - * @dataProvider provideTransformations - * @param string $label - * @param array $input - * @param array $transforms - * @param array|Exception $expect - */ - public function testTransformations( $label, $input, $transforms, $expect ) { - $result = new ApiResult( false ); - $result->addValue( null, 'test', $input ); - - if ( $expect instanceof Exception ) { - try { - $output = $result->getResultData( 'test', $transforms ); - $this->fail( 'Expected exception not thrown', $label ); - } catch ( Exception $ex ) { - $this->assertEquals( $ex, $expect, $label ); - } - } else { - $output = $result->getResultData( 'test', $transforms ); - $this->assertEquals( $expect, $output, $label ); - } - } - - public function provideTransformations() { - $kvp = function ( $keyKey, $key, $valKey, $value ) { - return [ - $keyKey => $key, - $valKey => $value, - ApiResult::META_PRESERVE_KEYS => [ $keyKey ], - ApiResult::META_CONTENT => $valKey, - ApiResult::META_TYPE => 'assoc', - ]; - }; - $typeArr = [ - 'defaultArray' => [ 2 => 'a', 0 => 'b', 1 => 'c' ], - 'defaultAssoc' => [ 'x' => 'a', 1 => 'b', 0 => 'c' ], - 'defaultAssoc2' => [ 2 => 'a', 3 => 'b', 0 => 'c' ], - 'array' => [ 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'array' ], - 'BCarray' => [ 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'BCarray' ], - 'BCassoc' => [ 'a', 'b', 'c', ApiResult::META_TYPE => 'BCassoc' ], - 'assoc' => [ 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ], - 'kvp' => [ 'x' => 'a', 'y' => 'b', 'z' => [ 'c' ], ApiResult::META_TYPE => 'kvp' ], - 'BCkvp' => [ 'x' => 'a', 'y' => 'b', - ApiResult::META_TYPE => 'BCkvp', - ApiResult::META_KVP_KEY_NAME => 'key', - ], - 'kvpmerge' => [ 'x' => 'a', 'y' => [ 'b' ], 'z' => [ 'c' => 'd' ], - ApiResult::META_TYPE => 'kvp', - ApiResult::META_KVP_MERGE => true, - ], - 'emptyDefault' => [ '_dummy' => 1 ], - 'emptyAssoc' => [ '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ], - '_dummy' => 1, - ApiResult::META_PRESERVE_KEYS => [ '_dummy' ], - ]; - $stripArr = [ - 'foo' => [ - 'bar' => [ '_dummy' => 'foobaz' ], - 'baz' => [ - ApiResult::META_SUBELEMENTS => [ 'foo', 'bar' ], - ApiResult::META_INDEXED_TAG_NAME => 'itn', - ApiResult::META_PRESERVE_KEYS => [ 'foo', 'bar', '_dummy2', 0 ], - ApiResult::META_TYPE => 'array', - ], - 'x' => 'ok', - '_dummy' => 'foobaz', - ], - ApiResult::META_SUBELEMENTS => [ 'foo', 'bar' ], - ApiResult::META_INDEXED_TAG_NAME => 'itn', - ApiResult::META_PRESERVE_KEYS => [ 'foo', 'bar', '_dummy2', 0 ], - ApiResult::META_TYPE => 'array', - '_dummy' => 'foobaz', - '_dummy2' => 'foobaz!', - ]; - - return [ - [ - 'BC: META_BC_BOOLS', - [ - 'BCtrue' => true, - 'BCfalse' => false, - 'true' => true, - 'false' => false, - ApiResult::META_BC_BOOLS => [ 0, 'true', 'false' ], - ], - [ 'BC' => [] ], - [ - 'BCtrue' => '', - 'true' => true, - 'false' => false, - ApiResult::META_BC_BOOLS => [ 0, 'true', 'false' ], - ] - ], - [ - 'BC: META_BC_SUBELEMENTS', - [ - 'bc' => 'foo', - 'nobc' => 'bar', - ApiResult::META_BC_SUBELEMENTS => [ 'bc' ], - ], - [ 'BC' => [] ], - [ - 'bc' => [ - '*' => 'foo', - ApiResult::META_CONTENT => '*', - ApiResult::META_TYPE => 'assoc', - ], - 'nobc' => 'bar', - ApiResult::META_BC_SUBELEMENTS => [ 'bc' ], - ], - ], - [ - 'BC: META_CONTENT', - [ - 'content' => '!!!', - ApiResult::META_CONTENT => 'content', - ], - [ 'BC' => [] ], - [ - '*' => '!!!', - ApiResult::META_CONTENT => '*', - ], - ], - [ - 'BC: BCkvp type', - [ - 'foo' => 'foo value', - 'bar' => 'bar value', - '_baz' => 'baz value', - ApiResult::META_TYPE => 'BCkvp', - ApiResult::META_KVP_KEY_NAME => 'key', - ApiResult::META_PRESERVE_KEYS => [ '_baz' ], - ], - [ 'BC' => [] ], - [ - $kvp( 'key', 'foo', '*', 'foo value' ), - $kvp( 'key', 'bar', '*', 'bar value' ), - $kvp( 'key', '_baz', '*', 'baz value' ), - ApiResult::META_TYPE => 'array', - ApiResult::META_KVP_KEY_NAME => 'key', - ApiResult::META_PRESERVE_KEYS => [ '_baz' ], - ], - ], - [ - 'BC: BCarray type', - [ - ApiResult::META_TYPE => 'BCarray', - ], - [ 'BC' => [] ], - [ - ApiResult::META_TYPE => 'default', - ], - ], - [ - 'BC: BCassoc type', - [ - ApiResult::META_TYPE => 'BCassoc', - ], - [ 'BC' => [] ], - [ - ApiResult::META_TYPE => 'default', - ], - ], - [ - 'BC: BCkvp exception', - [ - ApiResult::META_TYPE => 'BCkvp', - ], - [ 'BC' => [] ], - new UnexpectedValueException( - 'Type "BCkvp" used without setting ApiResult::META_KVP_KEY_NAME metadata item' - ), - ], - [ - 'BC: nobool, no*, nosub', - [ - 'true' => true, - 'false' => false, - 'content' => 'content', - ApiResult::META_CONTENT => 'content', - 'bc' => 'foo', - ApiResult::META_BC_SUBELEMENTS => [ 'bc' ], - 'BCarray' => [ ApiResult::META_TYPE => 'BCarray' ], - 'BCassoc' => [ ApiResult::META_TYPE => 'BCassoc' ], - 'BCkvp' => [ - 'foo' => 'foo value', - 'bar' => 'bar value', - '_baz' => 'baz value', - ApiResult::META_TYPE => 'BCkvp', - ApiResult::META_KVP_KEY_NAME => 'key', - ApiResult::META_PRESERVE_KEYS => [ '_baz' ], - ], - ], - [ 'BC' => [ 'nobool', 'no*', 'nosub' ] ], - [ - 'true' => true, - 'false' => false, - 'content' => 'content', - 'bc' => 'foo', - 'BCarray' => [ ApiResult::META_TYPE => 'default' ], - 'BCassoc' => [ ApiResult::META_TYPE => 'default' ], - 'BCkvp' => [ - $kvp( 'key', 'foo', '*', 'foo value' ), - $kvp( 'key', 'bar', '*', 'bar value' ), - $kvp( 'key', '_baz', '*', 'baz value' ), - ApiResult::META_TYPE => 'array', - ApiResult::META_KVP_KEY_NAME => 'key', - ApiResult::META_PRESERVE_KEYS => [ '_baz' ], - ], - ApiResult::META_CONTENT => 'content', - ApiResult::META_BC_SUBELEMENTS => [ 'bc' ], - ], - ], - - [ - 'Types: Normal transform', - $typeArr, - [ 'Types' => [] ], - [ - 'defaultArray' => [ 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ], - 'defaultAssoc' => [ 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ], - 'defaultAssoc2' => [ 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ], - 'array' => [ 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ], - 'BCarray' => [ 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ], - 'BCassoc' => [ 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ], - 'assoc' => [ 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ], - 'kvp' => [ 'x' => 'a', 'y' => 'b', - 'z' => [ 'c', ApiResult::META_TYPE => 'array' ], - ApiResult::META_TYPE => 'assoc' - ], - 'BCkvp' => [ 'x' => 'a', 'y' => 'b', - ApiResult::META_TYPE => 'assoc', - ApiResult::META_KVP_KEY_NAME => 'key', - ], - 'kvpmerge' => [ - 'x' => 'a', - 'y' => [ 'b', ApiResult::META_TYPE => 'array' ], - 'z' => [ 'c' => 'd', ApiResult::META_TYPE => 'assoc' ], - ApiResult::META_TYPE => 'assoc', - ApiResult::META_KVP_MERGE => true, - ], - 'emptyDefault' => [ '_dummy' => 1, ApiResult::META_TYPE => 'array' ], - 'emptyAssoc' => [ '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ], - '_dummy' => 1, - ApiResult::META_PRESERVE_KEYS => [ '_dummy' ], - ApiResult::META_TYPE => 'assoc', - ], - ], - [ - 'Types: AssocAsObject', - $typeArr, - [ 'Types' => [ 'AssocAsObject' => true ] ], - (object)[ - 'defaultArray' => [ 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ], - 'defaultAssoc' => (object)[ 'x' => 'a', - 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' - ], - 'defaultAssoc2' => (object)[ 2 => 'a', 3 => 'b', - 0 => 'c', ApiResult::META_TYPE => 'assoc' - ], - 'array' => [ 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ], - 'BCarray' => [ 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ], - 'BCassoc' => (object)[ 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ], - 'assoc' => (object)[ 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ], - 'kvp' => (object)[ 'x' => 'a', 'y' => 'b', - 'z' => [ 'c', ApiResult::META_TYPE => 'array' ], - ApiResult::META_TYPE => 'assoc' - ], - 'BCkvp' => (object)[ 'x' => 'a', 'y' => 'b', - ApiResult::META_TYPE => 'assoc', - ApiResult::META_KVP_KEY_NAME => 'key', - ], - 'kvpmerge' => (object)[ - 'x' => 'a', - 'y' => [ 'b', ApiResult::META_TYPE => 'array' ], - 'z' => (object)[ 'c' => 'd', ApiResult::META_TYPE => 'assoc' ], - ApiResult::META_TYPE => 'assoc', - ApiResult::META_KVP_MERGE => true, - ], - 'emptyDefault' => [ '_dummy' => 1, ApiResult::META_TYPE => 'array' ], - 'emptyAssoc' => (object)[ '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ], - '_dummy' => 1, - ApiResult::META_PRESERVE_KEYS => [ '_dummy' ], - ApiResult::META_TYPE => 'assoc', - ], - ], - [ - 'Types: ArmorKVP', - $typeArr, - [ 'Types' => [ 'ArmorKVP' => 'name' ] ], - [ - 'defaultArray' => [ 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ], - 'defaultAssoc' => [ 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ], - 'defaultAssoc2' => [ 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ], - 'array' => [ 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ], - 'BCarray' => [ 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ], - 'BCassoc' => [ 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ], - 'assoc' => [ 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ], - 'kvp' => [ - $kvp( 'name', 'x', 'value', 'a' ), - $kvp( 'name', 'y', 'value', 'b' ), - $kvp( 'name', 'z', 'value', [ 'c', ApiResult::META_TYPE => 'array' ] ), - ApiResult::META_TYPE => 'array' - ], - 'BCkvp' => [ - $kvp( 'key', 'x', 'value', 'a' ), - $kvp( 'key', 'y', 'value', 'b' ), - ApiResult::META_TYPE => 'array', - ApiResult::META_KVP_KEY_NAME => 'key', - ], - 'kvpmerge' => [ - $kvp( 'name', 'x', 'value', 'a' ), - $kvp( 'name', 'y', 'value', [ 'b', ApiResult::META_TYPE => 'array' ] ), - [ - 'name' => 'z', - 'c' => 'd', - ApiResult::META_TYPE => 'assoc', - ApiResult::META_PRESERVE_KEYS => [ 'name' ] - ], - ApiResult::META_TYPE => 'array', - ApiResult::META_KVP_MERGE => true, - ], - 'emptyDefault' => [ '_dummy' => 1, ApiResult::META_TYPE => 'array' ], - 'emptyAssoc' => [ '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ], - '_dummy' => 1, - ApiResult::META_PRESERVE_KEYS => [ '_dummy' ], - ApiResult::META_TYPE => 'assoc', - ], - ], - [ - 'Types: ArmorKVP + BC', - $typeArr, - [ 'BC' => [], 'Types' => [ 'ArmorKVP' => 'name' ] ], - [ - 'defaultArray' => [ 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ], - 'defaultAssoc' => [ 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ], - 'defaultAssoc2' => [ 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ], - 'array' => [ 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ], - 'BCarray' => [ 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ], - 'BCassoc' => [ 'a', 'b', 'c', ApiResult::META_TYPE => 'array' ], - 'assoc' => [ 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ], - 'kvp' => [ - $kvp( 'name', 'x', '*', 'a' ), - $kvp( 'name', 'y', '*', 'b' ), - $kvp( 'name', 'z', '*', [ 'c', ApiResult::META_TYPE => 'array' ] ), - ApiResult::META_TYPE => 'array' - ], - 'BCkvp' => [ - $kvp( 'key', 'x', '*', 'a' ), - $kvp( 'key', 'y', '*', 'b' ), - ApiResult::META_TYPE => 'array', - ApiResult::META_KVP_KEY_NAME => 'key', - ], - 'kvpmerge' => [ - $kvp( 'name', 'x', '*', 'a' ), - $kvp( 'name', 'y', '*', [ 'b', ApiResult::META_TYPE => 'array' ] ), - [ - 'name' => 'z', - 'c' => 'd', - ApiResult::META_TYPE => 'assoc', - ApiResult::META_PRESERVE_KEYS => [ 'name' ] ], - ApiResult::META_TYPE => 'array', - ApiResult::META_KVP_MERGE => true, - ], - 'emptyDefault' => [ '_dummy' => 1, ApiResult::META_TYPE => 'array' ], - 'emptyAssoc' => [ '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ], - '_dummy' => 1, - ApiResult::META_PRESERVE_KEYS => [ '_dummy' ], - ApiResult::META_TYPE => 'assoc', - ], - ], - [ - 'Types: ArmorKVP + AssocAsObject', - $typeArr, - [ 'Types' => [ 'ArmorKVP' => 'name', 'AssocAsObject' => true ] ], - (object)[ - 'defaultArray' => [ 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ], - 'defaultAssoc' => (object)[ 'x' => 'a', 1 => 'b', - 0 => 'c', ApiResult::META_TYPE => 'assoc' - ], - 'defaultAssoc2' => (object)[ 2 => 'a', 3 => 'b', - 0 => 'c', ApiResult::META_TYPE => 'assoc' - ], - 'array' => [ 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ], - 'BCarray' => [ 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ], - 'BCassoc' => (object)[ 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ], - 'assoc' => (object)[ 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ], - 'kvp' => [ - (object)$kvp( 'name', 'x', 'value', 'a' ), - (object)$kvp( 'name', 'y', 'value', 'b' ), - (object)$kvp( 'name', 'z', 'value', [ 'c', ApiResult::META_TYPE => 'array' ] ), - ApiResult::META_TYPE => 'array' - ], - 'BCkvp' => [ - (object)$kvp( 'key', 'x', 'value', 'a' ), - (object)$kvp( 'key', 'y', 'value', 'b' ), - ApiResult::META_TYPE => 'array', - ApiResult::META_KVP_KEY_NAME => 'key', - ], - 'kvpmerge' => [ - (object)$kvp( 'name', 'x', 'value', 'a' ), - (object)$kvp( 'name', 'y', 'value', [ 'b', ApiResult::META_TYPE => 'array' ] ), - (object)[ - 'name' => 'z', - 'c' => 'd', - ApiResult::META_TYPE => 'assoc', - ApiResult::META_PRESERVE_KEYS => [ 'name' ] - ], - ApiResult::META_TYPE => 'array', - ApiResult::META_KVP_MERGE => true, - ], - 'emptyDefault' => [ '_dummy' => 1, ApiResult::META_TYPE => 'array' ], - 'emptyAssoc' => (object)[ '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ], - '_dummy' => 1, - ApiResult::META_PRESERVE_KEYS => [ '_dummy' ], - ApiResult::META_TYPE => 'assoc', - ], - ], - [ - 'Types: BCkvp exception', - [ - ApiResult::META_TYPE => 'BCkvp', - ], - [ 'Types' => [] ], - new UnexpectedValueException( - 'Type "BCkvp" used without setting ApiResult::META_KVP_KEY_NAME metadata item' - ), - ], - - [ - 'Strip: With ArmorKVP + AssocAsObject transforms', - $typeArr, - [ 'Types' => [ 'ArmorKVP' => 'name', 'AssocAsObject' => true ], 'Strip' => 'all' ], - (object)[ - 'defaultArray' => [ 'b', 'c', 'a' ], - 'defaultAssoc' => (object)[ 'x' => 'a', 1 => 'b', 0 => 'c' ], - 'defaultAssoc2' => (object)[ 2 => 'a', 3 => 'b', 0 => 'c' ], - 'array' => [ 'a', 'c', 'b' ], - 'BCarray' => [ 'a', 'c', 'b' ], - 'BCassoc' => (object)[ 'a', 'b', 'c' ], - 'assoc' => (object)[ 2 => 'a', 0 => 'b', 1 => 'c' ], - 'kvp' => [ - (object)[ 'name' => 'x', 'value' => 'a' ], - (object)[ 'name' => 'y', 'value' => 'b' ], - (object)[ 'name' => 'z', 'value' => [ 'c' ] ], - ], - 'BCkvp' => [ - (object)[ 'key' => 'x', 'value' => 'a' ], - (object)[ 'key' => 'y', 'value' => 'b' ], - ], - 'kvpmerge' => [ - (object)[ 'name' => 'x', 'value' => 'a' ], - (object)[ 'name' => 'y', 'value' => [ 'b' ] ], - (object)[ 'name' => 'z', 'c' => 'd' ], - ], - 'emptyDefault' => [], - 'emptyAssoc' => (object)[], - '_dummy' => 1, - ], - ], - - [ - 'Strip: all', - $stripArr, - [ 'Strip' => 'all' ], - [ - 'foo' => [ - 'bar' => [], - 'baz' => [], - 'x' => 'ok', - ], - '_dummy2' => 'foobaz!', - ], - ], - [ - 'Strip: base', - $stripArr, - [ 'Strip' => 'base' ], - [ - 'foo' => [ - 'bar' => [ '_dummy' => 'foobaz' ], - 'baz' => [ - ApiResult::META_SUBELEMENTS => [ 'foo', 'bar' ], - ApiResult::META_INDEXED_TAG_NAME => 'itn', - ApiResult::META_PRESERVE_KEYS => [ 'foo', 'bar', '_dummy2', 0 ], - ApiResult::META_TYPE => 'array', - ], - 'x' => 'ok', - '_dummy' => 'foobaz', - ], - '_dummy2' => 'foobaz!', - ], - ], - [ - 'Strip: bc', - $stripArr, - [ 'Strip' => 'bc' ], - [ - 'foo' => [ - 'bar' => [], - 'baz' => [ - ApiResult::META_SUBELEMENTS => [ 'foo', 'bar' ], - ApiResult::META_INDEXED_TAG_NAME => 'itn', - ], - 'x' => 'ok', - ], - '_dummy2' => 'foobaz!', - ApiResult::META_SUBELEMENTS => [ 'foo', 'bar' ], - ApiResult::META_INDEXED_TAG_NAME => 'itn', - ], - ], - - [ - 'Custom transform', - [ - 'foo' => '?', - 'bar' => '?', - '_dummy' => '?', - '_dummy2' => '?', - '_dummy3' => '?', - ApiResult::META_CONTENT => 'foo', - ApiResult::META_PRESERVE_KEYS => [ '_dummy2', '_dummy3' ], - ], - [ - 'Custom' => [ $this, 'customTransform' ], - 'BC' => [], - 'Types' => [], - 'Strip' => 'all' - ], - [ - '*' => 'FOO', - 'bar' => 'BAR', - 'baz' => [ 'a', 'b' ], - '_dummy2' => '_DUMMY2', - '_dummy3' => '_DUMMY3', - ApiResult::META_CONTENT => 'bar', - ], - ], - ]; - } - - /** - * Custom transformer for testTransformations - * @param array &$data - * @param array &$metadata - */ - public function customTransform( &$data, &$metadata ) { - // Prevent recursion - if ( isset( $metadata['_added'] ) ) { - $metadata[ApiResult::META_TYPE] = 'array'; - return; - } - - foreach ( $data as $k => $v ) { - $data[$k] = strtoupper( $k ); - } - $data['baz'] = [ '_added' => 1, 'z' => 'b', 'y' => 'a' ]; - $metadata[ApiResult::META_PRESERVE_KEYS][0] = '_dummy'; - $data[ApiResult::META_CONTENT] = 'bar'; - } - - /** - * @covers ApiResult - */ - public function testAddMetadataToResultVars() { - $arr = [ - 'a' => "foo", - 'b' => false, - 'c' => 10, - 'sequential_numeric_keys' => [ 'a', 'b', 'c' ], - 'non_sequential_numeric_keys' => [ 'a', 'b', 4 => 'c' ], - 'string_keys' => [ - 'one' => 1, - 'two' => 2 - ], - 'object_sequential_keys' => (object)[ 'a', 'b', 'c' ], - '_type' => "should be overwritten in result", - ]; - $this->assertSame( [ - ApiResult::META_TYPE => 'kvp', - ApiResult::META_KVP_KEY_NAME => 'key', - ApiResult::META_PRESERVE_KEYS => [ - 'a', 'b', 'c', - 'sequential_numeric_keys', 'non_sequential_numeric_keys', - 'string_keys', 'object_sequential_keys' - ], - ApiResult::META_BC_BOOLS => [ 'b' ], - ApiResult::META_INDEXED_TAG_NAME => 'var', - 'a' => "foo", - 'b' => false, - 'c' => 10, - 'sequential_numeric_keys' => [ - ApiResult::META_TYPE => 'array', - ApiResult::META_BC_BOOLS => [], - ApiResult::META_INDEXED_TAG_NAME => 'value', - 0 => 'a', - 1 => 'b', - 2 => 'c', - ], - 'non_sequential_numeric_keys' => [ - ApiResult::META_TYPE => 'kvp', - ApiResult::META_KVP_KEY_NAME => 'key', - ApiResult::META_PRESERVE_KEYS => [ 0, 1, 4 ], - ApiResult::META_BC_BOOLS => [], - ApiResult::META_INDEXED_TAG_NAME => 'var', - 0 => 'a', - 1 => 'b', - 4 => 'c', - ], - 'string_keys' => [ - ApiResult::META_TYPE => 'kvp', - ApiResult::META_KVP_KEY_NAME => 'key', - ApiResult::META_PRESERVE_KEYS => [ 'one', 'two' ], - ApiResult::META_BC_BOOLS => [], - ApiResult::META_INDEXED_TAG_NAME => 'var', - 'one' => 1, - 'two' => 2, - ], - 'object_sequential_keys' => [ - ApiResult::META_TYPE => 'kvp', - ApiResult::META_KVP_KEY_NAME => 'key', - ApiResult::META_PRESERVE_KEYS => [ 0, 1, 2 ], - ApiResult::META_BC_BOOLS => [], - ApiResult::META_INDEXED_TAG_NAME => 'var', - 0 => 'a', - 1 => 'b', - 2 => 'c', - ], - ], ApiResult::addMetadataToResultVars( $arr ) ); - } - - public function testObjectSerialization() { - $arr = []; - ApiResult::setValue( $arr, 'foo', (object)[ 'a' => 1, 'b' => 2 ] ); - $this->assertSame( [ - 'a' => 1, - 'b' => 2, - ApiResult::META_TYPE => 'assoc', - ], $arr['foo'] ); - - $arr = []; - ApiResult::setValue( $arr, 'foo', new ApiResultTestStringifiableObject() ); - $this->assertSame( 'Ok', $arr['foo'] ); - - $arr = []; - ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( 'Ok' ) ); - $this->assertSame( 'Ok', $arr['foo'] ); - - try { - $arr = []; - ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( - new ApiResultTestStringifiableObject() - ) ); - $this->fail( 'Expected exception not thrown' ); - } catch ( UnexpectedValueException $ex ) { - $this->assertSame( - 'ApiResultTestSerializableObject::serializeForApiResult() ' . - 'returned an object of class ApiResultTestStringifiableObject', - $ex->getMessage(), - 'Expected exception' - ); - } - - try { - $arr = []; - ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( NAN ) ); - $this->fail( 'Expected exception not thrown' ); - } catch ( UnexpectedValueException $ex ) { - $this->assertSame( - 'ApiResultTestSerializableObject::serializeForApiResult() ' . - 'returned an invalid value: Cannot add non-finite floats to ApiResult', - $ex->getMessage(), - 'Expected exception' - ); - } - - $arr = []; - ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( - [ - 'one' => new ApiResultTestStringifiableObject( '1' ), - 'two' => new ApiResultTestSerializableObject( 2 ), - ] - ) ); - $this->assertSame( [ - 'one' => '1', - 'two' => 2, - ], $arr['foo'] ); - } -} - -class ApiResultTestStringifiableObject { - private $ret; - - public function __construct( $ret = 'Ok' ) { - $this->ret = $ret; - } - - public function __toString() { - return $this->ret; - } -} - -class ApiResultTestSerializableObject { - private $ret; - - public function __construct( $ret ) { - $this->ret = $ret; - } - - public function __toString() { - return "Fail"; - } - - public function serializeForApiResult() { - return $this->ret; - } -} diff --git a/tests/phpunit/includes/api/ApiUsageExceptionTest.php b/tests/phpunit/includes/api/ApiUsageExceptionTest.php deleted file mode 100644 index bb72021121..0000000000 --- a/tests/phpunit/includes/api/ApiUsageExceptionTest.php +++ /dev/null @@ -1,44 +0,0 @@ -fatal( $messageKey, $messageParameter ); - - $apiUsageException = new ApiUsageException( null, $statusValue ); - /** @var \Message $gotMessage */ - $gotMessage = $apiUsageException->getMessageObject(); - - $this->assertInstanceOf( \Message::class, $gotMessage ); - $this->assertEquals( $messageKey, $gotMessage->getKey() ); - $this->assertEquals( [ $messageParameter ], $gotMessage->getParams() ); - } - - public function testNewWithMessage_ThenGetMessageObject_ReturnsApiMessageWithProvidedData() { - $expectedMessage = new Message( 'some-message-key', [ 'some message parameter' ] ); - $expectedCode = 'some-error-code'; - $expectedData = [ 'some-error-data' ]; - - $apiUsageException = ApiUsageException::newWithMessage( - null, - $expectedMessage, - $expectedCode, - $expectedData - ); - /** @var \ApiMessage $gotMessage */ - $gotMessage = $apiUsageException->getMessageObject(); - - $this->assertInstanceOf( \ApiMessage::class, $gotMessage ); - $this->assertEquals( $expectedMessage->getKey(), $gotMessage->getKey() ); - $this->assertEquals( $expectedMessage->getParams(), $gotMessage->getParams() ); - $this->assertEquals( $expectedCode, $gotMessage->getApiCode() ); - $this->assertEquals( $expectedData, $gotMessage->getApiData() ); - } - -} diff --git a/tests/phpunit/includes/auth/AbstractPreAuthenticationProviderTest.php b/tests/phpunit/includes/auth/AbstractPreAuthenticationProviderTest.php deleted file mode 100644 index 2970a2807c..0000000000 --- a/tests/phpunit/includes/auth/AbstractPreAuthenticationProviderTest.php +++ /dev/null @@ -1,45 +0,0 @@ -getMockForAbstractClass( AbstractPreAuthenticationProvider::class ); - - $this->assertEquals( - [], - $provider->getAuthenticationRequests( AuthManager::ACTION_LOGIN, [] ) - ); - $this->assertEquals( - \StatusValue::newGood(), - $provider->testForAuthentication( [] ) - ); - $this->assertEquals( - \StatusValue::newGood(), - $provider->testForAccountCreation( $user, $user, [] ) - ); - $this->assertEquals( - \StatusValue::newGood(), - $provider->testUserForCreation( $user, AuthManager::AUTOCREATE_SOURCE_SESSION ) - ); - $this->assertEquals( - \StatusValue::newGood(), - $provider->testUserForCreation( $user, false ) - ); - $this->assertEquals( - \StatusValue::newGood(), - $provider->testForAccountLink( $user ) - ); - - $res = AuthenticationResponse::newPass(); - $provider->postAuthentication( $user, $res ); - $provider->postAccountCreation( $user, $user, $res ); - $provider->postAccountLink( $user, $res ); - } -} diff --git a/tests/phpunit/includes/auth/AbstractSecondaryAuthenticationProviderTest.php b/tests/phpunit/includes/auth/AbstractSecondaryAuthenticationProviderTest.php deleted file mode 100644 index cd17862838..0000000000 --- a/tests/phpunit/includes/auth/AbstractSecondaryAuthenticationProviderTest.php +++ /dev/null @@ -1,84 +0,0 @@ -getMockForAbstractClass( AbstractSecondaryAuthenticationProvider::class ); - - try { - $provider->continueSecondaryAuthentication( $user, [] ); - $this->fail( 'Expected exception not thrown' ); - } catch ( \BadMethodCallException $ex ) { - } - - try { - $provider->continueSecondaryAccountCreation( $user, $user, [] ); - $this->fail( 'Expected exception not thrown' ); - } catch ( \BadMethodCallException $ex ) { - } - - $req = $this->getMockForAbstractClass( AuthenticationRequest::class ); - - $this->assertTrue( $provider->providerAllowsPropertyChange( 'foo' ) ); - $this->assertEquals( - \StatusValue::newGood( 'ignored' ), - $provider->providerAllowsAuthenticationDataChange( $req ) - ); - $this->assertEquals( - \StatusValue::newGood(), - $provider->testForAccountCreation( $user, $user, [] ) - ); - $this->assertEquals( - \StatusValue::newGood(), - $provider->testUserForCreation( $user, AuthManager::AUTOCREATE_SOURCE_SESSION ) - ); - $this->assertEquals( - \StatusValue::newGood(), - $provider->testUserForCreation( $user, false ) - ); - - $provider->providerChangeAuthenticationData( $req ); - $provider->autoCreatedAccount( $user, AuthManager::AUTOCREATE_SOURCE_SESSION ); - - $res = AuthenticationResponse::newPass(); - $provider->postAuthentication( $user, $res ); - $provider->postAccountCreation( $user, $user, $res ); - } - - public function testProviderRevokeAccessForUser() { - $reqs = []; - for ( $i = 0; $i < 3; $i++ ) { - $reqs[$i] = $this->createMock( AuthenticationRequest::class ); - $reqs[$i]->done = false; - } - - $provider = $this->getMockBuilder( AbstractSecondaryAuthenticationProvider::class ) - ->setMethods( [ 'providerChangeAuthenticationData' ] ) - ->getMockForAbstractClass(); - $provider->expects( $this->once() )->method( 'getAuthenticationRequests' ) - ->with( - $this->identicalTo( AuthManager::ACTION_REMOVE ), - $this->identicalTo( [ 'username' => 'UTSysop' ] ) - ) - ->will( $this->returnValue( $reqs ) ); - $provider->expects( $this->exactly( 3 ) )->method( 'providerChangeAuthenticationData' ) - ->will( $this->returnCallback( function ( $req ) { - $this->assertSame( 'UTSysop', $req->username ); - $this->assertFalse( $req->done ); - $req->done = true; - } ) ); - - $provider->providerRevokeAccessForUser( 'UTSysop' ); - - foreach ( $reqs as $i => $req ) { - $this->assertTrue( $req->done, "#$i" ); - } - } -} diff --git a/tests/phpunit/includes/auth/AuthenticationResponseTest.php b/tests/phpunit/includes/auth/AuthenticationResponseTest.php deleted file mode 100644 index c79682275f..0000000000 --- a/tests/phpunit/includes/auth/AuthenticationResponseTest.php +++ /dev/null @@ -1,112 +0,0 @@ -messageType = 'warning'; - foreach ( $expect as $field => $value ) { - $res->$field = $value; - } - $ret = call_user_func_array( "MediaWiki\\Auth\\AuthenticationResponse::$constructor", $args ); - $this->assertEquals( $res, $ret ); - } else { - try { - call_user_func_array( "MediaWiki\\Auth\\AuthenticationResponse::$constructor", $args ); - $this->fail( 'Expected exception not thrown' ); - } catch ( \Exception $ex ) { - $this->assertEquals( $expect, $ex ); - } - } - } - - public function provideConstructors() { - $req = $this->getMockForAbstractClass( AuthenticationRequest::class ); - $msg = new \Message( 'mainpage' ); - - return [ - [ 'newPass', [], [ - 'status' => AuthenticationResponse::PASS, - ] ], - [ 'newPass', [ 'name' ], [ - 'status' => AuthenticationResponse::PASS, - 'username' => 'name', - ] ], - [ 'newPass', [ 'name', null ], [ - 'status' => AuthenticationResponse::PASS, - 'username' => 'name', - ] ], - - [ 'newFail', [ $msg ], [ - 'status' => AuthenticationResponse::FAIL, - 'message' => $msg, - 'messageType' => 'error', - ] ], - - [ 'newRestart', [ $msg ], [ - 'status' => AuthenticationResponse::RESTART, - 'message' => $msg, - ] ], - - [ 'newAbstain', [], [ - 'status' => AuthenticationResponse::ABSTAIN, - ] ], - - [ 'newUI', [ [ $req ], $msg ], [ - 'status' => AuthenticationResponse::UI, - 'neededRequests' => [ $req ], - 'message' => $msg, - 'messageType' => 'warning', - ] ], - - [ 'newUI', [ [ $req ], $msg, 'warning' ], [ - 'status' => AuthenticationResponse::UI, - 'neededRequests' => [ $req ], - 'message' => $msg, - 'messageType' => 'warning', - ] ], - - [ 'newUI', [ [ $req ], $msg, 'error' ], [ - 'status' => AuthenticationResponse::UI, - 'neededRequests' => [ $req ], - 'message' => $msg, - 'messageType' => 'error', - ] ], - [ 'newUI', [ [], $msg ], - new \InvalidArgumentException( '$reqs may not be empty' ) - ], - - [ 'newRedirect', [ [ $req ], 'http://example.org/redir' ], [ - 'status' => AuthenticationResponse::REDIRECT, - 'neededRequests' => [ $req ], - 'redirectTarget' => 'http://example.org/redir', - ] ], - [ - 'newRedirect', - [ [ $req ], 'http://example.org/redir', [ 'foo' => 'bar' ] ], - [ - 'status' => AuthenticationResponse::REDIRECT, - 'neededRequests' => [ $req ], - 'redirectTarget' => 'http://example.org/redir', - 'redirectApiData' => [ 'foo' => 'bar' ], - ] - ], - [ 'newRedirect', [ [], 'http://example.org/redir' ], - new \InvalidArgumentException( '$reqs may not be empty' ) - ], - ]; - } - -} diff --git a/tests/phpunit/includes/auth/ConfirmLinkSecondaryAuthenticationProviderTest.php b/tests/phpunit/includes/auth/ConfirmLinkSecondaryAuthenticationProviderTest.php deleted file mode 100644 index b17da2e2cf..0000000000 --- a/tests/phpunit/includes/auth/ConfirmLinkSecondaryAuthenticationProviderTest.php +++ /dev/null @@ -1,289 +0,0 @@ -assertEquals( $response, $provider->getAuthenticationRequests( $action, [] ) ); - } - - public static function provideGetAuthenticationRequests() { - return [ - [ AuthManager::ACTION_LOGIN, [] ], - [ AuthManager::ACTION_CREATE, [] ], - [ AuthManager::ACTION_LINK, [] ], - [ AuthManager::ACTION_CHANGE, [] ], - [ AuthManager::ACTION_REMOVE, [] ], - ]; - } - - public function testBeginSecondaryAuthentication() { - $user = \User::newFromName( 'UTSysop' ); - $obj = new \stdClass; - - $mock = $this->getMockBuilder( ConfirmLinkSecondaryAuthenticationProvider::class ) - ->setMethods( [ 'beginLinkAttempt', 'continueLinkAttempt' ] ) - ->getMock(); - $mock->expects( $this->once() )->method( 'beginLinkAttempt' ) - ->with( $this->identicalTo( $user ), $this->identicalTo( 'AuthManager::authnState' ) ) - ->will( $this->returnValue( $obj ) ); - $mock->expects( $this->never() )->method( 'continueLinkAttempt' ); - - $this->assertSame( $obj, $mock->beginSecondaryAuthentication( $user, [] ) ); - } - - public function testContinueSecondaryAuthentication() { - $user = \User::newFromName( 'UTSysop' ); - $obj = new \stdClass; - $reqs = [ new \stdClass ]; - - $mock = $this->getMockBuilder( ConfirmLinkSecondaryAuthenticationProvider::class ) - ->setMethods( [ 'beginLinkAttempt', 'continueLinkAttempt' ] ) - ->getMock(); - $mock->expects( $this->never() )->method( 'beginLinkAttempt' ); - $mock->expects( $this->once() )->method( 'continueLinkAttempt' ) - ->with( - $this->identicalTo( $user ), - $this->identicalTo( 'AuthManager::authnState' ), - $this->identicalTo( $reqs ) - ) - ->will( $this->returnValue( $obj ) ); - - $this->assertSame( $obj, $mock->continueSecondaryAuthentication( $user, $reqs ) ); - } - - public function testBeginSecondaryAccountCreation() { - $user = \User::newFromName( 'UTSysop' ); - $obj = new \stdClass; - - $mock = $this->getMockBuilder( ConfirmLinkSecondaryAuthenticationProvider::class ) - ->setMethods( [ 'beginLinkAttempt', 'continueLinkAttempt' ] ) - ->getMock(); - $mock->expects( $this->once() )->method( 'beginLinkAttempt' ) - ->with( $this->identicalTo( $user ), $this->identicalTo( 'AuthManager::accountCreationState' ) ) - ->will( $this->returnValue( $obj ) ); - $mock->expects( $this->never() )->method( 'continueLinkAttempt' ); - - $this->assertSame( $obj, $mock->beginSecondaryAccountCreation( $user, $user, [] ) ); - } - - public function testContinueSecondaryAccountCreation() { - $user = \User::newFromName( 'UTSysop' ); - $obj = new \stdClass; - $reqs = [ new \stdClass ]; - - $mock = $this->getMockBuilder( ConfirmLinkSecondaryAuthenticationProvider::class ) - ->setMethods( [ 'beginLinkAttempt', 'continueLinkAttempt' ] ) - ->getMock(); - $mock->expects( $this->never() )->method( 'beginLinkAttempt' ); - $mock->expects( $this->once() )->method( 'continueLinkAttempt' ) - ->with( - $this->identicalTo( $user ), - $this->identicalTo( 'AuthManager::accountCreationState' ), - $this->identicalTo( $reqs ) - ) - ->will( $this->returnValue( $obj ) ); - - $this->assertSame( $obj, $mock->continueSecondaryAccountCreation( $user, $user, $reqs ) ); - } - - /** - * Get requests for testing - * @return AuthenticationRequest[] - */ - private function getLinkRequests() { - $reqs = []; - - $mb = $this->getMockBuilder( AuthenticationRequest::class ) - ->setMethods( [ 'getUniqueId' ] ); - for ( $i = 1; $i <= 3; $i++ ) { - $req = $mb->getMockForAbstractClass(); - $req->expects( $this->any() )->method( 'getUniqueId' ) - ->will( $this->returnValue( "Request$i" ) ); - $req->id = $i - 1; - $reqs[$req->getUniqueId()] = $req; - } - - return $reqs; - } - - public function testBeginLinkAttempt() { - $badReq = $this->getMockBuilder( AuthenticationRequest::class ) - ->setMethods( [ 'getUniqueId' ] ) - ->getMockForAbstractClass(); - $badReq->expects( $this->any() )->method( 'getUniqueId' ) - ->will( $this->returnValue( "BadReq" ) ); - - $user = \User::newFromName( 'UTSysop' ); - $provider = TestingAccessWrapper::newFromObject( - new ConfirmLinkSecondaryAuthenticationProvider - ); - $request = new \FauxRequest(); - $manager = $this->getMockBuilder( AuthManager::class ) - ->setMethods( [ 'allowsAuthenticationDataChange' ] ) - ->setConstructorArgs( [ $request, \RequestContext::getMain()->getConfig() ] ) - ->getMock(); - $manager->expects( $this->any() )->method( 'allowsAuthenticationDataChange' ) - ->will( $this->returnCallback( function ( $req ) { - return $req->getUniqueId() !== 'BadReq' - ? \StatusValue::newGood() - : \StatusValue::newFatal( 'no' ); - } ) ); - $provider->setManager( $manager ); - - $this->assertEquals( - AuthenticationResponse::newAbstain(), - $provider->beginLinkAttempt( $user, 'state' ) - ); - - $request->getSession()->setSecret( 'state', [ - 'maybeLink' => [], - ] ); - $this->assertEquals( - AuthenticationResponse::newAbstain(), - $provider->beginLinkAttempt( $user, 'state' ) - ); - - $reqs = $this->getLinkRequests(); - $request->getSession()->setSecret( 'state', [ - 'maybeLink' => $reqs + [ 'BadReq' => $badReq ] - ] ); - $res = $provider->beginLinkAttempt( $user, 'state' ); - $this->assertInstanceOf( AuthenticationResponse::class, $res ); - $this->assertSame( AuthenticationResponse::UI, $res->status ); - $this->assertSame( 'authprovider-confirmlink-message', $res->message->getKey() ); - $this->assertCount( 1, $res->neededRequests ); - $req = $res->neededRequests[0]; - $this->assertInstanceOf( ConfirmLinkAuthenticationRequest::class, $req ); - $expectReqs = $this->getLinkRequests(); - foreach ( $expectReqs as $r ) { - $r->action = AuthManager::ACTION_CHANGE; - $r->username = $user->getName(); - } - $this->assertEquals( $expectReqs, TestingAccessWrapper::newFromObject( $req )->linkRequests ); - } - - public function testContinueLinkAttempt() { - $user = \User::newFromName( 'UTSysop' ); - $obj = new \stdClass; - $reqs = $this->getLinkRequests(); - - $done = [ false, false, false ]; - - // First, test the pass-through for not containing the ConfirmLinkAuthenticationRequest - $mock = $this->getMockBuilder( ConfirmLinkSecondaryAuthenticationProvider::class ) - ->setMethods( [ 'beginLinkAttempt' ] ) - ->getMock(); - $mock->expects( $this->once() )->method( 'beginLinkAttempt' ) - ->with( $this->identicalTo( $user ), $this->identicalTo( 'state' ) ) - ->will( $this->returnValue( $obj ) ); - $this->assertSame( - $obj, - TestingAccessWrapper::newFromObject( $mock )->continueLinkAttempt( $user, 'state', $reqs ) - ); - - // Now test the actual functioning - $provider = $this->getMockBuilder( ConfirmLinkSecondaryAuthenticationProvider::class ) - ->setMethods( [ - 'beginLinkAttempt', 'providerAllowsAuthenticationDataChange', - 'providerChangeAuthenticationData' - ] ) - ->getMock(); - $provider->expects( $this->never() )->method( 'beginLinkAttempt' ); - $provider->expects( $this->any() )->method( 'providerAllowsAuthenticationDataChange' ) - ->will( $this->returnCallback( function ( $req ) use ( $reqs ) { - return $req->getUniqueId() === 'Request3' - ? \StatusValue::newFatal( 'foo' ) : \StatusValue::newGood(); - } ) ); - $provider->expects( $this->any() )->method( 'providerChangeAuthenticationData' ) - ->will( $this->returnCallback( function ( $req ) use ( &$done ) { - $done[$req->id] = true; - } ) ); - $config = new \HashConfig( [ - 'AuthManagerConfig' => [ - 'preauth' => [], - 'primaryauth' => [], - 'secondaryauth' => [ - [ 'factory' => function () use ( $provider ) { - return $provider; - } ], - ], - ], - ] ); - $request = new \FauxRequest(); - $manager = new AuthManager( $request, $config ); - $provider->setManager( $manager ); - $provider = TestingAccessWrapper::newFromObject( $provider ); - - $req = new ConfirmLinkAuthenticationRequest( $reqs ); - - $this->assertEquals( - AuthenticationResponse::newAbstain(), - $provider->continueLinkAttempt( $user, 'state', [ $req ] ) - ); - - $request->getSession()->setSecret( 'state', [ - 'maybeLink' => [], - ] ); - $this->assertEquals( - AuthenticationResponse::newAbstain(), - $provider->continueLinkAttempt( $user, 'state', [ $req ] ) - ); - - $request->getSession()->setSecret( 'state', [ - 'maybeLink' => $reqs - ] ); - $this->assertEquals( - AuthenticationResponse::newPass(), - $res = $provider->continueLinkAttempt( $user, 'state', [ $req ] ) - ); - $this->assertSame( [ false, false, false ], $done ); - - $request->getSession()->setSecret( 'state', [ - 'maybeLink' => [ $reqs['Request2'] ], - ] ); - $req->confirmedLinkIDs = [ 'Request1', 'Request2' ]; - $res = $provider->continueLinkAttempt( $user, 'state', [ $req ] ); - $this->assertEquals( AuthenticationResponse::newPass(), $res ); - $this->assertSame( [ false, true, false ], $done ); - $done = [ false, false, false ]; - - $request->getSession()->setSecret( 'state', [ - 'maybeLink' => $reqs, - ] ); - $req->confirmedLinkIDs = [ 'Request1', 'Request2' ]; - $res = $provider->continueLinkAttempt( $user, 'state', [ $req ] ); - $this->assertEquals( AuthenticationResponse::newPass(), $res ); - $this->assertSame( [ true, true, false ], $done ); - $done = [ false, false, false ]; - - $request->getSession()->setSecret( 'state', [ - 'maybeLink' => $reqs, - ] ); - $req->confirmedLinkIDs = [ 'Request1', 'Request3' ]; - $res = $provider->continueLinkAttempt( $user, 'state', [ $req ] ); - $this->assertEquals( AuthenticationResponse::UI, $res->status ); - $this->assertCount( 1, $res->neededRequests ); - $this->assertInstanceOf( ButtonAuthenticationRequest::class, $res->neededRequests[0] ); - $this->assertSame( [ true, false, false ], $done ); - $done = [ false, false, false ]; - - $res = $provider->continueLinkAttempt( $user, 'state', [ $res->neededRequests[0] ] ); - $this->assertEquals( AuthenticationResponse::newPass(), $res ); - $this->assertSame( [ false, false, false ], $done ); - } - -} diff --git a/tests/phpunit/includes/auth/EmailNotificationSecondaryAuthenticationProviderTest.php b/tests/phpunit/includes/auth/EmailNotificationSecondaryAuthenticationProviderTest.php deleted file mode 100644 index ff22def7f8..0000000000 --- a/tests/phpunit/includes/auth/EmailNotificationSecondaryAuthenticationProviderTest.php +++ /dev/null @@ -1,112 +0,0 @@ - true, - 'EmailAuthentication' => true, - ] ); - - $provider = new EmailNotificationSecondaryAuthenticationProvider(); - $provider->setConfig( $config ); - $providerPriv = TestingAccessWrapper::newFromObject( $provider ); - $this->assertTrue( $providerPriv->sendConfirmationEmail ); - - $provider = new EmailNotificationSecondaryAuthenticationProvider( [ - 'sendConfirmationEmail' => false, - ] ); - $provider->setConfig( $config ); - $providerPriv = TestingAccessWrapper::newFromObject( $provider ); - $this->assertFalse( $providerPriv->sendConfirmationEmail ); - } - - /** - * @dataProvider provideGetAuthenticationRequests - * @param string $action - * @param AuthenticationRequest[] $expected - */ - public function testGetAuthenticationRequests( $action, $expected ) { - $provider = new EmailNotificationSecondaryAuthenticationProvider( [ - 'sendConfirmationEmail' => true, - ] ); - $this->assertSame( $expected, $provider->getAuthenticationRequests( $action, [] ) ); - } - - public function provideGetAuthenticationRequests() { - return [ - [ AuthManager::ACTION_LOGIN, [] ], - [ AuthManager::ACTION_CREATE, [] ], - [ AuthManager::ACTION_LINK, [] ], - [ AuthManager::ACTION_CHANGE, [] ], - [ AuthManager::ACTION_REMOVE, [] ], - ]; - } - - public function testBeginSecondaryAuthentication() { - $provider = new EmailNotificationSecondaryAuthenticationProvider( [ - 'sendConfirmationEmail' => true, - ] ); - $this->assertEquals( AuthenticationResponse::newAbstain(), - $provider->beginSecondaryAuthentication( \User::newFromName( 'Foo' ), [] ) ); - } - - public function testBeginSecondaryAccountCreation() { - $authManager = new AuthManager( new \FauxRequest(), new \HashConfig() ); - - $creator = $this->getMockBuilder( \User::class )->getMock(); - $userWithoutEmail = $this->getMockBuilder( \User::class )->getMock(); - $userWithoutEmail->expects( $this->any() )->method( 'getEmail' )->willReturn( '' ); - $userWithoutEmail->expects( $this->any() )->method( 'getInstanceForUpdate' )->willReturnSelf(); - $userWithoutEmail->expects( $this->never() )->method( 'sendConfirmationMail' ); - $userWithEmailError = $this->getMockBuilder( \User::class )->getMock(); - $userWithEmailError->expects( $this->any() )->method( 'getEmail' )->willReturn( 'foo@bar.baz' ); - $userWithEmailError->expects( $this->any() )->method( 'getInstanceForUpdate' )->willReturnSelf(); - $userWithEmailError->expects( $this->any() )->method( 'sendConfirmationMail' ) - ->willReturn( \Status::newFatal( 'fail' ) ); - $userExpectsConfirmation = $this->getMockBuilder( \User::class )->getMock(); - $userExpectsConfirmation->expects( $this->any() )->method( 'getEmail' ) - ->willReturn( 'foo@bar.baz' ); - $userExpectsConfirmation->expects( $this->any() )->method( 'getInstanceForUpdate' ) - ->willReturnSelf(); - $userExpectsConfirmation->expects( $this->once() )->method( 'sendConfirmationMail' ) - ->willReturn( \Status::newGood() ); - $userNotExpectsConfirmation = $this->getMockBuilder( \User::class )->getMock(); - $userNotExpectsConfirmation->expects( $this->any() )->method( 'getEmail' ) - ->willReturn( 'foo@bar.baz' ); - $userNotExpectsConfirmation->expects( $this->any() )->method( 'getInstanceForUpdate' ) - ->willReturnSelf(); - $userNotExpectsConfirmation->expects( $this->never() )->method( 'sendConfirmationMail' ); - - $provider = new EmailNotificationSecondaryAuthenticationProvider( [ - 'sendConfirmationEmail' => false, - ] ); - $provider->setManager( $authManager ); - $provider->beginSecondaryAccountCreation( $userNotExpectsConfirmation, $creator, [] ); - - $provider = new EmailNotificationSecondaryAuthenticationProvider( [ - 'sendConfirmationEmail' => true, - ] ); - $provider->setManager( $authManager ); - $provider->beginSecondaryAccountCreation( $userWithoutEmail, $creator, [] ); - $provider->beginSecondaryAccountCreation( $userExpectsConfirmation, $creator, [] ); - - // test logging of email errors - $logger = $this->getMockForAbstractClass( LoggerInterface::class ); - $logger->expects( $this->once() )->method( 'warning' ); - $provider->setLogger( $logger ); - $provider->beginSecondaryAccountCreation( $userWithEmailError, $creator, [] ); - - // test disable flag used by other providers - $authManager->setAuthenticationSessionData( 'no-email', true ); - $provider->setManager( $authManager ); - $provider->beginSecondaryAccountCreation( $userNotExpectsConfirmation, $creator, [] ); - } -} diff --git a/tests/phpunit/includes/changes/ChangesListFilterGroupTest.php b/tests/phpunit/includes/changes/ChangesListFilterGroupTest.php deleted file mode 100644 index 6190516e68..0000000000 --- a/tests/phpunit/includes/changes/ChangesListFilterGroupTest.php +++ /dev/null @@ -1,79 +0,0 @@ - 'some_type', - 'name' => 'group_name', - 'priority' => 1, - 'filters' => [], - ] - ); - } - - public function testAutoPriorities() { - $group = new MockChangesListFilterGroup( - [ - 'type' => 'some_type', - 'name' => 'groupName', - 'isFullCoverage' => true, - 'priority' => 1, - 'filters' => [ - [ 'name' => 'hidefoo' ], - [ 'name' => 'hidebar' ], - [ 'name' => 'hidebaz' ], - ], - ] - ); - - $filters = $group->getFilters(); - $this->assertEquals( - [ - -2, - -3, - -4, - ], - array_map( - function ( $f ) { - return $f->getPriority(); - }, - array_values( $filters ) - ) - ); - } - - // Get without warnings - public function testGetFilter() { - $group = new MockChangesListFilterGroup( - [ - 'type' => 'some_type', - 'name' => 'groupName', - 'isFullCoverage' => true, - 'priority' => 1, - 'filters' => [ - [ 'name' => 'foo' ], - ], - ] - ); - - $this->assertEquals( - 'foo', - $group->getFilter( 'foo' )->getName() - ); - - $this->assertEquals( - null, - $group->getFilter( 'bar' ) - ); - } -} diff --git a/tests/phpunit/includes/collation/CustomUppercaseCollationTest.php b/tests/phpunit/includes/collation/CustomUppercaseCollationTest.php deleted file mode 100644 index 417b468b1d..0000000000 --- a/tests/phpunit/includes/collation/CustomUppercaseCollationTest.php +++ /dev/null @@ -1,68 +0,0 @@ -collation = new CustomUppercaseCollation( [ - 'D', - 'C', - 'Cs', - 'B' - ], Language::factory( 'en' ) ); - - parent::setUp(); - } - - /** - * @dataProvider providerOrder - */ - public function testOrder( $first, $second, $msg ) { - $sortkey1 = $this->collation->getSortKey( $first ); - $sortkey2 = $this->collation->getSortKey( $second ); - - $this->assertTrue( strcmp( $sortkey1, $sortkey2 ) < 0, $msg ); - } - - public function providerOrder() { - return [ - [ 'X', 'Z', 'Maintain order of unrearranged' ], - [ 'D', 'C', 'Actually resorts' ], - [ 'D', 'B', 'resort test 2' ], - [ 'Adobe', 'Abode', 'not first letter' ], - [ '💩 ', 'C', 'Test relocated to end' ], - [ 'c', 'b', 'lowercase' ], - [ 'x', 'z', 'lowercase original' ], - [ 'Cz', 'Cs', 'digraphs' ], - [ 'C50D', 'C100', 'Numbers' ] - ]; - } - - /** - * @dataProvider provideGetFirstLetter - */ - public function testGetFirstLetter( $string, $first ) { - $this->assertSame( $this->collation->getFirstLetter( $string ), $first ); - } - - public function provideGetFirstLetter() { - return [ - [ 'Do', 'D' ], - [ 'do', 'D' ], - [ 'Ao', 'A' ], - [ 'afdsa', 'A' ], - [ "\u{F3000}Foo", 'D' ], - [ "\u{F3001}Foo", 'C' ], - [ "\u{F3002}Foo", 'Cs' ], - [ "\u{F3003}Foo", 'B' ], - [ "\u{F3004}Foo", "\u{F3004}" ], - [ 'C', 'C' ], - [ 'Cz', 'C' ], - [ 'Cs', 'Cs' ], - [ 'CS', 'Cs' ], - [ 'cs', 'Cs' ], - ]; - } -} diff --git a/tests/phpunit/includes/composer/ComposerVersionNormalizerTest.php b/tests/phpunit/includes/composer/ComposerVersionNormalizerTest.php deleted file mode 100644 index c5c0dc7d6b..0000000000 --- a/tests/phpunit/includes/composer/ComposerVersionNormalizerTest.php +++ /dev/null @@ -1,163 +0,0 @@ - - */ -class ComposerVersionNormalizerTest extends PHPUnit\Framework\TestCase { - - use MediaWikiCoversValidator; - use PHPUnit4And6Compat; - - /** - * @dataProvider nonStringProvider - */ - public function testGivenNonString_normalizeThrowsInvalidArgumentException( $nonString ) { - $normalizer = new ComposerVersionNormalizer(); - - $this->setExpectedException( InvalidArgumentException::class ); - $normalizer->normalizeSuffix( $nonString ); - } - - public function nonStringProvider() { - return [ - [ null ], - [ 42 ], - [ [] ], - [ new stdClass() ], - [ true ], - ]; - } - - /** - * @dataProvider simpleVersionProvider - */ - public function testGivenSimpleVersion_normalizeSuffixReturnsAsIs( $simpleVersion ) { - $this->assertRemainsUnchanged( $simpleVersion ); - } - - protected function assertRemainsUnchanged( $version ) { - $normalizer = new ComposerVersionNormalizer(); - - $this->assertEquals( - $version, - $normalizer->normalizeSuffix( $version ) - ); - } - - public function simpleVersionProvider() { - return [ - [ '1.22.0' ], - [ '1.19.2' ], - [ '1.19.2.0' ], - [ '1.9' ], - [ '123.321.456.654' ], - ]; - } - - /** - * @dataProvider complexVersionProvider - */ - public function testGivenComplexVersionWithoutDash_normalizeSuffixAddsDash( - $withoutDash, $withDash - ) { - $normalizer = new ComposerVersionNormalizer(); - - $this->assertEquals( - $withDash, - $normalizer->normalizeSuffix( $withoutDash ) - ); - } - - public function complexVersionProvider() { - return [ - [ '1.22.0alpha', '1.22.0-alpha' ], - [ '1.22.0RC', '1.22.0-RC' ], - [ '1.19beta', '1.19-beta' ], - [ '1.9RC4', '1.9-RC4' ], - [ '1.9.1.2RC4', '1.9.1.2-RC4' ], - [ '1.9.1.2RC', '1.9.1.2-RC' ], - [ '123.321.456.654RC9001', '123.321.456.654-RC9001' ], - ]; - } - - /** - * @dataProvider complexVersionProvider - */ - public function testGivenComplexVersionWithDash_normalizeSuffixReturnsAsIs( - $withoutDash, $withDash - ) { - $this->assertRemainsUnchanged( $withDash ); - } - - /** - * @dataProvider fourLevelVersionsProvider - */ - public function testGivenFourLevels_levelCountNormalizationDoesNothing( $version ) { - $normalizer = new ComposerVersionNormalizer(); - - $this->assertEquals( - $version, - $normalizer->normalizeLevelCount( $version ) - ); - } - - public function fourLevelVersionsProvider() { - return [ - [ '1.22.0.0' ], - [ '1.19.2.4' ], - [ '1.19.2.0' ], - [ '1.9.0.1' ], - [ '123.321.456.654' ], - [ '123.321.456.654RC4' ], - [ '123.321.456.654-RC4' ], - ]; - } - - /** - * @dataProvider levelNormalizationProvider - */ - public function testGivenFewerLevels_levelCountNormalizationEnsuresFourLevels( - $expected, $version - ) { - $normalizer = new ComposerVersionNormalizer(); - - $this->assertEquals( - $expected, - $normalizer->normalizeLevelCount( $version ) - ); - } - - public function levelNormalizationProvider() { - return [ - [ '1.22.0.0', '1.22' ], - [ '1.22.0.0', '1.22.0' ], - [ '1.19.2.0', '1.19.2' ], - [ '12345.0.0.0', '12345' ], - [ '12345.0.0.0-RC4', '12345-RC4' ], - [ '12345.0.0.0-alpha', '12345-alpha' ], - ]; - } - - /** - * @dataProvider invalidVersionProvider - */ - public function testGivenInvalidVersion_normalizeSuffixReturnsAsIs( $invalidVersion ) { - $this->assertRemainsUnchanged( $invalidVersion ); - } - - public function invalidVersionProvider() { - return [ - [ '1.221-a' ], - [ '1.221-' ], - [ '1.22rc4a' ], - [ 'a1.22rc' ], - [ '.1.22rc' ], - [ 'a' ], - [ 'alpha42' ], - ]; - } -} diff --git a/tests/phpunit/includes/config/ConfigFactoryTest.php b/tests/phpunit/includes/config/ConfigFactoryTest.php deleted file mode 100644 index ea747afac1..0000000000 --- a/tests/phpunit/includes/config/ConfigFactoryTest.php +++ /dev/null @@ -1,168 +0,0 @@ -register( 'unittest', 'GlobalVarConfig::newInstance' ); - $this->assertInstanceOf( GlobalVarConfig::class, $factory->makeConfig( 'unittest' ) ); - } - - /** - * @covers ConfigFactory::register - */ - public function testRegisterInvalid() { - $factory = new ConfigFactory(); - $this->setExpectedException( InvalidArgumentException::class ); - $factory->register( 'invalid', 'Invalid callback' ); - } - - /** - * @covers ConfigFactory::register - */ - public function testRegisterInvalidInstance() { - $factory = new ConfigFactory(); - $this->setExpectedException( InvalidArgumentException::class ); - $factory->register( 'invalidInstance', new stdClass ); - } - - /** - * @covers ConfigFactory::register - */ - public function testRegisterInstance() { - $config = GlobalVarConfig::newInstance(); - $factory = new ConfigFactory(); - $factory->register( 'unittest', $config ); - $this->assertSame( $config, $factory->makeConfig( 'unittest' ) ); - } - - /** - * @covers ConfigFactory::register - */ - public function testRegisterAgain() { - $factory = new ConfigFactory(); - $factory->register( 'unittest', 'GlobalVarConfig::newInstance' ); - $config1 = $factory->makeConfig( 'unittest' ); - - $factory->register( 'unittest', 'GlobalVarConfig::newInstance' ); - $config2 = $factory->makeConfig( 'unittest' ); - - $this->assertNotSame( $config1, $config2 ); - } - - /** - * @covers ConfigFactory::salvage - */ - public function testSalvage() { - $oldFactory = new ConfigFactory(); - $oldFactory->register( 'foo', 'GlobalVarConfig::newInstance' ); - $oldFactory->register( 'bar', 'GlobalVarConfig::newInstance' ); - $oldFactory->register( 'quux', 'GlobalVarConfig::newInstance' ); - - // instantiate two of the three defined configurations - $foo = $oldFactory->makeConfig( 'foo' ); - $bar = $oldFactory->makeConfig( 'bar' ); - $quux = $oldFactory->makeConfig( 'quux' ); - - // define new config instance - $newFactory = new ConfigFactory(); - $newFactory->register( 'foo', 'GlobalVarConfig::newInstance' ); - $newFactory->register( 'bar', function () { - return new HashConfig(); - } ); - - // "foo" and "quux" are defined in the old and the new factory. - // The old factory has instances for "foo" and "bar", but not "quux". - $newFactory->salvage( $oldFactory ); - - $newFoo = $newFactory->makeConfig( 'foo' ); - $this->assertSame( $foo, $newFoo, 'existing instance should be salvaged' ); - - $newBar = $newFactory->makeConfig( 'bar' ); - $this->assertNotSame( $bar, $newBar, 'don\'t salvage if callbacks differ' ); - - // the new factory doesn't have quux defined, so the quux instance should not be salvaged - $this->setExpectedException( ConfigException::class ); - $newFactory->makeConfig( 'quux' ); - } - - /** - * @covers ConfigFactory::getConfigNames - */ - public function testGetConfigNames() { - $factory = new ConfigFactory(); - $factory->register( 'foo', 'GlobalVarConfig::newInstance' ); - $factory->register( 'bar', new HashConfig() ); - - $this->assertEquals( [ 'foo', 'bar' ], $factory->getConfigNames() ); - } - - /** - * @covers ConfigFactory::makeConfig - */ - public function testMakeConfigWithCallback() { - $factory = new ConfigFactory(); - $factory->register( 'unittest', 'GlobalVarConfig::newInstance' ); - - $conf = $factory->makeConfig( 'unittest' ); - $this->assertInstanceOf( Config::class, $conf ); - $this->assertSame( $conf, $factory->makeConfig( 'unittest' ) ); - } - - /** - * @covers ConfigFactory::makeConfig - */ - public function testMakeConfigWithObject() { - $factory = new ConfigFactory(); - $conf = new HashConfig(); - $factory->register( 'test', $conf ); - $this->assertSame( $conf, $factory->makeConfig( 'test' ) ); - } - - /** - * @covers ConfigFactory::makeConfig - */ - public function testMakeConfigFallback() { - $factory = new ConfigFactory(); - $factory->register( '*', 'GlobalVarConfig::newInstance' ); - $conf = $factory->makeConfig( 'unittest' ); - $this->assertInstanceOf( Config::class, $conf ); - } - - /** - * @covers ConfigFactory::makeConfig - */ - public function testMakeConfigWithNoBuilders() { - $factory = new ConfigFactory(); - $this->setExpectedException( ConfigException::class ); - $factory->makeConfig( 'nobuilderregistered' ); - } - - /** - * @covers ConfigFactory::makeConfig - */ - public function testMakeConfigWithInvalidCallback() { - $factory = new ConfigFactory(); - $factory->register( 'unittest', function () { - return true; // Not a Config object - } ); - $this->setExpectedException( UnexpectedValueException::class ); - $factory->makeConfig( 'unittest' ); - } - - /** - * @covers ConfigFactory::getDefaultInstance - */ - public function testGetDefaultInstance() { - // NOTE: the global config factory returned here has been overwritten - // for operation in test mode. It may not reflect LocalSettings. - $factory = MediaWikiServices::getInstance()->getConfigFactory(); - $this->assertInstanceOf( Config::class, $factory->makeConfig( 'main' ) ); - } - -} diff --git a/tests/phpunit/includes/config/EtcdConfigTest.php b/tests/phpunit/includes/config/EtcdConfigTest.php deleted file mode 100644 index 3eecf82704..0000000000 --- a/tests/phpunit/includes/config/EtcdConfigTest.php +++ /dev/null @@ -1,621 +0,0 @@ -getMockBuilder( EtcdConfig::class ) - ->setConstructorArgs( [ $options + [ - 'host' => 'etcd-tcp.example.net', - 'directory' => '/', - 'timeout' => 0.1, - ] ] ) - ->setMethods( [ 'fetchAllFromEtcd' ] ) - ->getMock(); - } - - private static function createEtcdResponse( array $response ) { - $baseResponse = [ - 'config' => null, - 'error' => null, - 'retry' => false, - 'modifiedIndex' => 0, - ]; - return array_merge( $baseResponse, $response ); - } - - private function createSimpleConfigMock( array $config, $index = 0 ) { - $mock = $this->createConfigMock(); - $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' ) - ->willReturn( self::createEtcdResponse( [ - 'config' => $config, - 'modifiedIndex' => $index, - ] ) ); - return $mock; - } - - /** - * @covers EtcdConfig::has - */ - public function testHasKnown() { - $config = $this->createSimpleConfigMock( [ - 'known' => 'value' - ] ); - $this->assertSame( true, $config->has( 'known' ) ); - } - - /** - * @covers EtcdConfig::__construct - * @covers EtcdConfig::get - */ - public function testGetKnown() { - $config = $this->createSimpleConfigMock( [ - 'known' => 'value' - ] ); - $this->assertSame( 'value', $config->get( 'known' ) ); - } - - /** - * @covers EtcdConfig::has - */ - public function testHasUnknown() { - $config = $this->createSimpleConfigMock( [ - 'known' => 'value' - ] ); - $this->assertSame( false, $config->has( 'unknown' ) ); - } - - /** - * @covers EtcdConfig::get - */ - public function testGetUnknown() { - $config = $this->createSimpleConfigMock( [ - 'known' => 'value' - ] ); - $this->setExpectedException( ConfigException::class ); - $config->get( 'unknown' ); - } - - /** - * @covers EtcdConfig::getModifiedIndex - */ - public function testGetModifiedIndex() { - $config = $this->createSimpleConfigMock( - [ 'some' => 'value' ], - 123 - ); - $this->assertSame( 123, $config->getModifiedIndex() ); - } - - /** - * @covers EtcdConfig::__construct - */ - public function testConstructCacheObj() { - $cache = $this->getMockBuilder( HashBagOStuff::class ) - ->setMethods( [ 'get' ] ) - ->getMock(); - $cache->expects( $this->once() )->method( 'get' ) - ->willReturn( [ - 'config' => [ 'known' => 'from-cache' ], - 'expires' => INF, - 'modifiedIndex' => 123 - ] ); - $config = $this->createConfigMock( [ 'cache' => $cache ] ); - - $this->assertSame( 'from-cache', $config->get( 'known' ) ); - } - - /** - * @covers EtcdConfig::__construct - */ - public function testConstructCacheSpec() { - $config = $this->createConfigMock( [ 'cache' => [ - 'class' => HashBagOStuff::class - ] ] ); - $config->expects( $this->once() )->method( 'fetchAllFromEtcd' ) - ->willReturn( self::createEtcdResponse( - [ 'config' => [ 'known' => 'from-fetch' ], ] ) ); - - $this->assertSame( 'from-fetch', $config->get( 'known' ) ); - } - - /** - * Test matrix - * - * - [x] Cache miss - * Result: Fetched value - * > cache miss | gets lock | backend succeeds - * - * - [x] Cache miss with backend error - * Result: ConfigException - * > cache miss | gets lock | backend error (no retry) - * - * - [x] Cache hit after retry - * Result: Cached value (populated by process holding lock) - * > cache miss | no lock | cache retry - * - * - [x] Cache hit - * Result: Cached value - * > cache hit - * - * - [x] Process cache hit - * Result: Cached value - * > process cache hit - * - * - [x] Cache expired - * Result: Fetched value - * > cache expired | gets lock | backend succeeds - * - * - [x] Cache expired with backend failure - * Result: Cached value (stale) - * > cache expired | gets lock | backend fails (allows retry) - * - * - [x] Cache expired and no lock - * Result: Cached value (stale) - * > cache expired | no lock - * - * Other notable scenarios: - * - * - [ ] Cache miss with backend retry - * Result: Fetched value - * > cache expired | gets lock | backend failure (allows retry) - */ - - /** - * @covers EtcdConfig::load - */ - public function testLoadCacheMiss() { - // Create cache mock - $cache = $this->getMockBuilder( HashBagOStuff::class ) - ->setMethods( [ 'get', 'lock' ] ) - ->getMock(); - // .. misses cache - $cache->expects( $this->once() )->method( 'get' ) - ->willReturn( false ); - // .. gets lock - $cache->expects( $this->once() )->method( 'lock' ) - ->willReturn( true ); - - // Create config mock - $mock = $this->createConfigMock( [ - 'cache' => $cache, - ] ); - $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' ) - ->willReturn( - self::createEtcdResponse( [ 'config' => [ 'known' => 'from-fetch' ] ] ) ); - - $this->assertSame( 'from-fetch', $mock->get( 'known' ) ); - } - - /** - * @covers EtcdConfig::load - */ - public function testLoadCacheMissBackendError() { - // Create cache mock - $cache = $this->getMockBuilder( HashBagOStuff::class ) - ->setMethods( [ 'get', 'lock' ] ) - ->getMock(); - // .. misses cache - $cache->expects( $this->once() )->method( 'get' ) - ->willReturn( false ); - // .. gets lock - $cache->expects( $this->once() )->method( 'lock' ) - ->willReturn( true ); - - // Create config mock - $mock = $this->createConfigMock( [ - 'cache' => $cache, - ] ); - $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' ) - ->willReturn( self::createEtcdResponse( [ 'error' => 'Fake error', ] ) ); - - $this->setExpectedException( ConfigException::class ); - $mock->get( 'key' ); - } - - /** - * @covers EtcdConfig::load - */ - public function testLoadCacheMissWithoutLock() { - // Create cache mock - $cache = $this->getMockBuilder( HashBagOStuff::class ) - ->setMethods( [ 'get', 'lock' ] ) - ->getMock(); - $cache->expects( $this->exactly( 2 ) )->method( 'get' ) - ->will( $this->onConsecutiveCalls( - // .. misses cache first time - false, - // .. hits cache on retry - [ - 'config' => [ 'known' => 'from-cache' ], - 'expires' => INF, - 'modifiedIndex' => 123 - ] - ) ); - // .. misses lock - $cache->expects( $this->once() )->method( 'lock' ) - ->willReturn( false ); - - // Create config mock - $mock = $this->createConfigMock( [ - 'cache' => $cache, - ] ); - $mock->expects( $this->never() )->method( 'fetchAllFromEtcd' ); - - $this->assertSame( 'from-cache', $mock->get( 'known' ) ); - } - - /** - * @covers EtcdConfig::load - */ - public function testLoadCacheHit() { - // Create cache mock - $cache = $this->getMockBuilder( HashBagOStuff::class ) - ->setMethods( [ 'get', 'lock' ] ) - ->getMock(); - $cache->expects( $this->once() )->method( 'get' ) - // .. hits cache - ->willReturn( [ - 'config' => [ 'known' => 'from-cache' ], - 'expires' => INF, - 'modifiedIndex' => 0, - ] ); - $cache->expects( $this->never() )->method( 'lock' ); - - // Create config mock - $mock = $this->createConfigMock( [ - 'cache' => $cache, - ] ); - $mock->expects( $this->never() )->method( 'fetchAllFromEtcd' ); - - $this->assertSame( 'from-cache', $mock->get( 'known' ) ); - } - - /** - * @covers EtcdConfig::load - */ - public function testLoadProcessCacheHit() { - // Create cache mock - $cache = $this->getMockBuilder( HashBagOStuff::class ) - ->setMethods( [ 'get', 'lock' ] ) - ->getMock(); - $cache->expects( $this->once() )->method( 'get' ) - // .. hits cache - ->willReturn( [ - 'config' => [ 'known' => 'from-cache' ], - 'expires' => INF, - 'modifiedIndex' => 0, - ] ); - $cache->expects( $this->never() )->method( 'lock' ); - - // Create config mock - $mock = $this->createConfigMock( [ - 'cache' => $cache, - ] ); - $mock->expects( $this->never() )->method( 'fetchAllFromEtcd' ); - - $this->assertSame( 'from-cache', $mock->get( 'known' ), 'Cache hit' ); - $this->assertSame( 'from-cache', $mock->get( 'known' ), 'Process cache hit' ); - } - - /** - * @covers EtcdConfig::load - */ - public function testLoadCacheExpiredLockFetchSucceeded() { - // Create cache mock - $cache = $this->getMockBuilder( HashBagOStuff::class ) - ->setMethods( [ 'get', 'lock' ] ) - ->getMock(); - $cache->expects( $this->once() )->method( 'get' )->willReturn( - // .. stale cache - [ - 'config' => [ 'known' => 'from-cache-expired' ], - 'expires' => -INF, - 'modifiedIndex' => 0, - ] - ); - // .. gets lock - $cache->expects( $this->once() )->method( 'lock' ) - ->willReturn( true ); - - // Create config mock - $mock = $this->createConfigMock( [ - 'cache' => $cache, - ] ); - $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' ) - ->willReturn( self::createEtcdResponse( [ 'config' => [ 'known' => 'from-fetch' ] ] ) ); - - $this->assertSame( 'from-fetch', $mock->get( 'known' ) ); - } - - /** - * @covers EtcdConfig::load - */ - public function testLoadCacheExpiredLockFetchFails() { - // Create cache mock - $cache = $this->getMockBuilder( HashBagOStuff::class ) - ->setMethods( [ 'get', 'lock' ] ) - ->getMock(); - $cache->expects( $this->once() )->method( 'get' )->willReturn( - // .. stale cache - [ - 'config' => [ 'known' => 'from-cache-expired' ], - 'expires' => -INF, - 'modifiedIndex' => 0, - ] - ); - // .. gets lock - $cache->expects( $this->once() )->method( 'lock' ) - ->willReturn( true ); - - // Create config mock - $mock = $this->createConfigMock( [ - 'cache' => $cache, - ] ); - $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' ) - ->willReturn( self::createEtcdResponse( [ 'error' => 'Fake failure', 'retry' => true ] ) ); - - $this->assertSame( 'from-cache-expired', $mock->get( 'known' ) ); - } - - /** - * @covers EtcdConfig::load - */ - public function testLoadCacheExpiredNoLock() { - // Create cache mock - $cache = $this->getMockBuilder( HashBagOStuff::class ) - ->setMethods( [ 'get', 'lock' ] ) - ->getMock(); - $cache->expects( $this->once() )->method( 'get' ) - // .. hits cache (expired value) - ->willReturn( [ - 'config' => [ 'known' => 'from-cache-expired' ], - 'expires' => -INF, - 'modifiedIndex' => 0, - ] ); - // .. misses lock - $cache->expects( $this->once() )->method( 'lock' ) - ->willReturn( false ); - - // Create config mock - $mock = $this->createConfigMock( [ - 'cache' => $cache, - ] ); - $mock->expects( $this->never() )->method( 'fetchAllFromEtcd' ); - - $this->assertSame( 'from-cache-expired', $mock->get( 'known' ) ); - } - - public static function provideFetchFromServer() { - return [ - '200 OK - Success' => [ - 'http' => [ - 'code' => 200, - 'reason' => 'OK', - 'headers' => [], - 'body' => json_encode( [ 'node' => [ 'nodes' => [ - [ - 'key' => '/example/foo', - 'value' => json_encode( [ 'val' => true ] ), - 'modifiedIndex' => 123 - ], - ] ] ] ), - 'error' => '', - ], - 'expect' => self::createEtcdResponse( [ - 'config' => [ 'foo' => true ], // data - 'modifiedIndex' => 123 - ] ), - ], - '200 OK - Empty dir' => [ - 'http' => [ - 'code' => 200, - 'reason' => 'OK', - 'headers' => [], - 'body' => json_encode( [ 'node' => [ 'nodes' => [ - [ - 'key' => '/example/foo', - 'value' => json_encode( [ 'val' => true ] ), - 'modifiedIndex' => 123 - ], - [ - 'key' => '/example/sub', - 'dir' => true, - 'modifiedIndex' => 234, - 'nodes' => [], - ], - [ - 'key' => '/example/bar', - 'value' => json_encode( [ 'val' => false ] ), - 'modifiedIndex' => 125 - ], - ] ] ] ), - 'error' => '', - ], - 'expect' => self::createEtcdResponse( [ - 'config' => [ 'foo' => true, 'bar' => false ], // data - 'modifiedIndex' => 125 // largest modified index - ] ), - ], - '200 OK - Recursive' => [ - 'http' => [ - 'code' => 200, - 'reason' => 'OK', - 'headers' => [], - 'body' => json_encode( [ 'node' => [ 'nodes' => [ - [ - 'key' => '/example/a', - 'dir' => true, - 'modifiedIndex' => 124, - 'nodes' => [ - [ - 'key' => 'b', - 'value' => json_encode( [ 'val' => true ] ), - 'modifiedIndex' => 123, - - ], - [ - 'key' => 'c', - 'value' => json_encode( [ 'val' => false ] ), - 'modifiedIndex' => 123, - ], - ], - ], - ] ] ] ), - 'error' => '', - ], - 'expect' => self::createEtcdResponse( [ - 'config' => [ 'a/b' => true, 'a/c' => false ], // data - 'modifiedIndex' => 123 // largest modified index - ] ), - ], - '200 OK - Missing nodes at second level' => [ - 'http' => [ - 'code' => 200, - 'reason' => 'OK', - 'headers' => [], - 'body' => json_encode( [ 'node' => [ 'nodes' => [ - [ - 'key' => '/example/a', - 'dir' => true, - 'modifiedIndex' => 0, - ], - ] ] ] ), - 'error' => '', - ], - 'expect' => self::createEtcdResponse( [ - 'error' => "Unexpected JSON response in dir 'a'; missing 'nodes' list.", - ] ), - ], - '200 OK - Directory with non-array "nodes" key' => [ - 'http' => [ - 'code' => 200, - 'reason' => 'OK', - 'headers' => [], - 'body' => json_encode( [ 'node' => [ 'nodes' => [ - [ - 'key' => '/example/a', - 'dir' => true, - 'nodes' => 'not an array' - ], - ] ] ] ), - 'error' => '', - ], - 'expect' => self::createEtcdResponse( [ - 'error' => "Unexpected JSON response in dir 'a'; 'nodes' is not an array.", - ] ), - ], - '200 OK - Correctly encoded garbage response' => [ - 'http' => [ - 'code' => 200, - 'reason' => 'OK', - 'headers' => [], - 'body' => json_encode( [ 'foo' => 'bar' ] ), - 'error' => '', - ], - 'expect' => self::createEtcdResponse( [ - 'error' => "Unexpected JSON response: Missing or invalid node at top level.", - ] ), - ], - '200 OK - Bad value' => [ - 'http' => [ - 'code' => 200, - 'reason' => 'OK', - 'headers' => [], - 'body' => json_encode( [ 'node' => [ 'nodes' => [ - [ - 'key' => '/example/foo', - 'value' => ';"broken{value', - 'modifiedIndex' => 123, - ] - ] ] ] ), - 'error' => '', - ], - 'expect' => self::createEtcdResponse( [ - 'error' => "Failed to parse value for 'foo'.", - ] ), - ], - '200 OK - Empty node list' => [ - 'http' => [ - 'code' => 200, - 'reason' => 'OK', - 'headers' => [], - 'body' => '{"node":{"nodes":[], "modifiedIndex": 12 }}', - 'error' => '', - ], - 'expect' => self::createEtcdResponse( [ - 'config' => [], // data - ] ), - ], - '200 OK - Invalid JSON' => [ - 'http' => [ - 'code' => 200, - 'reason' => 'OK', - 'headers' => [ 'content-length' => 0 ], - 'body' => '', - 'error' => '(curl error: no status set)', - ], - 'expect' => self::createEtcdResponse( [ - 'error' => "Error unserializing JSON response.", - ] ), - ], - '404 Not Found' => [ - 'http' => [ - 'code' => 404, - 'reason' => 'Not Found', - 'headers' => [ 'content-length' => 0 ], - 'body' => '', - 'error' => '', - ], - 'expect' => self::createEtcdResponse( [ - 'error' => 'HTTP 404 (Not Found)', - ] ), - ], - '400 Bad Request - custom error' => [ - 'http' => [ - 'code' => 400, - 'reason' => 'Bad Request', - 'headers' => [ 'content-length' => 0 ], - 'body' => '', - 'error' => 'No good reason', - ], - 'expect' => self::createEtcdResponse( [ - 'error' => 'No good reason', - 'retry' => true, // retry - ] ), - ], - ]; - } - - /** - * @covers EtcdConfig::fetchAllFromEtcdServer - * @covers EtcdConfig::unserialize - * @covers EtcdConfig::parseResponse - * @covers EtcdConfig::parseDirectory - * @covers EtcdConfigParseError - * @dataProvider provideFetchFromServer - */ - public function testFetchFromServer( array $httpResponse, array $expected ) { - $http = $this->getMockBuilder( MultiHttpClient::class ) - ->disableOriginalConstructor() - ->getMock(); - $http->expects( $this->once() )->method( 'run' ) - ->willReturn( array_values( $httpResponse ) ); - - $conf = $this->getMockBuilder( EtcdConfig::class ) - ->disableOriginalConstructor() - ->getMock(); - // Access for protected member and method - $conf = TestingAccessWrapper::newFromObject( $conf ); - $conf->http = $http; - - $this->assertSame( - $expected, - $conf->fetchAllFromEtcdServer( 'etcd-tcp.example.net' ) - ); - } -} diff --git a/tests/phpunit/includes/config/HashConfigTest.php b/tests/phpunit/includes/config/HashConfigTest.php deleted file mode 100644 index bac8311cd4..0000000000 --- a/tests/phpunit/includes/config/HashConfigTest.php +++ /dev/null @@ -1,63 +0,0 @@ -assertInstanceOf( HashConfig::class, $conf ); - } - - /** - * @covers HashConfig::__construct - */ - public function testConstructor() { - $conf = new HashConfig(); - $this->assertInstanceOf( HashConfig::class, $conf ); - - // Test passing arguments to the constructor - $conf2 = new HashConfig( [ - 'one' => '1', - ] ); - $this->assertEquals( '1', $conf2->get( 'one' ) ); - } - - /** - * @covers HashConfig::get - */ - public function testGet() { - $conf = new HashConfig( [ - 'one' => '1', - ] ); - $this->assertEquals( '1', $conf->get( 'one' ) ); - $this->setExpectedException( ConfigException::class, 'HashConfig::get: undefined option' ); - $conf->get( 'two' ); - } - - /** - * @covers HashConfig::has - */ - public function testHas() { - $conf = new HashConfig( [ - 'one' => '1', - ] ); - $this->assertTrue( $conf->has( 'one' ) ); - $this->assertFalse( $conf->has( 'two' ) ); - } - - /** - * @covers HashConfig::set - */ - public function testSet() { - $conf = new HashConfig( [ - 'one' => '1', - ] ); - $conf->set( 'two', '2' ); - $this->assertEquals( '2', $conf->get( 'two' ) ); - // Check that set overwrites - $conf->set( 'one', '3' ); - $this->assertEquals( '3', $conf->get( 'one' ) ); - } -} diff --git a/tests/phpunit/includes/config/MultiConfigTest.php b/tests/phpunit/includes/config/MultiConfigTest.php deleted file mode 100644 index fc2839513b..0000000000 --- a/tests/phpunit/includes/config/MultiConfigTest.php +++ /dev/null @@ -1,39 +0,0 @@ - 'bar' ] ), - new HashConfig( [ 'foo' => 'baz', 'bar' => 'foo' ] ), - new HashConfig( [ 'bar' => 'baz' ] ), - ] ); - - $this->assertEquals( 'bar', $multi->get( 'foo' ) ); - $this->assertEquals( 'foo', $multi->get( 'bar' ) ); - $this->setExpectedException( ConfigException::class, 'MultiConfig::get: undefined option:' ); - $multi->get( 'notset' ); - } - - /** - * @covers MultiConfig::has - */ - public function testHas() { - $conf = new MultiConfig( [ - new HashConfig( [ 'foo' => 'foo' ] ), - new HashConfig( [ 'something' => 'bleh' ] ), - new HashConfig( [ 'meh' => 'eh' ] ), - ] ); - - $this->assertTrue( $conf->has( 'foo' ) ); - $this->assertTrue( $conf->has( 'something' ) ); - $this->assertTrue( $conf->has( 'meh' ) ); - $this->assertFalse( $conf->has( 'what' ) ); - } -} diff --git a/tests/phpunit/includes/config/ServiceOptionsTest.php b/tests/phpunit/includes/config/ServiceOptionsTest.php deleted file mode 100644 index 966cf411c7..0000000000 --- a/tests/phpunit/includes/config/ServiceOptionsTest.php +++ /dev/null @@ -1,149 +0,0 @@ - $val ) { - $this->assertSame( $val, $options->get( $key ) ); - } - - // This is lumped in the same test because there's no support for depending on a test that - // has a data provider. - $options->assertRequiredOptions( array_keys( $expected ) ); - - // Suppress warning if no assertions were run. This is expected for empty arguments. - $this->assertTrue( true ); - } - - public function provideConstructor() { - return [ - 'No keys' => [ [], [], [ 'a' => 'aval' ] ], - 'Simple array source' => [ - [ 'a' => 'aval', 'b' => 'bval' ], - [ 'a', 'b' ], - [ 'a' => 'aval', 'b' => 'bval', 'c' => 'cval' ], - ], - 'Simple HashConfig source' => [ - [ 'a' => 'aval', 'b' => 'bval' ], - [ 'a', 'b' ], - new HashConfig( [ 'a' => 'aval', 'b' => 'bval', 'c' => 'cval' ] ), - ], - 'Three different sources' => [ - [ 'a' => 'aval', 'b' => 'bval' ], - [ 'a', 'b' ], - [ 'z' => 'zval' ], - new HashConfig( [ 'a' => 'aval', 'c' => 'cval' ] ), - [ 'b' => 'bval', 'd' => 'dval' ], - ], - 'null key' => [ - [ 'a' => null ], - [ 'a' ], - [ 'a' => null ], - ], - 'Numeric option name' => [ - [ '0' => 'nothing' ], - [ '0' ], - [ '0' => 'nothing' ], - ], - 'Multiple sources for one key' => [ - [ 'a' => 'winner' ], - [ 'a' ], - [ 'a' => 'winner' ], - [ 'a' => 'second place' ], - ], - 'Object value is passed by reference' => [ - [ 'a' => self::$testObj ], - [ 'a' ], - [ 'a' => self::$testObj ], - ], - ]; - } - - /** - * @covers ::__construct - */ - public function testKeyNotFound() { - $this->setExpectedException( InvalidArgumentException::class, - 'Key "a" not found in input sources' ); - - new ServiceOptions( [ 'a' ], [ 'b' => 'bval' ], [ 'c' => 'cval' ] ); - } - - /** - * @covers ::__construct - * @covers ::assertRequiredOptions - */ - public function testOutOfOrderAssertRequiredOptions() { - $options = new ServiceOptions( [ 'a', 'b' ], [ 'a' => '', 'b' => '' ] ); - $options->assertRequiredOptions( [ 'b', 'a' ] ); - $this->assertTrue( true, 'No exception thrown' ); - } - - /** - * @covers ::__construct - * @covers ::get - */ - public function testGetUnrecognized() { - $this->setExpectedException( InvalidArgumentException::class, - 'Unrecognized option "b"' ); - - $options = new ServiceOptions( [ 'a' ], [ 'a' => '' ] ); - $options->get( 'b' ); - } - - /** - * @covers ::__construct - * @covers ::assertRequiredOptions - */ - public function testExtraKeys() { - $this->setExpectedException( Wikimedia\Assert\PreconditionException::class, - 'Precondition failed: Unsupported options passed: b, c!' ); - - $options = new ServiceOptions( [ 'a', 'b', 'c' ], [ 'a' => '', 'b' => '', 'c' => '' ] ); - $options->assertRequiredOptions( [ 'a' ] ); - } - - /** - * @covers ::__construct - * @covers ::assertRequiredOptions - */ - public function testMissingKeys() { - $this->setExpectedException( Wikimedia\Assert\PreconditionException::class, - 'Precondition failed: Required options missing: a, b!' ); - - $options = new ServiceOptions( [ 'c' ], [ 'c' => '' ] ); - $options->assertRequiredOptions( [ 'a', 'b', 'c' ] ); - } - - /** - * @covers ::__construct - * @covers ::assertRequiredOptions - */ - public function testExtraAndMissingKeys() { - $this->setExpectedException( Wikimedia\Assert\PreconditionException::class, - 'Precondition failed: Unsupported options passed: b! Required options missing: c!' ); - - $options = new ServiceOptions( [ 'a', 'b' ], [ 'a' => '', 'b' => '' ] ); - $options->assertRequiredOptions( [ 'a', 'c' ] ); - } -} diff --git a/tests/phpunit/includes/content/JsonContentHandlerTest.php b/tests/phpunit/includes/content/JsonContentHandlerTest.php deleted file mode 100644 index abfb6733a5..0000000000 --- a/tests/phpunit/includes/content/JsonContentHandlerTest.php +++ /dev/null @@ -1,14 +0,0 @@ -makeEmptyContent(); - $this->assertInstanceOf( JsonContent::class, $content ); - $this->assertTrue( $content->isValid() ); - } -} diff --git a/tests/phpunit/includes/db/DatabaseOracleTest.php b/tests/phpunit/includes/db/DatabaseOracleTest.php deleted file mode 100644 index 061e121a24..0000000000 --- a/tests/phpunit/includes/db/DatabaseOracleTest.php +++ /dev/null @@ -1,52 +0,0 @@ -getMockBuilder( DatabaseOracle::class ) - ->disableOriginalConstructor() - ->setMethods( null ) - ->getMock(); - } - - public function provideBuildSubstring() { - yield [ 'someField', 1, 2, 'SUBSTR(someField,1,2)' ]; - yield [ 'someField', 1, null, 'SUBSTR(someField,1)' ]; - } - - /** - * @covers DatabaseOracle::buildSubstring - * @dataProvider provideBuildSubstring - */ - public function testBuildSubstring( $input, $start, $length, $expected ) { - $mockDb = $this->getMockDb(); - $output = $mockDb->buildSubstring( $input, $start, $length ); - $this->assertSame( $expected, $output ); - } - - public function provideBuildSubstring_invalidParams() { - yield [ -1, 1 ]; - yield [ 1, -1 ]; - yield [ 1, 'foo' ]; - yield [ 'foo', 1 ]; - yield [ null, 1 ]; - yield [ 0, 1 ]; - } - - /** - * @covers DatabaseOracle::buildSubstring - * @dataProvider provideBuildSubstring_invalidParams - */ - public function testBuildSubstring_invalidParams( $start, $length ) { - $mockDb = $this->getMockDb(); - $this->setExpectedException( InvalidArgumentException::class ); - $mockDb->buildSubstring( 'foo', $start, $length ); - } - -} diff --git a/tests/phpunit/includes/debug/MWDebugTest.php b/tests/phpunit/includes/debug/MWDebugTest.php deleted file mode 100644 index 6f0b1db9f2..0000000000 --- a/tests/phpunit/includes/debug/MWDebugTest.php +++ /dev/null @@ -1,140 +0,0 @@ -assertEquals( - [ [ - 'msg' => 'logging a string', - 'type' => 'log', - 'caller' => 'MWDebugTest->testAddLog', - ] ], - MWDebug::getLog() - ); - } - - /** - * @covers MWDebug::warning - */ - public function testAddWarning() { - MWDebug::warning( 'Warning message' ); - $this->assertEquals( - [ [ - 'msg' => 'Warning message', - 'type' => 'warn', - 'caller' => 'MWDebugTest::testAddWarning', - ] ], - MWDebug::getLog() - ); - } - - /** - * @covers MWDebug::deprecated - */ - public function testAvoidDuplicateDeprecations() { - MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' ); - MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' ); - - // assertCount() not available on WMF integration server - $this->assertEquals( 1, - count( MWDebug::getLog() ), - "Only one deprecated warning per function should be kept" - ); - } - - /** - * @covers MWDebug::deprecated - */ - public function testAvoidNonConsecutivesDuplicateDeprecations() { - MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' ); - MWDebug::warning( 'some warning' ); - MWDebug::log( 'we could have logged something too' ); - // Another deprecation - MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' ); - - // assertCount() not available on WMF integration server - $this->assertEquals( 3, - count( MWDebug::getLog() ), - "Only one deprecated warning per function should be kept" - ); - } - - /** - * @covers MWDebug::appendDebugInfoToApiResult - */ - public function testAppendDebugInfoToApiResultXmlFormat() { - $request = $this->newApiRequest( - [ 'action' => 'help', 'format' => 'xml' ], - '/api.php?action=help&format=xml' - ); - - $context = new RequestContext(); - $context->setRequest( $request ); - - $apiMain = new ApiMain( $context ); - - $result = new ApiResult( $apiMain ); - - MWDebug::appendDebugInfoToApiResult( $context, $result ); - - $this->assertInstanceOf( ApiResult::class, $result ); - $data = $result->getResultData(); - - $expectedKeys = [ 'mwVersion', 'phpEngine', 'phpVersion', 'gitRevision', 'gitBranch', - 'gitViewUrl', 'time', 'log', 'debugLog', 'queries', 'request', 'memory', - 'memoryPeak', 'includes', '_element' ]; - - foreach ( $expectedKeys as $expectedKey ) { - $this->assertArrayHasKey( $expectedKey, $data['debuginfo'], "debuginfo has $expectedKey" ); - } - - $xml = ApiFormatXml::recXmlPrint( 'help', $data, null ); - - // exception not thrown - $this->assertInternalType( 'string', $xml ); - } - - /** - * @param string[] $params - * @param string $requestUrl - * - * @return FauxRequest - */ - private function newApiRequest( array $params, $requestUrl ) { - $request = $this->getMockBuilder( FauxRequest::class ) - ->setMethods( [ 'getRequestURL' ] ) - ->setConstructorArgs( [ - $params - ] ) - ->getMock(); - - $request->expects( $this->any() ) - ->method( 'getRequestURL' ) - ->will( $this->returnValue( $requestUrl ) ); - - return $request; - } - -} diff --git a/tests/phpunit/includes/debug/logger/MonologSpiTest.php b/tests/phpunit/includes/debug/logger/MonologSpiTest.php deleted file mode 100644 index fda3ac614a..0000000000 --- a/tests/phpunit/includes/debug/logger/MonologSpiTest.php +++ /dev/null @@ -1,136 +0,0 @@ - [ - '@default' => [ - 'processors' => [ 'constructor' ], - 'handlers' => [ 'constructor' ], - ], - ], - 'processors' => [ - 'constructor' => [ - 'class' => 'constructor', - ], - ], - 'handlers' => [ - 'constructor' => [ - 'class' => 'constructor', - 'formatter' => 'constructor', - ], - ], - 'formatters' => [ - 'constructor' => [ - 'class' => 'constructor', - ], - ], - ]; - - $fixture = new MonologSpi( $base ); - $this->assertSame( - $base, - TestingAccessWrapper::newFromObject( $fixture )->config - ); - - $fixture->mergeConfig( [ - 'loggers' => [ - 'merged' => [ - 'processors' => [ 'merged' ], - 'handlers' => [ 'merged' ], - ], - ], - 'processors' => [ - 'merged' => [ - 'class' => 'merged', - ], - ], - 'magic' => [ - 'idkfa' => [ 'xyzzy' ], - ], - 'handlers' => [ - 'merged' => [ - 'class' => 'merged', - 'formatter' => 'merged', - ], - ], - 'formatters' => [ - 'merged' => [ - 'class' => 'merged', - ], - ], - ] ); - $this->assertSame( - [ - 'loggers' => [ - '@default' => [ - 'processors' => [ 'constructor' ], - 'handlers' => [ 'constructor' ], - ], - 'merged' => [ - 'processors' => [ 'merged' ], - 'handlers' => [ 'merged' ], - ], - ], - 'processors' => [ - 'constructor' => [ - 'class' => 'constructor', - ], - 'merged' => [ - 'class' => 'merged', - ], - ], - 'handlers' => [ - 'constructor' => [ - 'class' => 'constructor', - 'formatter' => 'constructor', - ], - 'merged' => [ - 'class' => 'merged', - 'formatter' => 'merged', - ], - ], - 'formatters' => [ - 'constructor' => [ - 'class' => 'constructor', - ], - 'merged' => [ - 'class' => 'merged', - ], - ], - 'magic' => [ - 'idkfa' => [ 'xyzzy' ], - ], - ], - TestingAccessWrapper::newFromObject( $fixture )->config - ); - } - -} diff --git a/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php b/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php deleted file mode 100644 index baa4df7390..0000000000 --- a/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php +++ /dev/null @@ -1,76 +0,0 @@ -markTestSkipped( 'Avro is required for the AvroFormatterTest' ); - } - parent::setUp(); - } - - public function testSchemaNotAvailable() { - $formatter = new AvroFormatter( [] ); - $this->setExpectedException( - 'PHPUnit_Framework_Error_Notice', - "The schema for channel 'marty' is not available" - ); - $formatter->format( [ 'channel' => 'marty' ] ); - } - - public function testSchemaNotAvailableReturnValue() { - $formatter = new AvroFormatter( [] ); - $noticeEnabled = PHPUnit_Framework_Error_Notice::$enabled; - // disable conversion of notices - PHPUnit_Framework_Error_Notice::$enabled = false; - // have to keep the user notice from being output - \Wikimedia\suppressWarnings(); - $res = $formatter->format( [ 'channel' => 'marty' ] ); - \Wikimedia\restoreWarnings(); - PHPUnit_Framework_Error_Notice::$enabled = $noticeEnabled; - $this->assertNull( $res ); - } - - public function testDoesSomethingWhenSchemaAvailable() { - $formatter = new AvroFormatter( [ - 'string' => [ - 'schema' => [ 'type' => 'string' ], - 'revision' => 1010101, - ] - ] ); - $res = $formatter->format( [ - 'channel' => 'string', - 'context' => 'better to be', - ] ); - $this->assertNotNull( $res ); - // basically just tell us if avro changes its string encoding, or if - // we completely fail to generate a log message. - $this->assertEquals( 'AAAAAAAAD2m1GGJldHRlciB0byBiZQ==', base64_encode( $res ) ); - } -} diff --git a/tests/phpunit/includes/debug/logger/monolog/CeeFormatterTest.php b/tests/phpunit/includes/debug/logger/monolog/CeeFormatterTest.php deleted file mode 100644 index b30c7a4c92..0000000000 --- a/tests/phpunit/includes/debug/logger/monolog/CeeFormatterTest.php +++ /dev/null @@ -1,20 +0,0 @@ - [ 'url' => 1 ], 'context' => [ 'url' => 2 ] ]; - $this->assertSame( - $cee_formatter->format( $record ), - "@cee: " . $ls_formatter->format( $record ) ); - } -} diff --git a/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php b/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php deleted file mode 100644 index 4c0ca04fef..0000000000 --- a/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php +++ /dev/null @@ -1,227 +0,0 @@ -markTestSkipped( 'Monolog and Kafka are required for the KafkaHandlerTest' ); - } - - parent::setUp(); - } - - public function topicNamingProvider() { - return [ - [ [], 'monolog_foo' ], - [ [ 'alias' => [ 'foo' => 'bar' ] ], 'bar' ] - ]; - } - - /** - * @dataProvider topicNamingProvider - */ - public function testTopicNaming( $options, $expect ) { - $produce = $this->getMockBuilder( 'Kafka\Produce' ) - ->disableOriginalConstructor() - ->getMock(); - $produce->expects( $this->any() ) - ->method( 'getAvailablePartitions' ) - ->will( $this->returnValue( [ 'A' ] ) ); - $produce->expects( $this->once() ) - ->method( 'setMessages' ) - ->with( $expect, $this->anything(), $this->anything() ); - $produce->expects( $this->any() ) - ->method( 'send' ) - ->will( $this->returnValue( true ) ); - - $handler = new KafkaHandler( $produce, $options ); - $handler->handle( [ - 'channel' => 'foo', - 'level' => Logger::EMERGENCY, - 'extra' => [], - 'context' => [], - ] ); - } - - public function swallowsExceptionsWhenRequested() { - return [ - // defaults to false - [ [], true ], - // also try false explicitly - [ [ 'swallowExceptions' => false ], true ], - // turn it on - [ [ 'swallowExceptions' => true ], false ], - ]; - } - - /** - * @dataProvider swallowsExceptionsWhenRequested - */ - public function testGetAvailablePartitionsException( $options, $expectException ) { - $produce = $this->getMockBuilder( 'Kafka\Produce' ) - ->disableOriginalConstructor() - ->getMock(); - $produce->expects( $this->any() ) - ->method( 'getAvailablePartitions' ) - ->will( $this->throwException( new \Kafka\Exception ) ); - $produce->expects( $this->any() ) - ->method( 'send' ) - ->will( $this->returnValue( true ) ); - - if ( $expectException ) { - $this->setExpectedException( 'Kafka\Exception' ); - } - - $handler = new KafkaHandler( $produce, $options ); - $handler->handle( [ - 'channel' => 'foo', - 'level' => Logger::EMERGENCY, - 'extra' => [], - 'context' => [], - ] ); - - if ( !$expectException ) { - $this->assertTrue( true, 'no exception was thrown' ); - } - } - - /** - * @dataProvider swallowsExceptionsWhenRequested - */ - public function testSendException( $options, $expectException ) { - $produce = $this->getMockBuilder( 'Kafka\Produce' ) - ->disableOriginalConstructor() - ->getMock(); - $produce->expects( $this->any() ) - ->method( 'getAvailablePartitions' ) - ->will( $this->returnValue( [ 'A' ] ) ); - $produce->expects( $this->any() ) - ->method( 'send' ) - ->will( $this->throwException( new \Kafka\Exception ) ); - - if ( $expectException ) { - $this->setExpectedException( 'Kafka\Exception' ); - } - - $handler = new KafkaHandler( $produce, $options ); - $handler->handle( [ - 'channel' => 'foo', - 'level' => Logger::EMERGENCY, - 'extra' => [], - 'context' => [], - ] ); - - if ( !$expectException ) { - $this->assertTrue( true, 'no exception was thrown' ); - } - } - - public function testHandlesNullFormatterResult() { - $produce = $this->getMockBuilder( 'Kafka\Produce' ) - ->disableOriginalConstructor() - ->getMock(); - $produce->expects( $this->any() ) - ->method( 'getAvailablePartitions' ) - ->will( $this->returnValue( [ 'A' ] ) ); - $mockMethod = $produce->expects( $this->exactly( 2 ) ) - ->method( 'setMessages' ); - $produce->expects( $this->any() ) - ->method( 'send' ) - ->will( $this->returnValue( true ) ); - // evil hax - $matcher = TestingAccessWrapper::newFromObject( $mockMethod )->matcher; - TestingAccessWrapper::newFromObject( $matcher )->parametersMatcher = - new \PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters( [ - [ $this->anything(), $this->anything(), [ 'words' ] ], - [ $this->anything(), $this->anything(), [ 'lines' ] ] - ] ); - - $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class ); - $formatter->expects( $this->any() ) - ->method( 'format' ) - ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) ); - - $handler = new KafkaHandler( $produce, [] ); - $handler->setFormatter( $formatter ); - for ( $i = 0; $i < 3; ++$i ) { - $handler->handle( [ - 'channel' => 'foo', - 'level' => Logger::EMERGENCY, - 'extra' => [], - 'context' => [], - ] ); - } - } - - public function testBatchHandlesNullFormatterResult() { - $produce = $this->getMockBuilder( 'Kafka\Produce' ) - ->disableOriginalConstructor() - ->getMock(); - $produce->expects( $this->any() ) - ->method( 'getAvailablePartitions' ) - ->will( $this->returnValue( [ 'A' ] ) ); - $produce->expects( $this->once() ) - ->method( 'setMessages' ) - ->with( $this->anything(), $this->anything(), [ 'words', 'lines' ] ); - $produce->expects( $this->any() ) - ->method( 'send' ) - ->will( $this->returnValue( true ) ); - - $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class ); - $formatter->expects( $this->any() ) - ->method( 'format' ) - ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) ); - - $handler = new KafkaHandler( $produce, [] ); - $handler->setFormatter( $formatter ); - $handler->handleBatch( [ - [ - 'channel' => 'foo', - 'level' => Logger::EMERGENCY, - 'extra' => [], - 'context' => [], - ], - [ - 'channel' => 'foo', - 'level' => Logger::EMERGENCY, - 'extra' => [], - 'context' => [], - ], - [ - 'channel' => 'foo', - 'level' => Logger::EMERGENCY, - 'extra' => [], - 'context' => [], - ], - ] ); - } -} diff --git a/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php b/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php deleted file mode 100644 index bdd5c81118..0000000000 --- a/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php +++ /dev/null @@ -1,122 +0,0 @@ -markTestSkipped( 'This test requires monolog to be installed' ); - } - parent::setUp(); - } - - /** - * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException - */ - public function testNormalizeExceptionNoTrace() { - $fixture = new LineFormatter(); - $fixture->includeStacktraces( false ); - $fixture = TestingAccessWrapper::newFromObject( $fixture ); - $boom = new InvalidArgumentException( 'boom', 0, - new LengthException( 'too long', 0, - new LogicException( 'Spock wuz here' ) - ) - ); - $out = $fixture->normalizeException( $boom ); - $this->assertContains( "\n[Exception InvalidArgumentException]", $out ); - $this->assertContains( "\nCaused by: [Exception LengthException]", $out ); - $this->assertContains( "\nCaused by: [Exception LogicException]", $out ); - $this->assertNotContains( "\n #0", $out ); - } - - /** - * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException - */ - public function testNormalizeExceptionTrace() { - $fixture = new LineFormatter(); - $fixture->includeStacktraces( true ); - $fixture = TestingAccessWrapper::newFromObject( $fixture ); - $boom = new InvalidArgumentException( 'boom', 0, - new LengthException( 'too long', 0, - new LogicException( 'Spock wuz here' ) - ) - ); - $out = $fixture->normalizeException( $boom ); - $this->assertContains( "\n[Exception InvalidArgumentException]", $out ); - $this->assertContains( "\nCaused by: [Exception LengthException]", $out ); - $this->assertContains( "\nCaused by: [Exception LogicException]", $out ); - $this->assertContains( "\n #0", $out ); - } - - /** - * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException - */ - public function testNormalizeExceptionErrorNoTrace() { - if ( !class_exists( AssertionError::class ) ) { - $this->markTestSkipped( 'AssertionError class does not exist' ); - } - - $fixture = new LineFormatter(); - $fixture->includeStacktraces( false ); - $fixture = TestingAccessWrapper::newFromObject( $fixture ); - $boom = new InvalidArgumentException( 'boom', 0, - new LengthException( 'too long', 0, - new AssertionError( 'Spock wuz here' ) - ) - ); - $out = $fixture->normalizeException( $boom ); - $this->assertContains( "\n[Exception InvalidArgumentException]", $out ); - $this->assertContains( "\nCaused by: [Exception LengthException]", $out ); - $this->assertContains( "\nCaused by: [Error AssertionError]", $out ); - $this->assertNotContains( "\n #0", $out ); - } - - /** - * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException - */ - public function testNormalizeExceptionErrorTrace() { - if ( !class_exists( AssertionError::class ) ) { - $this->markTestSkipped( 'AssertionError class does not exist' ); - } - - $fixture = new LineFormatter(); - $fixture->includeStacktraces( true ); - $fixture = TestingAccessWrapper::newFromObject( $fixture ); - $boom = new InvalidArgumentException( 'boom', 0, - new LengthException( 'too long', 0, - new AssertionError( 'Spock wuz here' ) - ) - ); - $out = $fixture->normalizeException( $boom ); - $this->assertContains( "\n[Exception InvalidArgumentException]", $out ); - $this->assertContains( "\nCaused by: [Exception LengthException]", $out ); - $this->assertContains( "\nCaused by: [Error AssertionError]", $out ); - $this->assertContains( "\n #0", $out ); - } -} diff --git a/tests/phpunit/includes/debug/logger/monolog/LogstashFormatterTest.php b/tests/phpunit/includes/debug/logger/monolog/LogstashFormatterTest.php deleted file mode 100644 index a1207b26b2..0000000000 --- a/tests/phpunit/includes/debug/logger/monolog/LogstashFormatterTest.php +++ /dev/null @@ -1,59 +0,0 @@ -format( $record ), true ); - foreach ( $expected as $key => $value ) { - $this->assertArrayHasKey( $key, $formatted ); - $this->assertSame( $value, $formatted[$key] ); - } - foreach ( $notExpected as $key ) { - $this->assertArrayNotHasKey( $key, $formatted ); - } - } - - public function provideV1() { - return [ - [ - [ 'extra' => [ 'foo' => 1 ], 'context' => [ 'bar' => 2 ] ], - [ 'foo' => 1, 'bar' => 2 ], - [ 'logstash_formatter_key_conflict' ], - ], - [ - [ 'extra' => [ 'url' => 1 ], 'context' => [ 'url' => 2 ] ], - [ 'url' => 1, 'c_url' => 2, 'logstash_formatter_key_conflict' => [ 'url' ] ], - [], - ], - [ - [ 'channel' => 'x', 'context' => [ 'channel' => 'y' ] ], - [ 'channel' => 'x', 'c_channel' => 'y', - 'logstash_formatter_key_conflict' => [ 'channel' ] ], - [], - ], - ]; - } - - /** - * @covers MediaWiki\Logger\Monolog\LogstashFormatter::formatV1 - */ - public function testV1WithPrefix() { - $formatter = new LogstashFormatter( 'app', 'system', null, 'ctx_', LogstashFormatter::V1 ); - $record = [ 'extra' => [ 'url' => 1 ], 'context' => [ 'url' => 2 ] ]; - $formatted = json_decode( $formatter->format( $record ), true ); - $this->assertArrayHasKey( 'url', $formatted ); - $this->assertSame( 1, $formatted['url'] ); - $this->assertArrayHasKey( 'ctx_url', $formatted ); - $this->assertSame( 2, $formatted['ctx_url'] ); - $this->assertArrayNotHasKey( 'c_url', $formatted ); - } -} diff --git a/tests/phpunit/includes/deferred/MWCallableUpdateTest.php b/tests/phpunit/includes/deferred/MWCallableUpdateTest.php deleted file mode 100644 index 3ab9b5659e..0000000000 --- a/tests/phpunit/includes/deferred/MWCallableUpdateTest.php +++ /dev/null @@ -1,82 +0,0 @@ -assertSame( 0, $ran ); - $update->doUpdate(); - $this->assertSame( 1, $ran ); - } - - public function testCancel() { - // Prepare update and DB - $db = new DatabaseTestHelper( __METHOD__ ); - $db->begin( __METHOD__ ); - $ran = 0; - $update = new MWCallableUpdate( function () use ( &$ran ) { - $ran++; - }, __METHOD__, $db ); - - // Emulate rollback - $db->rollback( __METHOD__ ); - - $update->doUpdate(); - - // Ensure it was cancelled - $this->assertSame( 0, $ran ); - } - - public function testCancelSome() { - // Prepare update and DB - $db1 = new DatabaseTestHelper( __METHOD__ ); - $db1->begin( __METHOD__ ); - $db2 = new DatabaseTestHelper( __METHOD__ ); - $db2->begin( __METHOD__ ); - $ran = 0; - $update = new MWCallableUpdate( function () use ( &$ran ) { - $ran++; - }, __METHOD__, [ $db1, $db2 ] ); - - // Emulate rollback - $db1->rollback( __METHOD__ ); - - $update->doUpdate(); - - // Prevents: "Notice: DB transaction writes or callbacks still pending" - $db2->rollback( __METHOD__ ); - - // Ensure it was cancelled - $this->assertSame( 0, $ran ); - } - - public function testCancelAll() { - // Prepare update and DB - $db1 = new DatabaseTestHelper( __METHOD__ ); - $db1->begin( __METHOD__ ); - $db2 = new DatabaseTestHelper( __METHOD__ ); - $db2->begin( __METHOD__ ); - $ran = 0; - $update = new MWCallableUpdate( function () use ( &$ran ) { - $ran++; - }, __METHOD__, [ $db1, $db2 ] ); - - // Emulate rollbacks - $db1->rollback( __METHOD__ ); - $db2->rollback( __METHOD__ ); - - $update->doUpdate(); - - // Ensure it was cancelled - $this->assertSame( 0, $ran ); - } - -} diff --git a/tests/phpunit/includes/deferred/TransactionRoundDefiningUpdateTest.php b/tests/phpunit/includes/deferred/TransactionRoundDefiningUpdateTest.php deleted file mode 100644 index 693897e676..0000000000 --- a/tests/phpunit/includes/deferred/TransactionRoundDefiningUpdateTest.php +++ /dev/null @@ -1,19 +0,0 @@ -assertSame( 0, $ran ); - $update->doUpdate(); - $this->assertSame( 1, $ran ); - } -} diff --git a/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php b/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php deleted file mode 100644 index 8d94404cd4..0000000000 --- a/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php +++ /dev/null @@ -1,134 +0,0 @@ -format( $input ); - $this->assertEquals( $expectedOutput, $output ); - } - - private function getMockDiff( $edits ) { - $diff = $this->getMockBuilder( Diff::class ) - ->disableOriginalConstructor() - ->getMock(); - $diff->expects( $this->any() ) - ->method( 'getEdits' ) - ->will( $this->returnValue( $edits ) ); - return $diff; - } - - private function getMockDiffOp( $type = null, $orig = [], $closing = [] ) { - $diffOp = $this->getMockBuilder( DiffOp::class ) - ->disableOriginalConstructor() - ->getMock(); - $diffOp->expects( $this->any() ) - ->method( 'getType' ) - ->will( $this->returnValue( $type ) ); - $diffOp->expects( $this->any() ) - ->method( 'getOrig' ) - ->will( $this->returnValue( $orig ) ); - if ( $type === 'change' ) { - $diffOp->expects( $this->any() ) - ->method( 'getClosing' ) - ->with( $this->isType( 'integer' ) ) - ->will( $this->returnCallback( function () { - return 'mockLine'; - } ) ); - } else { - $diffOp->expects( $this->any() ) - ->method( 'getClosing' ) - ->will( $this->returnValue( $closing ) ); - } - return $diffOp; - } - - public function provideTestFormat() { - $emptyArrayTestCases = [ - $this->getMockDiff( [] ), - $this->getMockDiff( [ $this->getMockDiffOp( 'add' ) ] ), - $this->getMockDiff( [ $this->getMockDiffOp( 'delete' ) ] ), - $this->getMockDiff( [ $this->getMockDiffOp( 'change' ) ] ), - $this->getMockDiff( [ $this->getMockDiffOp( 'copy' ) ] ), - $this->getMockDiff( [ $this->getMockDiffOp( 'FOOBARBAZ' ) ] ), - $this->getMockDiff( [ $this->getMockDiffOp( 'add', 'line' ) ] ), - $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [], [ 'line' ] ) ] ), - $this->getMockDiff( [ $this->getMockDiffOp( 'copy', [], [ 'line' ] ) ] ), - ]; - - $otherTestCases = []; - $otherTestCases[] = [ - $this->getMockDiff( [ $this->getMockDiffOp( 'add', [], [ 'a1' ] ) ] ), - [ [ 'action' => 'add', 'new' => 'a1', 'newline' => 1 ] ], - ]; - $otherTestCases[] = [ - $this->getMockDiff( [ $this->getMockDiffOp( 'add', [], [ 'a1', 'a2' ] ) ] ), - [ - [ 'action' => 'add', 'new' => 'a1', 'newline' => 1 ], - [ 'action' => 'add', 'new' => 'a2', 'newline' => 2 ], - ], - ]; - $otherTestCases[] = [ - $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [ 'd1' ] ) ] ), - [ [ 'action' => 'delete', 'old' => 'd1', 'oldline' => 1 ] ], - ]; - $otherTestCases[] = [ - $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [ 'd1', 'd2' ] ) ] ), - [ - [ 'action' => 'delete', 'old' => 'd1', 'oldline' => 1 ], - [ 'action' => 'delete', 'old' => 'd2', 'oldline' => 2 ], - ], - ]; - $otherTestCases[] = [ - $this->getMockDiff( [ $this->getMockDiffOp( 'change', [ 'd1' ], [ 'a1' ] ) ] ), - [ [ - 'action' => 'change', - 'old' => 'd1', - 'new' => 'mockLine', - 'newline' => 1, 'oldline' => 1 - ] ], - ]; - $otherTestCases[] = [ - $this->getMockDiff( [ $this->getMockDiffOp( - 'change', - [ 'd1', 'd2' ], - [ 'a1', 'a2' ] - ) ] ), - [ - [ - 'action' => 'change', - 'old' => 'd1', - 'new' => 'mockLine', - 'newline' => 1, 'oldline' => 1 - ], - [ - 'action' => 'change', - 'old' => 'd2', - 'new' => 'mockLine', - 'newline' => 2, 'oldline' => 2 - ], - ], - ]; - - $testCases = []; - foreach ( $emptyArrayTestCases as $testCase ) { - $testCases[] = [ $testCase, [] ]; - } - foreach ( $otherTestCases as $testCase ) { - $testCases[] = [ $testCase[0], $testCase[1] ]; - } - return $testCases; - } - -} diff --git a/tests/phpunit/includes/diff/DiffOpTest.php b/tests/phpunit/includes/diff/DiffOpTest.php deleted file mode 100644 index 3026fad6bd..0000000000 --- a/tests/phpunit/includes/diff/DiffOpTest.php +++ /dev/null @@ -1,68 +0,0 @@ -type = 'foo'; - $this->assertEquals( 'foo', $obj->getType() ); - } - - /** - * @covers DiffOp::getOrig - */ - public function testGetOrig() { - $obj = new FakeDiffOp(); - $obj->orig = [ 'foo' ]; - $this->assertEquals( [ 'foo' ], $obj->getOrig() ); - } - - /** - * @covers DiffOp::getClosing - */ - public function testGetClosing() { - $obj = new FakeDiffOp(); - $obj->closing = [ 'foo' ]; - $this->assertEquals( [ 'foo' ], $obj->getClosing() ); - } - - /** - * @covers DiffOp::getClosing - */ - public function testGetClosingWithParameter() { - $obj = new FakeDiffOp(); - $obj->closing = [ 'foo', 'bar', 'baz' ]; - $this->assertEquals( 'foo', $obj->getClosing( 0 ) ); - $this->assertEquals( 'bar', $obj->getClosing( 1 ) ); - $this->assertEquals( 'baz', $obj->getClosing( 2 ) ); - $this->assertEquals( null, $obj->getClosing( 3 ) ); - } - - /** - * @covers DiffOp::norig - */ - public function testNorig() { - $obj = new FakeDiffOp(); - $this->assertEquals( 0, $obj->norig() ); - $obj->orig = [ 'foo' ]; - $this->assertEquals( 1, $obj->norig() ); - } - - /** - * @covers DiffOp::nclosing - */ - public function testNclosing() { - $obj = new FakeDiffOp(); - $this->assertEquals( 0, $obj->nclosing() ); - $obj->closing = [ 'foo' ]; - $this->assertEquals( 1, $obj->nclosing() ); - } - -} diff --git a/tests/phpunit/includes/diff/DiffTest.php b/tests/phpunit/includes/diff/DiffTest.php deleted file mode 100644 index da6d7d9544..0000000000 --- a/tests/phpunit/includes/diff/DiffTest.php +++ /dev/null @@ -1,19 +0,0 @@ -edits = 'FooBarBaz'; - $this->assertEquals( 'FooBarBaz', $obj->getEdits() ); - } - -} diff --git a/tests/phpunit/includes/diff/DifferenceEngineSlotDiffRendererTest.php b/tests/phpunit/includes/diff/DifferenceEngineSlotDiffRendererTest.php deleted file mode 100644 index fe129b751a..0000000000 --- a/tests/phpunit/includes/diff/DifferenceEngineSlotDiffRendererTest.php +++ /dev/null @@ -1,44 +0,0 @@ -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 index a03280ddb2..0000000000 --- a/tests/phpunit/includes/diff/SlotDiffRendererTest.php +++ /dev/null @@ -1,78 +0,0 @@ -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/exception/HttpErrorTest.php b/tests/phpunit/includes/exception/HttpErrorTest.php deleted file mode 100644 index 90ccd1e551..0000000000 --- a/tests/phpunit/includes/exception/HttpErrorTest.php +++ /dev/null @@ -1,63 +0,0 @@ -assertFalse( $httpError->isLoggable(), 'http error is not loggable' ); - } - - public function testGetStatusCode() { - $httpError = new HttpError( 500, 'server error!' ); - $this->assertEquals( 500, $httpError->getStatusCode() ); - } - - /** - * @dataProvider getHtmlProvider - */ - public function testGetHtml( array $expected, $content, $header ) { - $httpError = new HttpError( 500, $content, $header ); - $errorHtml = $httpError->getHTML(); - - foreach ( $expected as $key => $html ) { - $this->assertContains( $html, $errorHtml, $key ); - } - } - - public function getHtmlProvider() { - return [ - [ - [ - 'head html' => 'Server Error 123', - 'body html' => '

Server Error 123

' - . '

a server error!

' - ], - 'a server error!', - 'Server Error 123' - ], - [ - [ - 'head html' => 'loginerror', - 'body html' => '

loginerror

' - . '

suspicious-userlogout

' - ], - new RawMessage( 'suspicious-userlogout' ), - new RawMessage( 'loginerror' ) - ], - [ - [ - 'head html' => 'Internal Server Error', - 'body html' => '

Internal Server Error

' - . '

a server error!

' - ], - 'a server error!', - null - ] - ]; - } -} diff --git a/tests/phpunit/includes/exception/MWExceptionHandlerTest.php b/tests/phpunit/includes/exception/MWExceptionHandlerTest.php deleted file mode 100644 index 6606065660..0000000000 --- a/tests/phpunit/includes/exception/MWExceptionHandlerTest.php +++ /dev/null @@ -1,74 +0,0 @@ -getTrace(); - $hasObject = false; - $hasArray = false; - foreach ( $trace as $frame ) { - if ( !isset( $frame['args'] ) ) { - continue; - } - foreach ( $frame['args'] as $arg ) { - $hasObject = $hasObject || is_object( $arg ); - $hasArray = $hasArray || is_array( $arg ); - } - - if ( $hasObject && $hasArray ) { - break; - } - } - $this->assertTrue( $hasObject, - "The stacktrace must have a function having an object has parameter" ); - $this->assertTrue( $hasArray, - "The stacktrace must have a function having an array has parameter" ); - - # Now we redact the trace.. and make sure no function arguments are - # arrays or objects. - $redacted = MWExceptionHandler::getRedactedTrace( $e ); - - foreach ( $redacted as $frame ) { - if ( !isset( $frame['args'] ) ) { - continue; - } - foreach ( $frame['args'] as $arg ) { - $this->assertNotInternalType( 'array', $arg ); - $this->assertNotInternalType( 'object', $arg ); - } - } - - $this->assertEquals( 'value', $refvar, 'Ensuring reference variable wasn\'t changed' ); - } - - /** - * Helper function for testExpandArgumentsInCall - * - * Pass it an object and an array, and something by reference :-) - * - * @throws Exception - */ - protected static function helperThrowAnException( $a, $b, &$c ) { - throw new Exception(); - } -} diff --git a/tests/phpunit/includes/exception/ReadOnlyErrorTest.php b/tests/phpunit/includes/exception/ReadOnlyErrorTest.php deleted file mode 100644 index ee5becffc6..0000000000 --- a/tests/phpunit/includes/exception/ReadOnlyErrorTest.php +++ /dev/null @@ -1,16 +0,0 @@ -assertEquals( 'readonly', $e->title ); - $this->assertEquals( 'readonlytext', $e->msg ); - $this->assertEquals( wfReadOnlyReason() ?: [], $e->params ); - } - -} diff --git a/tests/phpunit/includes/exception/UserNotLoggedInTest.php b/tests/phpunit/includes/exception/UserNotLoggedInTest.php deleted file mode 100644 index 55ec45a020..0000000000 --- a/tests/phpunit/includes/exception/UserNotLoggedInTest.php +++ /dev/null @@ -1,16 +0,0 @@ -assertEquals( 'exception-nologin', $e->title ); - $this->assertEquals( 'exception-nologin-text', $e->msg ); - $this->assertEquals( [], $e->params ); - } - -} diff --git a/tests/phpunit/includes/externalstore/ExternalStoreFactoryTest.php b/tests/phpunit/includes/externalstore/ExternalStoreFactoryTest.php deleted file mode 100644 index f762693864..0000000000 --- a/tests/phpunit/includes/externalstore/ExternalStoreFactoryTest.php +++ /dev/null @@ -1,41 +0,0 @@ -assertFalse( $factory->getStoreObject( 'ForTesting' ) ); - $this->assertFalse( $factory->getStoreObject( 'foo' ) ); - } - - public function provideStoreNames() { - yield 'Same case as construction' => [ 'ForTesting' ]; - yield 'All lower case' => [ 'fortesting' ]; - yield 'All upper case' => [ 'FORTESTING' ]; - yield 'Mix of cases' => [ 'FOrTEsTInG' ]; - } - - /** - * @dataProvider provideStoreNames - */ - public function testExternalStoreFactory_someStore_protoMatch( $proto ) { - $factory = new ExternalStoreFactory( [ 'ForTesting' ] ); - $store = $factory->getStoreObject( $proto ); - $this->assertInstanceOf( ExternalStoreForTesting::class, $store ); - } - - /** - * @dataProvider provideStoreNames - */ - public function testExternalStoreFactory_someStore_noProtoMatch( $proto ) { - $factory = new ExternalStoreFactory( [ 'SomeOtherClassName' ] ); - $store = $factory->getStoreObject( $proto ); - $this->assertFalse( $store ); - } - -} diff --git a/tests/phpunit/includes/filebackend/SwiftFileBackendTest.php b/tests/phpunit/includes/filebackend/SwiftFileBackendTest.php deleted file mode 100644 index 35eca28f7e..0000000000 --- a/tests/phpunit/includes/filebackend/SwiftFileBackendTest.php +++ /dev/null @@ -1,216 +0,0 @@ -backend = TestingAccessWrapper::newFromObject( - new SwiftFileBackend( [ - 'name' => 'local-swift-testing', - 'class' => SwiftFileBackend::class, - 'wikiId' => 'unit-testing', - 'lockManager' => LockManagerGroup::singleton()->get( 'fsLockManager' ), - 'swiftAuthUrl' => 'http://127.0.0.1:8080/auth', // unused - 'swiftUser' => 'test:tester', - 'swiftKey' => 'testing', - 'swiftTempUrlKey' => 'b3968d0207b54ece87cccc06515a89d4' // unused - ] ) - ); - } - - /** - * @dataProvider provider_testSanitizeHdrsStrict - */ - public function testSanitizeHdrsStrict( $raw, $sanitized ) { - $hdrs = $this->backend->sanitizeHdrsStrict( [ 'headers' => $raw ] ); - - $this->assertEquals( $hdrs, $sanitized, 'sanitizeHdrsStrict() has expected result' ); - } - - public static function provider_testSanitizeHdrsStrict() { - return [ - [ - [ - 'content-length' => 345, - 'content-type' => 'image+bitmap/jpeg', - 'content-disposition' => 'inline', - 'content-duration' => 35.6363, - 'content-Custom' => 'hello', - 'x-content-custom' => 'hello' - ], - [ - 'content-disposition' => 'inline', - 'content-duration' => 35.6363, - 'content-custom' => 'hello', - 'x-content-custom' => 'hello' - ] - ], - [ - [ - 'content-length' => 345, - 'content-type' => 'image+bitmap/jpeg', - 'content-Disposition' => 'inline; filename=xxx; ' . str_repeat( 'o', 1024 ), - 'content-duration' => 35.6363, - 'content-custom' => 'hello', - 'x-content-custom' => 'hello' - ], - [ - 'content-disposition' => 'inline;filename=xxx', - 'content-duration' => 35.6363, - 'content-custom' => 'hello', - 'x-content-custom' => 'hello' - ] - ], - [ - [ - 'content-length' => 345, - 'content-type' => 'image+bitmap/jpeg', - 'content-disposition' => 'filename=' . str_repeat( 'o', 1024 ) . ';inline', - 'content-duration' => 35.6363, - 'content-custom' => 'hello', - 'x-content-custom' => 'hello' - ], - [ - 'content-disposition' => '', - 'content-duration' => 35.6363, - 'content-custom' => 'hello', - 'x-content-custom' => 'hello' - ] - ] - ]; - } - - /** - * @dataProvider provider_testSanitizeHdrs - */ - public function testSanitizeHdrs( $raw, $sanitized ) { - $hdrs = $this->backend->sanitizeHdrs( [ 'headers' => $raw ] ); - - $this->assertEquals( $hdrs, $sanitized, 'sanitizeHdrs() has expected result' ); - } - - public static function provider_testSanitizeHdrs() { - return [ - [ - [ - 'content-length' => 345, - 'content-type' => 'image+bitmap/jpeg', - 'content-disposition' => 'inline', - 'content-duration' => 35.6363, - 'content-Custom' => 'hello', - 'x-content-custom' => 'hello' - ], - [ - 'content-type' => 'image+bitmap/jpeg', - 'content-disposition' => 'inline', - 'content-duration' => 35.6363, - 'content-custom' => 'hello', - 'x-content-custom' => 'hello' - ] - ], - [ - [ - 'content-length' => 345, - 'content-type' => 'image+bitmap/jpeg', - 'content-Disposition' => 'inline; filename=xxx; ' . str_repeat( 'o', 1024 ), - 'content-duration' => 35.6363, - 'content-custom' => 'hello', - 'x-content-custom' => 'hello' - ], - [ - 'content-type' => 'image+bitmap/jpeg', - 'content-disposition' => 'inline;filename=xxx', - 'content-duration' => 35.6363, - 'content-custom' => 'hello', - 'x-content-custom' => 'hello' - ] - ], - [ - [ - 'content-length' => 345, - 'content-type' => 'image+bitmap/jpeg', - 'content-disposition' => 'filename=' . str_repeat( 'o', 1024 ) . ';inline', - 'content-duration' => 35.6363, - 'content-custom' => 'hello', - 'x-content-custom' => 'hello' - ], - [ - 'content-type' => 'image+bitmap/jpeg', - 'content-disposition' => '', - 'content-duration' => 35.6363, - 'content-custom' => 'hello', - 'x-content-custom' => 'hello' - ] - ] - ]; - } - - /** - * @dataProvider provider_testGetMetadataHeaders - */ - public function testGetMetadataHeaders( $raw, $sanitized ) { - $hdrs = $this->backend->getMetadataHeaders( $raw ); - - $this->assertEquals( $hdrs, $sanitized, 'getMetadataHeaders() has expected result' ); - } - - public static function provider_testGetMetadataHeaders() { - return [ - [ - [ - 'content-length' => 345, - 'content-custom' => 'hello', - 'x-content-custom' => 'hello', - 'x-object-meta-custom' => 5, - 'x-object-meta-sha1Base36' => 'a3deadfg...', - ], - [ - 'x-object-meta-custom' => 5, - 'x-object-meta-sha1base36' => 'a3deadfg...', - ] - ] - ]; - } - - /** - * @dataProvider provider_testGetMetadata - */ - public function testGetMetadata( $raw, $sanitized ) { - $hdrs = $this->backend->getMetadata( $raw ); - - $this->assertEquals( $hdrs, $sanitized, 'getMetadata() has expected result' ); - } - - public static function provider_testGetMetadata() { - return [ - [ - [ - 'content-length' => 345, - 'content-custom' => 'hello', - 'x-content-custom' => 'hello', - 'x-object-meta-custom' => 5, - 'x-object-meta-sha1Base36' => 'a3deadfg...', - ], - [ - 'custom' => 5, - 'sha1base36' => 'a3deadfg...', - ] - ] - ]; - } -} diff --git a/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php b/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php deleted file mode 100644 index 346be7afa3..0000000000 --- a/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php +++ /dev/null @@ -1,140 +0,0 @@ -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/FileRepoTest.php b/tests/phpunit/includes/filerepo/FileRepoTest.php deleted file mode 100644 index 0d3e679aa8..0000000000 --- a/tests/phpunit/includes/filerepo/FileRepoTest.php +++ /dev/null @@ -1,55 +0,0 @@ - 'foobar' - ] ); - } - - /** - * @expectedException MWException - * @covers FileRepo::__construct - */ - public function testFileRepoConstructionOptionNeedBackendKey() { - new FileRepo( [ - 'name' => 'foobar' - ] ); - } - - /** - * @covers FileRepo::__construct - */ - public function testFileRepoConstructionWithRequiredOptions() { - $f = new FileRepo( [ - 'name' => 'FileRepoTestRepository', - 'backend' => new FSFileBackend( [ - 'name' => 'local-testing', - 'wikiId' => 'test_wiki', - 'containerPaths' => [] - ] ) - ] ); - $this->assertInstanceOf( FileRepo::class, $f ); - } -} diff --git a/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php b/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php deleted file mode 100644 index eaba22d7fb..0000000000 --- a/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php +++ /dev/null @@ -1,66 +0,0 @@ - 'BGR', - 'Burkina Faso' => 'BFA', - 'Burundi' => 'BDI', - ]; - - /** - * Verify that attempting to instantiate an HTMLAutoCompleteSelectField - * without providing any autocomplete options causes an exception to be - * thrown. - * - * @expectedException MWException - * @expectedExceptionMessage called without any autocompletions - */ - function testMissingAutocompletions() { - new HTMLAutoCompleteSelectField( [ 'fieldname' => 'Test' ] ); - } - - /** - * Verify that the autocomplete options are correctly encoded as - * the 'data-autocomplete' attribute of the field. - * - * @covers HTMLAutoCompleteSelectField::getAttributes - */ - function testGetAttributes() { - $field = new HTMLAutoCompleteSelectField( [ - 'fieldname' => 'Test', - 'autocomplete' => $this->options, - ] ); - - $attributes = $field->getAttributes( [] ); - $this->assertEquals( array_keys( $this->options ), - FormatJson::decode( $attributes['data-autocomplete'] ), - "The 'data-autocomplete' attribute encodes autocomplete option keys as a JSON array." - ); - } - - /** - * Test that the optional select dropdown is included or excluded based on - * the presence or absence of the 'options' parameter. - */ - function testOptionalSelectElement() { - $params = [ - 'fieldname' => 'Test', - 'autocomplete-data' => $this->options, - 'options' => $this->options, - ]; - - $field = new HTMLAutoCompleteSelectField( $params ); - $html = $field->getInputHTML( false ); - $this->assertRegExp( '/select/', $html, - "When the 'options' parameter is set, the HTML includes a " ); - } -} diff --git a/tests/phpunit/includes/htmlform/HTMLCheckMatrixTest.php b/tests/phpunit/includes/htmlform/HTMLCheckMatrixTest.php deleted file mode 100644 index 05c567df75..0000000000 --- a/tests/phpunit/includes/htmlform/HTMLCheckMatrixTest.php +++ /dev/null @@ -1,104 +0,0 @@ - [ '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/htmlform/HTMLFormTest.php b/tests/phpunit/includes/htmlform/HTMLFormTest.php deleted file mode 100644 index d7dc4112b7..0000000000 --- a/tests/phpunit/includes/htmlform/HTMLFormTest.php +++ /dev/null @@ -1,63 +0,0 @@ -setTitle( Title::newFromText( 'Foo' ) ); - return $form; - } - - public function testGetHTML_empty() { - $form = $this->newInstance(); - $form->prepareForm(); - $html = $form->getHTML( false ); - $this->assertStringStartsWith( '
newInstance(); - $form->getHTML( false ); - } - - public function testAutocompleteDefaultsToNull() { - $form = $this->newInstance(); - $this->assertNotContains( 'autocomplete', $form->wrapForm( '' ) ); - } - - public function testAutocompleteWhenSetToNull() { - $form = $this->newInstance(); - $form->setAutocomplete( null ); - $this->assertNotContains( 'autocomplete', $form->wrapForm( '' ) ); - } - - public function testAutocompleteWhenSetToFalse() { - $form = $this->newInstance(); - // Previously false was used instead of null to indicate the attribute should not be set - $form->setAutocomplete( false ); - $this->assertNotContains( 'autocomplete', $form->wrapForm( '' ) ); - } - - public function testAutocompleteWhenSetToOff() { - $form = $this->newInstance(); - $form->setAutocomplete( 'off' ); - $this->assertContains( ' autocomplete="off"', $form->wrapForm( '' ) ); - } - - public function testGetPreText() { - $preText = 'TEST'; - $form = $this->newInstance(); - $form->setPreText( $preText ); - $this->assertSame( $preText, $form->getPreText() ); - } - -} diff --git a/tests/phpunit/includes/htmlform/HTMLRestrictionsFieldTest.php b/tests/phpunit/includes/htmlform/HTMLRestrictionsFieldTest.php deleted file mode 100644 index c4290e1e64..0000000000 --- a/tests/phpunit/includes/htmlform/HTMLRestrictionsFieldTest.php +++ /dev/null @@ -1,71 +0,0 @@ - 'restrictions' ] ); - $this->assertNotEmpty( $field->getLabel(), 'has a default label' ); - $this->assertNotEmpty( $field->getHelpText(), 'has a default help text' ); - $this->assertEquals( MWRestrictions::newDefault(), $field->getDefault(), - 'defaults to the default MWRestrictions object' ); - - $field = new HTMLRestrictionsField( [ - 'fieldname' => 'restrictions', - 'label' => 'foo', - 'help' => 'bar', - 'default' => 'baz', - ] ); - $this->assertEquals( 'foo', $field->getLabel(), 'label can be customized' ); - $this->assertEquals( 'bar', $field->getHelpText(), 'help text can be customized' ); - $this->assertEquals( 'baz', $field->getDefault(), 'default can be customized' ); - } - - /** - * @dataProvider provideValidate - */ - public function testForm( $text, $value ) { - $form = HTMLForm::factory( 'ooui', [ - 'restrictions' => [ 'class' => HTMLRestrictionsField::class ], - ] ); - $request = new FauxRequest( [ 'wprestrictions' => $text ], true ); - $context = new DerivativeContext( RequestContext::getMain() ); - $context->setRequest( $request ); - $form->setContext( $context ); - $form->setTitle( Title::newFromText( 'Main Page' ) )->setSubmitCallback( function () { - return true; - } )->prepareForm(); - $status = $form->trySubmit(); - - if ( $status instanceof StatusValue ) { - $this->assertEquals( $value !== false, $status->isGood() ); - } elseif ( $value === false ) { - $this->assertNotSame( true, $status ); - } else { - $this->assertSame( true, $status ); - } - - if ( $value !== false ) { - $restrictions = $form->mFieldData['restrictions']; - $this->assertInstanceOf( MWRestrictions::class, $restrictions ); - $this->assertEquals( $value, $restrictions->toArray()['IPAddresses'] ); - } - - // sanity - $form->getHTML( $status ); - } - - public function provideValidate() { - return [ - // submitted text, value of 'IPAddresses' key or false for validation error - [ null, [ '0.0.0.0/0', '::/0' ] ], - [ '', [] ], - [ "1.2.3.4\n::/0", [ '1.2.3.4', '::/0' ] ], - [ "1.2.3.4\n::/x", false ], - ]; - } -} diff --git a/tests/phpunit/includes/http/GuzzleHttpRequestTest.php b/tests/phpunit/includes/http/GuzzleHttpRequestTest.php deleted file mode 100644 index c9356b6b10..0000000000 --- a/tests/phpunit/includes/http/GuzzleHttpRequestTest.php +++ /dev/null @@ -1,151 +0,0 @@ -bodyTextReceived .= $buffer; - return strlen( $buffer ); - } - - public function testSuccess() { - $handler = HandlerStack::create( new MockHandler( [ new Response( 200, [ - 'status' => 200, - ], $this->exampleBodyText ) ] ) ); - $r = new GuzzleHttpRequest( $this->exampleUrl, [ 'handler' => $handler ] ); - $r->execute(); - - $this->assertEquals( 200, $r->getStatus() ); - $this->assertEquals( $this->exampleBodyText, $r->getContent() ); - } - - public function testSuccessConstructorCallback() { - $this->bodyTextReceived = ''; - $handler = HandlerStack::create( new MockHandler( [ new Response( 200, [ - 'status' => 200, - ], $this->exampleBodyText ) ] ) ); - $r = new GuzzleHttpRequest( $this->exampleUrl, [ - 'callback' => [ $this, 'processHttpDataChunk' ], - 'handler' => $handler, - ] ); - $r->execute(); - - $this->assertEquals( 200, $r->getStatus() ); - $this->assertEquals( $this->exampleBodyText, $this->bodyTextReceived ); - } - - public function testSuccessSetCallback() { - $this->bodyTextReceived = ''; - $handler = HandlerStack::create( new MockHandler( [ new Response( 200, [ - 'status' => 200, - ], $this->exampleBodyText ) ] ) ); - $r = new GuzzleHttpRequest( $this->exampleUrl, [ - 'handler' => $handler, - ] ); - $r->setCallback( [ $this, 'processHttpDataChunk' ] ); - $r->execute(); - - $this->assertEquals( 200, $r->getStatus() ); - $this->assertEquals( $this->exampleBodyText, $this->bodyTextReceived ); - } - - /** - * use a callback stream to pipe the mocked response data to our callback function - */ - public function testSuccessSink() { - $this->bodyTextReceived = ''; - $handler = HandlerStack::create( new MockHandler( [ new Response( 200, [ - 'status' => 200, - ], $this->exampleBodyText ) ] ) ); - $r = new GuzzleHttpRequest( $this->exampleUrl, [ - 'handler' => $handler, - 'sink' => new MWCallbackStream( [ $this, 'processHttpDataChunk' ] ), - ] ); - $r->execute(); - - $this->assertEquals( 200, $r->getStatus() ); - $this->assertEquals( $this->exampleBodyText, $this->bodyTextReceived ); - } - - public function testBadUrl() { - $r = new GuzzleHttpRequest( '' ); - $s = $r->execute(); - $errorMsg = $s->getErrorsByType( 'error' )[0]['message']; - - $this->assertEquals( 0, $r->getStatus() ); - $this->assertEquals( 'http-invalid-url', $errorMsg ); - } - - public function testConnectException() { - $handler = HandlerStack::create( new MockHandler( [ new GuzzleHttp\Exception\ConnectException( - 'Mock Connection Exception', new Request( 'GET', $this->exampleUrl ) - ) ] ) ); - $r = new GuzzleHttpRequest( $this->exampleUrl, [ 'handler' => $handler ] ); - $s = $r->execute(); - $errorMsg = $s->getErrorsByType( 'error' )[0]['message']; - - $this->assertEquals( 0, $r->getStatus() ); - $this->assertEquals( 'http-request-error', $errorMsg ); - } - - public function testTimeout() { - $handler = HandlerStack::create( new MockHandler( [ new GuzzleHttp\Exception\RequestException( - 'Connection timed out', new Request( 'GET', $this->exampleUrl ) - ) ] ) ); - $r = new GuzzleHttpRequest( $this->exampleUrl, [ 'handler' => $handler ] ); - $s = $r->execute(); - $errorMsg = $s->getErrorsByType( 'error' )[0]['message']; - - $this->assertEquals( 0, $r->getStatus() ); - $this->assertEquals( 'http-timed-out', $errorMsg ); - } - - public function testNotFound() { - $handler = HandlerStack::create( new MockHandler( [ new Response( 404, [ - 'status' => '404', - ] ) ] ) ); - $r = new GuzzleHttpRequest( $this->exampleUrl, [ 'handler' => $handler ] ); - $s = $r->execute(); - $errorMsg = $s->getErrorsByType( 'error' )[0]['message']; - - $this->assertEquals( 404, $r->getStatus() ); - $this->assertEquals( 'http-bad-status', $errorMsg ); - } -} diff --git a/tests/phpunit/includes/http/HttpRequestFactoryTest.php b/tests/phpunit/includes/http/HttpRequestFactoryTest.php deleted file mode 100644 index 7429dcc9dd..0000000000 --- a/tests/phpunit/includes/http/HttpRequestFactoryTest.php +++ /dev/null @@ -1,119 +0,0 @@ -getMockBuilder( HttpRequestFactory::class ) - ->setMethods( [ 'create' ] ) - ->getMock(); - - $factory->method( 'create' ) - ->willReturnCallback( - function ( $url, array $options = [], $caller = __METHOD__ ) - use ( $req, $expectedUrl, $expectedOptions ) - { - $this->assertSame( $url, $expectedUrl ); - - foreach ( $expectedOptions as $opt => $exp ) { - $this->assertArrayHasKey( $opt, $options ); - $this->assertSame( $exp, $options[$opt] ); - } - - return $req; - } - ); - - return $factory; - } - - /** - * @return MWHttpRequest - */ - private function newFakeRequest( $result ) { - $req = $this->getMockBuilder( MWHttpRequest::class ) - ->disableOriginalConstructor() - ->setMethods( [ 'getContent', 'execute' ] ) - ->getMock(); - - if ( $result instanceof Status ) { - $req->method( 'getContent' ) - ->willReturn( $result->getValue() ); - $req->method( 'execute' ) - ->willReturn( $result ); - } else { - $req->method( 'getContent' ) - ->willReturn( $result ); - $req->method( 'execute' ) - ->willReturn( Status::newGood( $result ) ); - } - - return $req; - } - - public function testCreate() { - $factory = $this->newFactory(); - $this->assertInstanceOf( 'MWHttpRequest', $factory->create( 'http://example.test' ) ); - } - - public function testGetUserAgent() { - $factory = $this->newFactory(); - $this->assertStringStartsWith( 'MediaWiki/', $factory->getUserAgent() ); - } - - public function testGet() { - $req = $this->newFakeRequest( __METHOD__ ); - $factory = $this->newFactoryWithFakeRequest( - $req, 'https://example.test', [ 'method' => 'GET' ] - ); - - $this->assertSame( __METHOD__, $factory->get( 'https://example.test' ) ); - } - - public function testPost() { - $req = $this->newFakeRequest( __METHOD__ ); - $factory = $this->newFactoryWithFakeRequest( - $req, 'https://example.test', [ 'method' => 'POST' ] - ); - - $this->assertSame( __METHOD__, $factory->post( 'https://example.test' ) ); - } - - public function testRequest() { - $req = $this->newFakeRequest( __METHOD__ ); - $factory = $this->newFactoryWithFakeRequest( - $req, 'https://example.test', [ 'method' => 'GET' ] - ); - - $this->assertSame( __METHOD__, $factory->request( 'GET', 'https://example.test' ) ); - } - - public function testRequest_failed() { - $status = Status::newFatal( 'testing' ); - $req = $this->newFakeRequest( $status ); - $factory = $this->newFactoryWithFakeRequest( - $req, 'https://example.test', [ 'method' => 'POST' ] - ); - - $this->assertNull( $factory->request( 'POST', 'https://example.test' ) ); - } - -} diff --git a/tests/phpunit/includes/installer/InstallDocFormatterTest.php b/tests/phpunit/includes/installer/InstallDocFormatterTest.php deleted file mode 100644 index 9584d4b8c4..0000000000 --- a/tests/phpunit/includes/installer/InstallDocFormatterTest.php +++ /dev/null @@ -1,83 +0,0 @@ -assertEquals( - $expected, - InstallDocFormatter::format( $unformattedText ), - $message - ); - } - - /** - * Provider for testFormat() - */ - public static function provideDocFormattingTests() { - # Format: (expected string, unformattedText string, optional message) - return [ - # Escape some wikitext - [ 'Install <tag>', 'Install ', 'Escaping <' ], - [ 'Install {{template}}', 'Install {{template}}', 'Escaping [[' ], - [ 'Install [[page]]', 'Install [[page]]', 'Escaping {{' ], - [ 'Install __TOC__', 'Install __TOC__', 'Escaping __' ], - [ 'Install ', "Install \r", 'Removing \r' ], - - # Transform \t{1,2} into :{1,2} - [ ':One indentation', "\tOne indentation", 'Replacing a single \t' ], - [ '::Two indentations', "\t\tTwo indentations", 'Replacing 2 x \t' ], - - # Transform 'T123' links - [ - '[https://phabricator.wikimedia.org/T123 T123]', - 'T123', 'Testing T123 links' ], - [ - 'bug [https://phabricator.wikimedia.org/T123 T123]', - 'bug T123', 'Testing bug T123 links' ], - [ - '([https://phabricator.wikimedia.org/T987654 T987654])', - '(T987654)', 'Testing (T987654) links' ], - - # "Tabc" shouldn't work - [ 'Tfoobar', 'Tfoobar', "Don't match T followed by non-digits" ], - [ 'T!!fakefake!!', 'T!!fakefake!!', "Don't match T followed by non-digits" ], - - # Transform 'bug 123' links - [ - '[https://bugzilla.wikimedia.org/123 bug 123]', - 'bug 123', 'Testing bug 123 links' ], - [ - '([https://bugzilla.wikimedia.org/987654 bug 987654])', - '(bug 987654)', 'Testing (bug 987654) links' ], - - # "bug abc" shouldn't work - [ 'bug foobar', 'bug foobar', "Don't match bug followed by non-digits" ], - [ 'bug !!fakefake!!', 'bug !!fakefake!!', "Don't match bug followed by non-digits" ], - - # Transform '$wgFooBar' links - [ - '' - . '[https://www.mediawiki.org/wiki/Manual:$wgFooBar $wgFooBar]', - '$wgFooBar', 'Testing basic $wgFooBar' ], - [ - '' - . '[https://www.mediawiki.org/wiki/Manual:$wgFooBar45 $wgFooBar45]', - '$wgFooBar45', 'Testing $wgFooBar45 (with numbers)' ], - [ - '' - . '[https://www.mediawiki.org/wiki/Manual:$wgFoo_Bar $wgFoo_Bar]', - '$wgFoo_Bar', 'Testing $wgFoo_Bar (with underscore)' ], - - # Icky variables that shouldn't link - [ - '$myAwesomeVariable', - '$myAwesomeVariable', - 'Testing $myAwesomeVariable (not starting with $wg)' - ], - [ '$()not!a&Var', '$()not!a&Var', 'Testing $()not!a&Var (obviously not a variable)' ], - ]; - } -} diff --git a/tests/phpunit/includes/installer/OracleInstallerTest.php b/tests/phpunit/includes/installer/OracleInstallerTest.php deleted file mode 100644 index e255089de4..0000000000 --- a/tests/phpunit/includes/installer/OracleInstallerTest.php +++ /dev/null @@ -1,49 +0,0 @@ -assertEquals( $expected, - OracleInstaller::checkConnectStringFormat( $connectString ), - $msg - ); - } - - /** - * Provider to test OracleInstaller::checkConnectStringFormat() - */ - function provideOracleConnectStrings() { - // expected result, connectString[, message] - return [ - [ true, 'simple_01', 'Simple TNS name' ], - [ true, 'simple_01.world', 'TNS name with domain' ], - [ true, 'simple_01.domain.net', 'TNS name with domain' ], - [ true, 'host123', 'Host only' ], - [ true, 'host123.domain.net', 'FQDN only' ], - [ true, '//host123.domain.net', 'FQDN URL only' ], - [ true, '123.223.213.132', 'Host IP only' ], - [ true, 'host:1521', 'Host and port' ], - [ true, 'host:1521/service', 'Host, port and service' ], - [ true, 'host:1521/service:shared', 'Host, port, service and shared server type' ], - [ true, 'host:1521/service:dedicated', 'Host, port, service and dedicated server type' ], - [ true, 'host:1521/service:pooled', 'Host, port, service and pooled server type' ], - [ - true, - 'host:1521/service:shared/instance1', - 'Host, port, service, server type and instance' - ], - [ true, 'host:1521//instance1', 'Host, port and instance' ], - ]; - } - -} diff --git a/tests/phpunit/includes/interwiki/InterwikiLookupAdapterTest.php b/tests/phpunit/includes/interwiki/InterwikiLookupAdapterTest.php deleted file mode 100644 index 0a13de1d96..0000000000 --- a/tests/phpunit/includes/interwiki/InterwikiLookupAdapterTest.php +++ /dev/null @@ -1,133 +0,0 @@ -interwikiLookup = new InterwikiLookupAdapter( - $this->getSiteLookup( $this->getSites() ) - ); - } - - public function testIsValidInterwiki() { - $this->assertTrue( - $this->interwikiLookup->isValidInterwiki( 'enwt' ), - 'enwt known prefix is valid' - ); - $this->assertTrue( - $this->interwikiLookup->isValidInterwiki( 'foo' ), - 'foo site known prefix is valid' - ); - $this->assertFalse( - $this->interwikiLookup->isValidInterwiki( 'xyz' ), - 'unknown prefix is not valid' - ); - } - - public function testFetch() { - $interwiki = $this->interwikiLookup->fetch( '' ); - $this->assertNull( $interwiki ); - - $interwiki = $this->interwikiLookup->fetch( 'xyz' ); - $this->assertFalse( $interwiki ); - - $interwiki = $this->interwikiLookup->fetch( 'foo' ); - $this->assertInstanceOf( Interwiki::class, $interwiki ); - $this->assertSame( 'foobar', $interwiki->getWikiID() ); - - $interwiki = $this->interwikiLookup->fetch( 'enwt' ); - $this->assertInstanceOf( Interwiki::class, $interwiki ); - - $this->assertSame( 'https://en.wiktionary.org/wiki/$1', $interwiki->getURL(), 'getURL' ); - $this->assertSame( 'https://en.wiktionary.org/w/api.php', $interwiki->getAPI(), 'getAPI' ); - $this->assertSame( 'enwiktionary', $interwiki->getWikiID(), 'getWikiID' ); - $this->assertTrue( $interwiki->isLocal(), 'isLocal' ); - } - - public function testGetAllPrefixes() { - $foo = [ - 'iw_prefix' => 'foo', - 'iw_url' => '', - 'iw_api' => '', - 'iw_wikiid' => 'foobar', - 'iw_local' => false, - 'iw_trans' => false, - ]; - $enwt = [ - 'iw_prefix' => 'enwt', - 'iw_url' => 'https://en.wiktionary.org/wiki/$1', - 'iw_api' => 'https://en.wiktionary.org/w/api.php', - 'iw_wikiid' => 'enwiktionary', - 'iw_local' => true, - 'iw_trans' => false, - ]; - - $this->assertEquals( - [ $foo, $enwt ], - $this->interwikiLookup->getAllPrefixes(), - 'getAllPrefixes()' - ); - - $this->assertEquals( - [ $foo ], - $this->interwikiLookup->getAllPrefixes( false ), - 'get external prefixes' - ); - - $this->assertEquals( - [ $enwt ], - $this->interwikiLookup->getAllPrefixes( true ), - 'get local prefixes' - ); - } - - private function getSiteLookup( SiteList $sites ) { - $siteLookup = $this->getMockBuilder( SiteLookup::class ) - ->disableOriginalConstructor() - ->getMock(); - - $siteLookup->expects( $this->any() ) - ->method( 'getSites' ) - ->will( $this->returnValue( $sites ) ); - - return $siteLookup; - } - - private function getSites() { - $sites = []; - - $site = new Site(); - $site->setGlobalId( 'foobar' ); - $site->addInterwikiId( 'foo' ); - $site->setSource( 'external' ); - $sites[] = $site; - - $site = new MediaWikiSite(); - $site->setGlobalId( 'enwiktionary' ); - $site->setGroup( 'wiktionary' ); - $site->setLanguageCode( 'en' ); - $site->addNavigationId( 'enwiktionary' ); - $site->addInterwikiId( 'enwt' ); - $site->setSource( 'local' ); - $site->setPath( MediaWikiSite::PATH_PAGE, "https://en.wiktionary.org/wiki/$1" ); - $site->setPath( MediaWikiSite::PATH_FILE, "https://en.wiktionary.org/w/$1" ); - $sites[] = $site; - - return new SiteList( $sites ); - } - -} diff --git a/tests/phpunit/includes/jobqueue/JobQueueMemoryTest.php b/tests/phpunit/includes/jobqueue/JobQueueMemoryTest.php deleted file mode 100644 index 232b46a31b..0000000000 --- a/tests/phpunit/includes/jobqueue/JobQueueMemoryTest.php +++ /dev/null @@ -1,63 +0,0 @@ - JobQueueMemory::class, - 'domain' => WikiMap::getCurrentWikiDbDomain()->getId(), - 'type' => 'null', - ] ); - } - - private function newJobSpecification() { - return new JobSpecification( - 'null', - [ 'customParameter' => null ], - [], - Title::newFromText( 'Custom title' ) - ); - } - - public function testGetAllQueuedJobs() { - $queue = $this->newJobQueue(); - $this->assertCount( 0, $queue->getAllQueuedJobs() ); - - $queue->push( $this->newJobSpecification() ); - $this->assertCount( 1, $queue->getAllQueuedJobs() ); - } - - public function testGetAllAcquiredJobs() { - $queue = $this->newJobQueue(); - $this->assertCount( 0, $queue->getAllAcquiredJobs() ); - - $queue->push( $this->newJobSpecification() ); - $this->assertCount( 0, $queue->getAllAcquiredJobs() ); - - $queue->pop(); - $this->assertCount( 1, $queue->getAllAcquiredJobs() ); - } - - public function testJobFromSpecInternal() { - $queue = $this->newJobQueue(); - $job = $queue->jobFromSpecInternal( $this->newJobSpecification() ); - $this->assertInstanceOf( Job::class, $job ); - $this->assertSame( 'null', $job->getType() ); - $this->assertArrayHasKey( 'customParameter', $job->getParams() ); - $this->assertSame( 'Custom title', $job->getTitle()->getText() ); - } - -} diff --git a/tests/phpunit/includes/json/FormatJsonTest.php b/tests/phpunit/includes/json/FormatJsonTest.php deleted file mode 100644 index a6adf343d5..0000000000 --- a/tests/phpunit/includes/json/FormatJsonTest.php +++ /dev/null @@ -1,436 +0,0 @@ - new stdClass, - 'emptyArray' => [], - 'string' => 'foobar\\', - 'filledArray' => [ - [ - 123, - 456, - ], - // Nested json works without problems - '"7":["8",{"9":"10"}]', - // Whitespace clean up doesn't touch strings that look alike - "{\n\t\"emptyObject\": {\n\t},\n\t\"emptyArray\": [ ]\n}", - ], - ]; - - // No trailing whitespace, no trailing linefeed - $json = '{ - "emptyObject": {}, - "emptyArray": [], - "string": "foobar\\\\", - "filledArray": [ - [ - 123, - 456 - ], - "\"7\":[\"8\",{\"9\":\"10\"}]", - "{\n\t\"emptyObject\": {\n\t},\n\t\"emptyArray\": [ ]\n}" - ] -}'; - - $json = str_replace( "\r", '', $json ); // Windows compat - $json = str_replace( "\t", $expectedIndent, $json ); - $this->assertSame( $json, FormatJson::encode( $obj, $pretty ) ); - } - - public static function provideEncodeDefault() { - return self::getEncodeTestCases( [] ); - } - - /** - * @dataProvider provideEncodeDefault - */ - public function testEncodeDefault( $from, $to ) { - $this->assertSame( $to, FormatJson::encode( $from ) ); - } - - public static function provideEncodeUtf8() { - return self::getEncodeTestCases( [ 'unicode' ] ); - } - - /** - * @dataProvider provideEncodeUtf8 - */ - public function testEncodeUtf8( $from, $to ) { - $this->assertSame( $to, FormatJson::encode( $from, false, FormatJson::UTF8_OK ) ); - } - - public static function provideEncodeXmlMeta() { - return self::getEncodeTestCases( [ 'xmlmeta' ] ); - } - - /** - * @dataProvider provideEncodeXmlMeta - */ - public function testEncodeXmlMeta( $from, $to ) { - $this->assertSame( $to, FormatJson::encode( $from, false, FormatJson::XMLMETA_OK ) ); - } - - public static function provideEncodeAllOk() { - return self::getEncodeTestCases( [ 'unicode', 'xmlmeta' ] ); - } - - /** - * @dataProvider provideEncodeAllOk - */ - public function testEncodeAllOk( $from, $to ) { - $this->assertSame( $to, FormatJson::encode( $from, false, FormatJson::ALL_OK ) ); - } - - public function testEncodePhpBug46944() { - $this->assertNotEquals( - '\ud840\udc00', - strtolower( FormatJson::encode( "\xf0\xa0\x80\x80" ) ), - 'Test encoding an broken json_encode character (U+20000)' - ); - } - - public function testEncodeFail() { - // Set up a recursive object that can't be encoded. - $a = new stdClass; - $b = new stdClass; - $a->b = $b; - $b->a = $a; - $this->assertFalse( FormatJson::encode( $a ) ); - } - - public function testDecodeReturnType() { - $this->assertInternalType( - 'object', - FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}' ), - 'Default to object' - ); - - $this->assertInternalType( - 'array', - FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}', true ), - 'Optional array' - ); - } - - public static function provideParse() { - return [ - [ null ], - [ true ], - [ false ], - [ 0 ], - [ 1 ], - [ 1.2 ], - [ '' ], - [ 'str' ], - [ [ 0, 1, 2 ] ], - [ [ 'a' => 'b' ] ], - [ [ 'a' => 'b' ] ], - [ [ 'a' => 'b', 'x' => [ 'c' => 'd' ] ] ], - ]; - } - - /** - * Recursively convert arrays into stdClass - * @param array|string|bool|int|float|null $value - * @return stdClass|string|bool|int|float|null - */ - public static function toObject( $value ) { - return !is_array( $value ) ? $value : (object)array_map( __METHOD__, $value ); - } - - /** - * @dataProvider provideParse - * @param mixed $value - */ - public function testParse( $value ) { - $expected = self::toObject( $value ); - $json = FormatJson::encode( $expected, false, FormatJson::ALL_OK ); - $this->assertJson( $json ); - - $st = FormatJson::parse( $json ); - $this->assertInstanceOf( Status::class, $st ); - $this->assertTrue( $st->isGood() ); - $this->assertEquals( $expected, $st->getValue() ); - - $st = FormatJson::parse( $json, FormatJson::FORCE_ASSOC ); - $this->assertInstanceOf( Status::class, $st ); - $this->assertTrue( $st->isGood() ); - $this->assertEquals( $value, $st->getValue() ); - } - - /** - * 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 ); - } - } - - public static function provideParseErrors() { - return [ - [ 'aaa' ], - [ '{"j": 1 ] }' ], - ]; - } - - /** - * @dataProvider provideParseErrors - * @param mixed $value - */ - public function testParseErrors( $value ) { - $st = FormatJson::parse( $value ); - $this->assertInstanceOf( Status::class, $st ); - $this->assertFalse( $st->isOK() ); - } - - public function provideStripComments() { - return [ - [ '{"a":"b"}', '{"a":"b"}' ], - [ "{\"a\":\"b\"}\n", "{\"a\":\"b\"}\n" ], - [ '/*c*/{"c":"b"}', '{"c":"b"}' ], - [ '{"a":"c"}/*c*/', '{"a":"c"}' ], - [ '/*c//d*/{"c":"b"}', '{"c":"b"}' ], - [ '{/*c*/"c":"b"}', '{"c":"b"}' ], - [ "/*\nc\r\n*/{\"c\":\"b\"}", '{"c":"b"}' ], - [ "//c\n{\"c\":\"b\"}", '{"c":"b"}' ], - [ "//c\r\n{\"c\":\"b\"}", '{"c":"b"}' ], - [ '{"a":"c"}//c', '{"a":"c"}' ], - [ "{\"a-c\"://c\n\"b\"}", '{"a-c":"b"}' ], - [ '{"/*a":"b"}', '{"/*a":"b"}' ], - [ '{"a":"//b"}', '{"a":"//b"}' ], - [ '{"a":"b/*c*/"}', '{"a":"b/*c*/"}' ], - [ "{\"\\\"/*a\":\"b\"}", "{\"\\\"/*a\":\"b\"}" ], - [ '', '' ], - [ '/*c', '' ], - [ '//c', '' ], - [ '"http://example.com"', '"http://example.com"' ], - [ "\0", "\0" ], - [ '"Blåbærsyltetøy"', '"Blåbærsyltetøy"' ], - ]; - } - - /** - * @covers FormatJson::stripComments - * @dataProvider provideStripComments - * @param string $json - * @param string $expect - */ - public function testStripComments( $json, $expect ) { - $this->assertSame( $expect, FormatJson::stripComments( $json ) ); - } - - public function provideParseStripComments() { - return [ - [ '/* blah */true', true ], - [ "// blah \ntrue", true ], - [ '[ "a" , /* blah */ "b" ]', [ 'a', 'b' ] ], - ]; - } - - /** - * @covers FormatJson::parse - * @covers FormatJson::stripComments - * @dataProvider provideParseStripComments - * @param string $json - * @param mixed $expect - */ - public function testParseStripComments( $json, $expect ) { - $st = FormatJson::parse( $json, FormatJson::STRIP_COMMENTS ); - $this->assertInstanceOf( Status::class, $st ); - $this->assertTrue( $st->isGood() ); - $this->assertEquals( $expect, $st->getValue() ); - } - - /** - * Generate a set of test cases for a particular combination of encoder options. - * - * @param array $unescapedGroups List of character groups to leave unescaped - * @return array Arrays of unencoded strings and corresponding encoded strings - */ - private static function getEncodeTestCases( array $unescapedGroups ) { - $groups = [ - 'always' => [ - // Forward slash (always unescaped) - '/' => '/', - - // Control characters - "\0" => '\u0000', - "\x08" => '\b', - "\t" => '\t', - "\n" => '\n', - "\r" => '\r', - "\f" => '\f', - "\x1f" => '\u001f', // representative example - - // Double quotes - '"' => '\"', - - // Backslashes - '\\' => '\\\\', - '\\\\' => '\\\\\\\\', - '\\u00e9' => '\\\u00e9', // security check for Unicode unescaping - - // Line terminators - "\xe2\x80\xa8" => '\u2028', - "\xe2\x80\xa9" => '\u2029', - ], - 'unicode' => [ - "\xc3\xa9" => '\u00e9', - "\xf0\x9d\x92\x9e" => '\ud835\udc9e', // U+1D49E, outside the BMP - ], - 'xmlmeta' => [ - '<' => '\u003C', // JSON_HEX_TAG uses uppercase hex digits - '>' => '\u003E', - '&' => '\u0026', - ], - ]; - - $cases = []; - foreach ( $groups as $name => $rules ) { - $leaveUnescaped = in_array( $name, $unescapedGroups ); - foreach ( $rules as $from => $to ) { - $cases[] = [ $from, '"' . ( $leaveUnescaped ? $from : $to ) . '"' ]; - } - } - - return $cases; - } - - public function provideEmptyJsonKeyStrings() { - return [ - [ - '{"":"foo"}', - '{"":"foo"}', - '' - ], - [ - '{"_empty_":"foo"}', - '{"_empty_":"foo"}', - '_empty_' ], - [ - '{"\u005F\u0065\u006D\u0070\u0074\u0079\u005F":"foo"}', - '{"_empty_":"foo"}', - '_empty_' - ], - [ - '{"_empty_":"bar","":"foo"}', - '{"_empty_":"bar","":"foo"}', - '' - ], - [ - '{"":"bar","_empty_":"foo"}', - '{"":"bar","_empty_":"foo"}', - '_empty_' - ] - ]; - } - - /** - * @covers FormatJson::encode - * @covers FormatJson::decode - * @dataProvider provideEmptyJsonKeyStrings - * @param string $json - * - * Decoding behavior with empty keys can be surprising. - * See https://phabricator.wikimedia.org/T206411 - */ - public function testEmptyJsonKeyArray( $json, $expect, $php71Name ) { - // Decoding to array is consistent across supported PHP versions - $this->assertSame( $expect, FormatJson::encode( - FormatJson::decode( $json, true ) ) ); - - // Decoding to object differs between supported PHP versions - $obj = FormatJson::decode( $json ); - if ( version_compare( PHP_VERSION, '7.1', '<' ) ) { - $this->assertEquals( 'foo', $obj->_empty_ ); - } else { - $this->assertEquals( 'foo', $obj->{$php71Name} ); - } - } -} diff --git a/tests/phpunit/includes/libs/ArrayUtilsTest.php b/tests/phpunit/includes/libs/ArrayUtilsTest.php deleted file mode 100644 index 12b6320564..0000000000 --- a/tests/phpunit/includes/libs/ArrayUtilsTest.php +++ /dev/null @@ -1,308 +0,0 @@ -assertSame( - ArrayUtils::findLowerBound( - $valueCallback, $valueCount, $comparisonCallback, $target - ), $expected - ); - } - - function provideFindLowerBound() { - $indexValueCallback = function ( $size ) { - return function ( $val ) use ( $size ) { - $this->assertTrue( $val >= 0 ); - $this->assertTrue( $val < $size ); - return $val; - }; - }; - $comparisonCallback = function ( $a, $b ) { - return $a - $b; - }; - - return [ - [ - $indexValueCallback( 0 ), - 0, - $comparisonCallback, - 1, - false, - ], - [ - $indexValueCallback( 1 ), - 1, - $comparisonCallback, - -1, - false, - ], - [ - $indexValueCallback( 1 ), - 1, - $comparisonCallback, - 0, - 0, - ], - [ - $indexValueCallback( 1 ), - 1, - $comparisonCallback, - 1, - 0, - ], - [ - $indexValueCallback( 2 ), - 2, - $comparisonCallback, - -1, - false, - ], - [ - $indexValueCallback( 2 ), - 2, - $comparisonCallback, - 0, - 0, - ], - [ - $indexValueCallback( 2 ), - 2, - $comparisonCallback, - 0.5, - 0, - ], - [ - $indexValueCallback( 2 ), - 2, - $comparisonCallback, - 1, - 1, - ], - [ - $indexValueCallback( 2 ), - 2, - $comparisonCallback, - 1.5, - 1, - ], - [ - $indexValueCallback( 3 ), - 3, - $comparisonCallback, - 1, - 1, - ], - [ - $indexValueCallback( 3 ), - 3, - $comparisonCallback, - 1.5, - 1, - ], - [ - $indexValueCallback( 3 ), - 3, - $comparisonCallback, - 2, - 2, - ], - [ - $indexValueCallback( 3 ), - 3, - $comparisonCallback, - 3, - 2, - ], - ]; - } - - /** - * @covers ArrayUtils::arrayDiffAssocRecursive - * @dataProvider provideArrayDiffAssocRecursive - */ - function testArrayDiffAssocRecursive( $expected, ...$args ) { - $this->assertEquals( call_user_func_array( - 'ArrayUtils::arrayDiffAssocRecursive', $args - ), $expected ); - } - - function provideArrayDiffAssocRecursive() { - return [ - [ - [], - [], - [], - ], - [ - [], - [], - [], - [], - ], - [ - [ 1 ], - [ 1 ], - [], - ], - [ - [ 1 ], - [ 1 ], - [], - [], - ], - [ - [], - [], - [ 1 ], - ], - [ - [], - [], - [ 1 ], - [ 2 ], - ], - [ - [ '' => 1 ], - [ '' => 1 ], - [], - ], - [ - [], - [], - [ '' => 1 ], - ], - [ - [ 1 ], - [ 1 ], - [ 2 ], - ], - [ - [], - [ 1 ], - [ 2 ], - [ 1 ], - ], - [ - [], - [ 1 ], - [ 1, 2 ], - ], - [ - [ 1 => 1 ], - [ 1 => 1 ], - [ 1 ], - ], - [ - [], - [ 1 => 1 ], - [ 1 ], - [ 1 => 1 ], - ], - [ - [], - [ 1 => 1 ], - [ 1, 1, 1 ], - ], - [ - [], - [ [] ], - [], - ], - [ - [], - [ [ [] ] ], - [], - ], - [ - [ 1, [ 1 ] ], - [ 1, [ 1 ] ], - [], - ], - [ - [ 1 ], - [ 1, [ 1 ] ], - [ 2, [ 1 ] ], - ], - [ - [], - [ 1, [ 1 ] ], - [ 2, [ 1 ] ], - [ 1, [ 2 ] ], - ], - [ - [ 1 ], - [ 1, [] ], - [ 2 ], - ], - [ - [], - [ 1, [] ], - [ 2 ], - [ 1 ], - ], - [ - [ 1, [ 1 => 2 ] ], - [ 1, [ 1, 2 ] ], - [ 2, [ 1 ] ], - ], - [ - [ 1 ], - [ 1, [ 1, 2 ] ], - [ 2, [ 1 ] ], - [ 2, [ 1 => 2 ] ], - ], - [ - [ 1 => [ 1, 2 ] ], - [ 1, [ 1, 2 ] ], - [ 1, [ 2 ] ], - ], - [ - [ 1 => [ [ 2, 3 ], 2 ] ], - [ 1, [ [ 2, 3 ], 2 ] ], - [ 1, [ 2 ] ], - ], - [ - [ 1 => [ [ 2 ], 2 ] ], - [ 1, [ [ 2, 3 ], 2 ] ], - [ 1, [ [ 1 => 3 ] ] ], - ], - [ - [ 1 => [ 1 => 2 ] ], - [ 1, [ [ 2, 3 ], 2 ] ], - [ 1, [ [ 1 => 3, 0 => 2 ] ] ], - ], - [ - [ 1 => [ 1 => 2 ] ], - [ 1, [ [ 2, 3 ], 2 ] ], - [ 1, [ [ 1 => 3 ] ] ], - [ 1 => [ [ 2 ] ] ], - ], - [ - [], - [ 1, [ [ 2, 3 ], 2 ] ], - [ 1 => [ 1 => 2, 0 => [ 1 => 3, 0 => 2 ] ], 0 => 1 ], - ], - [ - [], - [ 1, [ [ 2, 3 ], 2 ] ], - [ 1 => [ 1 => 2 ] ], - [ 1 => [ [ 1 => 3 ] ] ], - [ 1 => [ [ 2 ] ] ], - [ 1 ], - ], - ]; - } -} diff --git a/tests/phpunit/includes/libs/CookieTest.php b/tests/phpunit/includes/libs/CookieTest.php deleted file mode 100644 index e383be9650..0000000000 --- a/tests/phpunit/includes/libs/CookieTest.php +++ /dev/null @@ -1,52 +0,0 @@ -assertEquals( $expected, $ok, $msg ); - } - - public static function cookieDomains() { - return [ - [ false, "org" ], - [ false, ".org" ], - [ true, "wikipedia.org" ], - [ true, ".wikipedia.org" ], - [ false, "co.uk" ], - [ false, ".co.uk" ], - [ false, "gov.uk" ], - [ false, ".gov.uk" ], - [ true, "supermarket.uk" ], - [ false, "uk" ], - [ false, ".uk" ], - [ false, "127.0.0." ], - [ false, "127." ], - [ false, "127.0.0.1." ], - [ true, "127.0.0.1" ], - [ false, "333.0.0.1" ], - [ true, "example.com" ], - [ false, "example.com." ], - [ true, ".example.com" ], - - [ true, ".example.com", "www.example.com" ], - [ false, "example.com", "www.example.com" ], - [ true, "127.0.0.1", "127.0.0.1" ], - [ false, "127.0.0.1", "localhost" ], - ]; - } - -} diff --git a/tests/phpunit/includes/libs/DeferredStringifierTest.php b/tests/phpunit/includes/libs/DeferredStringifierTest.php deleted file mode 100644 index c9cdf5831d..0000000000 --- a/tests/phpunit/includes/libs/DeferredStringifierTest.php +++ /dev/null @@ -1,54 +0,0 @@ -newInstanceArgs( $params ); - $this->assertEquals( $expected, (string)$ds ); - } - - public static function provideToString() { - return [ - // No args - [ - [ - function () { - return 'foo'; - } - ], - 'foo' - ], - // Has args - [ - [ - function ( $i ) { - return $i; - }, - 'bar' - ], - 'bar' - ], - ]; - } - - /** - * Verify that the callback is not called if - * it is never converted to a string - */ - public function testCallbackNotCalled() { - $ds = new DeferredStringifier( function () { - throw new Exception( 'This should not be reached!' ); - } ); - // No exception was thrown - $this->assertTrue( true ); - } -} diff --git a/tests/phpunit/includes/libs/DnsSrvDiscovererTest.php b/tests/phpunit/includes/libs/DnsSrvDiscovererTest.php deleted file mode 100644 index 1b3397c12a..0000000000 --- a/tests/phpunit/includes/libs/DnsSrvDiscovererTest.php +++ /dev/null @@ -1,144 +0,0 @@ -pickServer( $params ); - - $this->assertEquals( $expected, $record ); - } - - public static function provideRecords() { - return [ - [ - [ // record list - [ - 'target' => 'conf03.example.net', - 'port' => 'SRV', - 'pri' => 0, - 'weight' => 1, - ], - [ - 'target' => 'conf02.example.net', - 'port' => 'SRV', - 'pri' => 1, - 'weight' => 1, - ], - [ - 'target' => 'conf01.example.net', - 'port' => 'SRV', - 'pri' => 2, - 'weight' => 1, - ], - ], // selected record - [ - 'target' => 'conf03.example.net', - 'port' => 'SRV', - 'pri' => 0, - 'weight' => 1, - ] - ], - [ - [ // record list - [ - 'target' => 'conf03or2.example.net', - 'port' => 'SRV', - 'pri' => 0, - 'weight' => 1, - ], - [ - 'target' => 'conf03or2.example.net', - 'port' => 'SRV', - 'pri' => 0, - 'weight' => 1, - ], - [ - 'target' => 'conf01.example.net', - 'port' => 'SRV', - 'pri' => 2, - 'weight' => 1, - ], - [ - 'target' => 'conf04.example.net', - 'port' => 'SRV', - 'pri' => 2, - 'weight' => 1, - ], - [ - 'target' => 'conf05.example.net', - 'port' => 'SRV', - 'pri' => 3, - 'weight' => 1, - ], - ], // selected record - [ - 'target' => 'conf03or2.example.net', - 'port' => 'SRV', - 'pri' => 0, - 'weight' => 1, - ] - ], - ]; - } - - public function testRemoveServer() { - $dsd = new DnsSrvDiscoverer( 'localhost' ); - - $servers = [ - [ - 'target' => 'conf01.example.net', - 'port' => 35, - 'pri' => 2, - 'weight' => 1, - ], - [ - 'target' => 'conf04.example.net', - 'port' => 74, - 'pri' => 2, - 'weight' => 1, - ], - [ - 'target' => 'conf05.example.net', - 'port' => 77, - 'pri' => 3, - 'weight' => 1, - ], - ]; - $server = $servers[1]; - - $expected = [ - [ - 'target' => 'conf01.example.net', - 'port' => 35, - 'pri' => 2, - 'weight' => 1, - ], - [ - 'target' => 'conf05.example.net', - 'port' => 77, - 'pri' => 3, - 'weight' => 1, - ], - ]; - - $this->assertEquals( - $expected, - $dsd->removeServer( $server, $servers ), - "Correct server removed" - ); - $this->assertEquals( - $expected, - $dsd->removeServer( $server, $servers ), - "Nothing to remove" - ); - } -} diff --git a/tests/phpunit/includes/libs/EasyDeflateTest.php b/tests/phpunit/includes/libs/EasyDeflateTest.php deleted file mode 100644 index da39d48d90..0000000000 --- a/tests/phpunit/includes/libs/EasyDeflateTest.php +++ /dev/null @@ -1,64 +0,0 @@ - - * - * 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. - * - */ - -/** - * @covers EasyDeflate - */ -class EasyDeflateTest extends PHPUnit\Framework\TestCase { - - public function provideIsDeflated() { - return [ - [ 'rawdeflate,S8vPT0osAgA=', true ], - [ 'abcdefghijklmnopqrstuvwxyz', false ], - ]; - } - - /** - * @dataProvider provideIsDeflated - */ - public function testIsDeflated( $data, $expected ) { - $actual = EasyDeflate::isDeflated( $data ); - $this->assertSame( $expected, $actual ); - } - - public function provideInflate() { - return [ - [ 'rawdeflate,S8vPT0osAgA=', true, 'foobar' ], - // Fails base64_decode - [ 'rawdeflate,🌻', false, 'easydeflate-invaliddeflate' ], - // Fails gzinflate - [ 'rawdeflate,S8vPT0dfdAgB=', false, 'easydeflate-invaliddeflate' ], - ]; - } - - /** - * @dataProvider provideInflate - */ - public function testInflate( $data, $ok, $value ) { - $actual = EasyDeflate::inflate( $data ); - if ( $ok ) { - $this->assertTrue( $actual->isOK() ); - $this->assertSame( $value, $actual->getValue() ); - } else { - $this->assertFalse( $actual->isOK() ); - $this->assertTrue( $actual->hasMessage( $value ) ); - } - } -} diff --git a/tests/phpunit/includes/libs/GenericArrayObjectTest.php b/tests/phpunit/includes/libs/GenericArrayObjectTest.php deleted file mode 100644 index 3be2b06465..0000000000 --- a/tests/phpunit/includes/libs/GenericArrayObjectTest.php +++ /dev/null @@ -1,279 +0,0 @@ - - */ -abstract class GenericArrayObjectTest extends PHPUnit\Framework\TestCase { - - use MediaWikiCoversValidator; - - /** - * Returns objects that can serve as elements in the concrete - * GenericArrayObject deriving class being tested. - * - * @since 1.20 - * - * @return array - */ - abstract public function elementInstancesProvider(); - - /** - * Returns the name of the concrete class being tested. - * - * @since 1.20 - * - * @return string - */ - abstract public function getInstanceClass(); - - /** - * Provides instances of the concrete class being tested. - * - * @since 1.20 - * - * @return array - */ - public function instanceProvider() { - $instances = []; - - foreach ( $this->elementInstancesProvider() as $elementInstances ) { - $instances[] = $this->getNew( $elementInstances[0] ); - } - - return $this->arrayWrap( $instances ); - } - - /** - * @since 1.20 - * - * @param array $elements - * - * @return GenericArrayObject - */ - protected function getNew( array $elements = [] ) { - $class = $this->getInstanceClass(); - - return new $class( $elements ); - } - - /** - * @dataProvider elementInstancesProvider - * - * @since 1.20 - * - * @param array $elements - * - * @covers GenericArrayObject::__construct - */ - public function testConstructor( array $elements ) { - $arrayObject = $this->getNew( $elements ); - - $this->assertEquals( count( $elements ), $arrayObject->count() ); - } - - /** - * @dataProvider elementInstancesProvider - * - * @since 1.20 - * - * @param array $elements - * - * @covers GenericArrayObject::isEmpty - */ - public function testIsEmpty( array $elements ) { - $arrayObject = $this->getNew( $elements ); - - $this->assertEquals( $elements === [], $arrayObject->isEmpty() ); - } - - /** - * @dataProvider instanceProvider - * - * @since 1.20 - * - * @param GenericArrayObject $list - * - * @covers GenericArrayObject::offsetUnset - */ - public function testUnset( GenericArrayObject $list ) { - if ( $list->isEmpty() ) { - $this->assertTrue( true ); // We cannot test unset if there are no elements - } else { - $offset = $list->getIterator()->key(); - $count = $list->count(); - $list->offsetUnset( $offset ); - $this->assertEquals( $count - 1, $list->count() ); - } - - if ( !$list->isEmpty() ) { - $offset = $list->getIterator()->key(); - $count = $list->count(); - unset( $list[$offset] ); - $this->assertEquals( $count - 1, $list->count() ); - } - } - - /** - * @dataProvider elementInstancesProvider - * - * @since 1.20 - * - * @param array $elements - * - * @covers GenericArrayObject::append - */ - public function testAppend( array $elements ) { - $list = $this->getNew(); - - $listSize = count( $elements ); - - foreach ( $elements as $element ) { - $list->append( $element ); - } - - $this->assertEquals( $listSize, $list->count() ); - - $list = $this->getNew(); - - foreach ( $elements as $element ) { - $list[] = $element; - } - - $this->assertEquals( $listSize, $list->count() ); - - $this->checkTypeChecks( function ( GenericArrayObject $list, $element ) { - $list->append( $element ); - } ); - } - - /** - * @since 1.20 - * - * @param callable $function - */ - protected function checkTypeChecks( $function ) { - $excption = null; - $list = $this->getNew(); - - $elementClass = $list->getObjectType(); - - foreach ( [ 42, 'foo', [], new stdClass(), 4.2 ] as $element ) { - $validValid = $element instanceof $elementClass; - - try { - call_user_func( $function, $list, $element ); - $valid = true; - } catch ( InvalidArgumentException $exception ) { - $valid = false; - } - - $this->assertEquals( - $validValid, - $valid, - 'Object of invalid type got successfully added to a GenericArrayObject' - ); - } - } - - /** - * @dataProvider elementInstancesProvider - * - * @since 1.20 - * - * @param array $elements - * @covers GenericArrayObject::getObjectType - * @covers GenericArrayObject::offsetSet - */ - public function testOffsetSet( array $elements ) { - if ( $elements === [] ) { - $this->assertTrue( true ); - - return; - } - - $list = $this->getNew(); - - $element = reset( $elements ); - $list->offsetSet( 42, $element ); - $this->assertEquals( $element, $list->offsetGet( 42 ) ); - - $list = $this->getNew(); - - $element = reset( $elements ); - $list['oHai'] = $element; - $this->assertEquals( $element, $list['oHai'] ); - - $list = $this->getNew(); - - $element = reset( $elements ); - $list->offsetSet( 9001, $element ); - $this->assertEquals( $element, $list[9001] ); - - $list = $this->getNew(); - - $element = reset( $elements ); - $list->offsetSet( null, $element ); - $this->assertEquals( $element, $list[0] ); - - $list = $this->getNew(); - $offset = 0; - - foreach ( $elements as $element ) { - $list->offsetSet( null, $element ); - $this->assertEquals( $element, $list[$offset++] ); - } - - $this->assertEquals( count( $elements ), $list->count() ); - - $this->checkTypeChecks( function ( GenericArrayObject $list, $element ) { - $list->offsetSet( mt_rand(), $element ); - } ); - } - - /** - * @dataProvider instanceProvider - * - * @since 1.21 - * - * @param GenericArrayObject $list - * - * @covers GenericArrayObject::getSerializationData - * @covers GenericArrayObject::serialize - * @covers GenericArrayObject::unserialize - */ - public function testSerialization( GenericArrayObject $list ) { - $serialization = serialize( $list ); - $copy = unserialize( $serialization ); - - $this->assertEquals( $serialization, serialize( $copy ) ); - $this->assertEquals( count( $list ), count( $copy ) ); - - $list = $list->getArrayCopy(); - $copy = $copy->getArrayCopy(); - - $this->assertArrayEquals( $list, $copy, true, true ); - } -} diff --git a/tests/phpunit/includes/libs/HashRingTest.php b/tests/phpunit/includes/libs/HashRingTest.php deleted file mode 100644 index acaeb02558..0000000000 --- a/tests/phpunit/includes/libs/HashRingTest.php +++ /dev/null @@ -1,327 +0,0 @@ - 3, 's2' => 10, 's3' => 2, 's4' => 10, 's5' => 2, 's6' => 3 ]; - $ring = new HashRing( $map, 'md5' ); - - $serialized = serialize( $ring ); - $ringRemade = unserialize( $serialized ); - - for ( $i = 0; $i < 100; $i++ ) { - $this->assertEquals( - $ring->getLocation( "hello$i" ), - $ringRemade->getLocation( "hello$i" ), - 'Items placed at proper locations' - ); - } - } - - public function testHashRingMapping() { - // SHA-1 based and weighted - $ring = new HashRing( - [ 's1' => 1, 's2' => 1, 's3' => 2, 's4' => 2, 's5' => 2, 's6' => 3, 's7' => 0 ], - 'sha1' - ); - - $this->assertEquals( - [ 's1' => 1, 's2' => 1, 's3' => 2, 's4' => 2, 's5' => 2, 's6' => 3 ], - $ring->getLocationWeights(), - 'Normalized location weights' - ); - - $locations = []; - for ( $i = 0; $i < 25; $i++ ) { - $locations[ "hello$i"] = $ring->getLocation( "hello$i" ); - } - $expectedLocations = [ - "hello0" => "s4", - "hello1" => "s6", - "hello2" => "s3", - "hello3" => "s6", - "hello4" => "s6", - "hello5" => "s4", - "hello6" => "s3", - "hello7" => "s4", - "hello8" => "s3", - "hello9" => "s3", - "hello10" => "s3", - "hello11" => "s5", - "hello12" => "s4", - "hello13" => "s5", - "hello14" => "s2", - "hello15" => "s5", - "hello16" => "s6", - "hello17" => "s5", - "hello18" => "s1", - "hello19" => "s1", - "hello20" => "s6", - "hello21" => "s5", - "hello22" => "s3", - "hello23" => "s4", - "hello24" => "s1" - ]; - $this->assertEquals( $expectedLocations, $locations, 'Items placed at proper locations' ); - - $locations = []; - for ( $i = 0; $i < 5; $i++ ) { - $locations[ "hello$i"] = $ring->getLocations( "hello$i", 2 ); - } - - $expectedLocations = [ - "hello0" => [ "s4", "s5" ], - "hello1" => [ "s6", "s5" ], - "hello2" => [ "s3", "s1" ], - "hello3" => [ "s6", "s5" ], - "hello4" => [ "s6", "s3" ], - ]; - $this->assertEquals( $expectedLocations, $locations, 'Items placed at proper locations' ); - } - - /** - * @dataProvider providor_getHashLocationWeights - */ - public function testHashRingRatios( $locations, $expectedHits ) { - $ring = new HashRing( $locations, 'whirlpool' ); - - $locationStats = array_fill_keys( array_keys( $locations ), 0 ); - for ( $i = 0; $i < 10000; ++$i ) { - ++$locationStats[$ring->getLocation( "key-$i" )]; - } - $this->assertEquals( $expectedHits, $locationStats ); - } - - public static function providor_getHashLocationWeights() { - return [ - [ - [ 'big' => 10, 'medium' => 5, 'small' => 1 ], - [ 'big' => 6037, 'medium' => 3314, 'small' => 649 ] - ] - ]; - } - - /** - * @dataProvider providor_getHashLocationWeights2 - */ - public function testHashRingRatios2( $locations, $expected ) { - $ring = new HashRing( $locations, 'sha1' ); - $locationStats = array_fill_keys( array_keys( $locations ), 0 ); - for ( $i = 0; $i < 1000; ++$i ) { - foreach ( $ring->getLocations( "key-$i", 3 ) as $location ) { - ++$locationStats[$location]; - } - } - $this->assertEquals( $expected, $locationStats ); - } - - public static function providor_getHashLocationWeights2() { - return [ - [ - [ 'big1' => 10, 'big2' => 10, 'big3' => 10, 'small1' => 1, 'small2' => 1 ], - [ 'big1' => 929, 'big2' => 899, 'big3' => 887, 'small1' => 143, 'small2' => 142 ] - ] - ]; - } - - public function testHashRingEjection() { - $map = [ 's1' => 5, 's2' => 5, 's3' => 10, 's4' => 10, 's5' => 5, 's6' => 5 ]; - $ring = new HashRing( $map, 'md5' ); - - $ring->ejectFromLiveRing( 's3', 30 ); - $ring->ejectFromLiveRing( 's6', 15 ); - - $this->assertEquals( - [ 's1' => 5, 's2' => 5, 's4' => 10, 's5' => 5 ], - $ring->getLiveLocationWeights(), - 'Live location weights' - ); - - for ( $i = 0; $i < 100; ++$i ) { - $key = "key-$i"; - - $this->assertNotEquals( 's3', $ring->getLiveLocation( $key ), 'ejected' ); - $this->assertNotEquals( 's6', $ring->getLiveLocation( $key ), 'ejected' ); - - if ( !in_array( $ring->getLocation( $key ), [ 's3', 's6' ], true ) ) { - $this->assertEquals( - $ring->getLocation( $key ), - $ring->getLiveLocation( $key ), - "Live ring otherwise matches (#$i)" - ); - $this->assertEquals( - $ring->getLocations( $key, 1 ), - $ring->getLiveLocations( $key, 1 ), - "Live ring otherwise matches (#$i)" - ); - } - } - } - - public function testHashRingCollision() { - $ring1 = new HashRing( [ 0 => 1, 6497 => 1 ] ); - $ring2 = new HashRing( [ 6497 => 1, 0 => 1 ] ); - - for ( $i = 0; $i < 100; ++$i ) { - $this->assertEquals( $ring1->getLocation( $i ), $ring2->getLocation( $i ) ); - } - } - - public function testHashRingKetamaMode() { - // Same as https://github.com/RJ/ketama/blob/master/ketama.servers - $map = [ - '10.0.1.1:11211' => 600, - '10.0.1.2:11211' => 300, - '10.0.1.3:11211' => 200, - '10.0.1.4:11211' => 350, - '10.0.1.5:11211' => 1000, - '10.0.1.6:11211' => 800, - '10.0.1.7:11211' => 950, - '10.0.1.8:11211' => 100 - ]; - $ring = new HashRing( $map, 'md5' ); - $wrapper = \Wikimedia\TestingAccessWrapper::newFromObject( $ring ); - - $ketama_test = function ( $count ) use ( $wrapper ) { - $baseRing = $wrapper->baseRing; - - $lines = []; - for ( $key = 0; $key < $count; ++$key ) { - $location = $wrapper->getLocation( $key ); - - $itemPos = $wrapper->getItemPosition( $key ); - $nodeIndex = $wrapper->findNodeIndexForPosition( $itemPos, $baseRing ); - $nodePos = $baseRing[$nodeIndex][HashRing::KEY_POS]; - - $lines[] = sprintf( "%u %u %s\n", $itemPos, $nodePos, $location ); - } - - return "\n" . implode( '', $lines ); - }; - - // Known correct values generated from C code: - // https://github.com/RJ/ketama/blob/master/libketama/ketama_test.c - $expected = <<assertEquals( $expected, $ketama_test( 100 ), 'Ketama mode (diff check)' ); - - // Hash of known correct values from C code - $this->assertEquals( - 'c69ac9eb7a8a630c0cded201cefeaace', - md5( $ketama_test( 1e5 ) ), - 'Ketama mode (large, MD5 check)' - ); - - // Slower, full upstream MD5 check, manually verified 3/21/2018 - // $this->assertEquals( '5672b131391f5aa2b280936aec1eea74', md5( $ketama_test( 1e6 ) ) ); - } -} diff --git a/tests/phpunit/includes/libs/HtmlArmorTest.php b/tests/phpunit/includes/libs/HtmlArmorTest.php deleted file mode 100644 index c5e87e4e2a..0000000000 --- a/tests/phpunit/includes/libs/HtmlArmorTest.php +++ /dev/null @@ -1,55 +0,0 @@ -some html!' ] - ]; - } - - /** - * @dataProvider provideConstructor - */ - public function testConstructor( $value ) { - $this->assertInstanceOf( HtmlArmor::class, new HtmlArmor( $value ) ); - } - - public static function provideGetHtml() { - return [ - [ - 'foobar', - 'foobar', - ], - [ - '', - '<script>alert("evil!");</script>', - ], - [ - new HtmlArmor( '' ), - '', - ], - [ - new HtmlArmor( null ), - null, - ] - ]; - } - - /** - * @dataProvider provideGetHtml - */ - public function testGetHtml( $input, $expected ) { - $this->assertEquals( - $expected, - HtmlArmor::getHtml( $input ) - ); - } -} diff --git a/tests/phpunit/includes/libs/IEUrlExtensionTest.php b/tests/phpunit/includes/libs/IEUrlExtensionTest.php deleted file mode 100644 index e04b2e21bf..0000000000 --- a/tests/phpunit/includes/libs/IEUrlExtensionTest.php +++ /dev/null @@ -1,42 +0,0 @@ -assertEquals( - $expected, - IEUrlExtension::findIE6Extension( $url ), - $message - ); - } -} diff --git a/tests/phpunit/includes/libs/IPTest.php b/tests/phpunit/includes/libs/IPTest.php deleted file mode 100644 index 9ec53c00c7..0000000000 --- a/tests/phpunit/includes/libs/IPTest.php +++ /dev/null @@ -1,673 +0,0 @@ -assertFalse( IP::isIPAddress( $val ), $desc ); - } - - /** - * Provide a list of things that aren't IP addresses - */ - public function provideInvalidIPs() { - return [ - [ false, 'Boolean false is not an IP' ], - [ true, 'Boolean true is not an IP' ], - [ '', 'Empty string is not an IP' ], - [ 'abc', 'Garbage IP string' ], - [ ':', 'Single ":" is not an IP' ], - [ '2001:0DB8::A:1::1', 'IPv6 with a double :: occurrence' ], - [ '2001:0DB8::A:1::', 'IPv6 with a double :: occurrence, last at end' ], - [ '::2001:0DB8::5:1', 'IPv6 with a double :: occurrence, firt at beginning' ], - [ '124.24.52', 'IPv4 not enough quads' ], - [ '24.324.52.13', 'IPv4 out of range' ], - [ '.24.52.13', 'IPv4 starts with period' ], - [ 'fc:100:300', 'IPv6 with only 3 words' ], - ]; - } - - /** - * @covers IP::isIPAddress - */ - public function testisIPAddress() { - $this->assertTrue( IP::isIPAddress( '::' ), 'RFC 4291 IPv6 Unspecified Address' ); - $this->assertTrue( IP::isIPAddress( '::1' ), 'RFC 4291 IPv6 Loopback Address' ); - $this->assertTrue( IP::isIPAddress( '74.24.52.13/20' ), 'IPv4 range' ); - $this->assertTrue( IP::isIPAddress( 'fc:100:a:d:1:e:ac:0/24' ), 'IPv6 range' ); - $this->assertTrue( IP::isIPAddress( 'fc::100:a:d:1:e:ac/96' ), 'IPv6 range with "::"' ); - - $validIPs = [ 'fc:100::', 'fc:100:a:d:1:e:ac::', 'fc::100', '::fc:100:a:d:1:e:ac', - '::fc', 'fc::100:a:d:1:e:ac', 'fc:100:a:d:1:e:ac:0', '124.24.52.13', '1.24.52.13' ]; - foreach ( $validIPs as $ip ) { - $this->assertTrue( IP::isIPAddress( $ip ), "$ip is a valid IP address" ); - } - } - - /** - * @covers IP::isIPv6 - */ - public function testisIPv6() { - $this->assertFalse( IP::isIPv6( ':fc:100::' ), 'IPv6 starting with lone ":"' ); - $this->assertFalse( IP::isIPv6( 'fc:100:::' ), 'IPv6 ending with a ":::"' ); - $this->assertFalse( IP::isIPv6( 'fc:300' ), 'IPv6 with only 2 words' ); - $this->assertFalse( IP::isIPv6( 'fc:100:300' ), 'IPv6 with only 3 words' ); - - $this->assertTrue( IP::isIPv6( 'fc:100::' ) ); - $this->assertTrue( IP::isIPv6( 'fc:100:a::' ) ); - $this->assertTrue( IP::isIPv6( 'fc:100:a:d::' ) ); - $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1::' ) ); - $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e::' ) ); - $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac::' ) ); - - $this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0::' ), 'IPv6 with 8 words ending with "::"' ); - $this->assertFalse( - IP::isIPv6( 'fc:100:a:d:1:e:ac:0:1::' ), - 'IPv6 with 9 words ending with "::"' - ); - - $this->assertFalse( IP::isIPv6( ':::' ) ); - $this->assertFalse( IP::isIPv6( '::0:' ), 'IPv6 ending in a lone ":"' ); - - $this->assertTrue( IP::isIPv6( '::' ), 'IPv6 zero address' ); - $this->assertTrue( IP::isIPv6( '::0' ) ); - $this->assertTrue( IP::isIPv6( '::fc' ) ); - $this->assertTrue( IP::isIPv6( '::fc:100' ) ); - $this->assertTrue( IP::isIPv6( '::fc:100:a' ) ); - $this->assertTrue( IP::isIPv6( '::fc:100:a:d' ) ); - $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1' ) ); - $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e' ) ); - $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e:ac' ) ); - - $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' ); - $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' ); - - $this->assertFalse( IP::isIPv6( ':fc::100' ), 'IPv6 starting with lone ":"' ); - $this->assertFalse( IP::isIPv6( 'fc::100:' ), 'IPv6 ending with lone ":"' ); - $this->assertFalse( IP::isIPv6( 'fc:::100' ), 'IPv6 with ":::" in the middle' ); - - $this->assertTrue( IP::isIPv6( 'fc::100' ), 'IPv6 with "::" and 2 words' ); - $this->assertTrue( IP::isIPv6( 'fc::100:a' ), 'IPv6 with "::" and 3 words' ); - $this->assertTrue( IP::isIPv6( 'fc::100:a:d' ), 'IPv6 with "::" and 4 words' ); - $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' ); - $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e' ), 'IPv6 with "::" and 6 words' ); - $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' ); - $this->assertTrue( IP::isIPv6( '2001::df' ), 'IPv6 with "::" and 2 words' ); - $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' ); - $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' ); - - $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' ); - $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' ); - - $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac:0' ) ); - } - - /** - * @covers IP::isIPv4 - * @dataProvider provideInvalidIPv4Addresses - */ - public function testisNotIPv4( $bogusIP, $desc ) { - $this->assertFalse( IP::isIPv4( $bogusIP ), $desc ); - } - - public function provideInvalidIPv4Addresses() { - return [ - [ false, 'Boolean false is not an IP' ], - [ true, 'Boolean true is not an IP' ], - [ '', 'Empty string is not an IP' ], - [ 'abc', 'Letters are not an IP' ], - [ ':', 'A colon is not an IP' ], - [ '124.24.52', 'IPv4 not enough quads' ], - [ '24.324.52.13', 'IPv4 out of range' ], - [ '.24.52.13', 'IPv4 starts with period' ], - ]; - } - - /** - * @covers IP::isIPv4 - * @dataProvider provideValidIPv4Address - */ - public function testIsIPv4( $ip, $desc ) { - $this->assertTrue( IP::isIPv4( $ip ), $desc ); - } - - /** - * Provide some IPv4 addresses and ranges - */ - public function provideValidIPv4Address() { - return [ - [ '124.24.52.13', 'Valid IPv4 address' ], - [ '1.24.52.13', 'Another valid IPv4 address' ], - [ '74.24.52.13/20', 'An IPv4 range' ], - ]; - } - - /** - * @covers IP::isValid - */ - public function testValidIPs() { - foreach ( range( 0, 255 ) as $i ) { - $a = sprintf( "%03d", $i ); - $b = sprintf( "%02d", $i ); - $c = sprintf( "%01d", $i ); - foreach ( array_unique( [ $a, $b, $c ] ) as $f ) { - $ip = "$f.$f.$f.$f"; - $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv4 address" ); - } - } - foreach ( range( 0x0, 0xFFFF, 0xF ) as $i ) { - $a = sprintf( "%04x", $i ); - $b = sprintf( "%03x", $i ); - $c = sprintf( "%02x", $i ); - foreach ( array_unique( [ $a, $b, $c ] ) as $f ) { - $ip = "$f:$f:$f:$f:$f:$f:$f:$f"; - $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv6 address" ); - } - } - // test with some abbreviations - $this->assertFalse( IP::isValid( ':fc:100::' ), 'IPv6 starting with lone ":"' ); - $this->assertFalse( IP::isValid( 'fc:100:::' ), 'IPv6 ending with a ":::"' ); - $this->assertFalse( IP::isValid( 'fc:300' ), 'IPv6 with only 2 words' ); - $this->assertFalse( IP::isValid( 'fc:100:300' ), 'IPv6 with only 3 words' ); - - $this->assertTrue( IP::isValid( 'fc:100::' ) ); - $this->assertTrue( IP::isValid( 'fc:100:a:d:1:e::' ) ); - $this->assertTrue( IP::isValid( 'fc:100:a:d:1:e:ac::' ) ); - - $this->assertTrue( IP::isValid( 'fc::100' ), 'IPv6 with "::" and 2 words' ); - $this->assertTrue( IP::isValid( 'fc::100:a' ), 'IPv6 with "::" and 3 words' ); - $this->assertTrue( IP::isValid( '2001::df' ), 'IPv6 with "::" and 2 words' ); - $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' ); - $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' ); - $this->assertTrue( IP::isValid( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' ); - $this->assertTrue( IP::isValid( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' ); - - $this->assertFalse( - IP::isValid( 'fc:100:a:d:1:e:ac:0::' ), - 'IPv6 with 8 words ending with "::"' - ); - $this->assertFalse( - IP::isValid( 'fc:100:a:d:1:e:ac:0:1::' ), - 'IPv6 with 9 words ending with "::"' - ); - } - - /** - * @covers IP::isValid - */ - public function testInvalidIPs() { - // Out of range... - foreach ( range( 256, 999 ) as $i ) { - $a = sprintf( "%03d", $i ); - $b = sprintf( "%02d", $i ); - $c = sprintf( "%01d", $i ); - foreach ( array_unique( [ $a, $b, $c ] ) as $f ) { - $ip = "$f.$f.$f.$f"; - $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv4 address" ); - } - } - foreach ( range( 'g', 'z' ) as $i ) { - $a = sprintf( "%04s", $i ); - $b = sprintf( "%03s", $i ); - $c = sprintf( "%02s", $i ); - foreach ( array_unique( [ $a, $b, $c ] ) as $f ) { - $ip = "$f:$f:$f:$f:$f:$f:$f:$f"; - $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv6 address" ); - } - } - // Have CIDR - $ipCIDRs = [ - '212.35.31.121/32', - '212.35.31.121/18', - '212.35.31.121/24', - '::ff:d:321:5/96', - 'ff::d3:321:5/116', - 'c:ff:12:1:ea:d:321:5/120', - ]; - foreach ( $ipCIDRs as $i ) { - $this->assertFalse( IP::isValid( $i ), - "$i is an invalid IP address because it is a range" ); - } - // Incomplete/garbage - $invalid = [ - 'www.xn--var-xla.net', - '216.17.184.G', - '216.17.184.1.', - '216.17.184', - '216.17.184.', - '256.17.184.1' - ]; - foreach ( $invalid as $i ) { - $this->assertFalse( IP::isValid( $i ), "$i is an invalid IP address" ); - } - } - - /** - * Provide some valid IP ranges - */ - public function provideValidRanges() { - return [ - [ '116.17.184.5/32' ], - [ '0.17.184.5/30' ], - [ '16.17.184.1/24' ], - [ '30.242.52.14/1' ], - [ '10.232.52.13/8' ], - [ '30.242.52.14/0' ], - [ '::e:f:2001/96' ], - [ '::c:f:2001/128' ], - [ '::10:f:2001/70' ], - [ '::fe:f:2001/1' ], - [ '::6d:f:2001/8' ], - [ '::fe:f:2001/0' ], - ]; - } - - /** - * @covers IP::isValidRange - * @dataProvider provideValidRanges - */ - public function testValidRanges( $range ) { - $this->assertTrue( IP::isValidRange( $range ), "$range is a valid IP range" ); - } - - /** - * @covers IP::isValidRange - * @dataProvider provideInvalidRanges - */ - public function testInvalidRanges( $invalid ) { - $this->assertFalse( IP::isValidRange( $invalid ), "$invalid is not a valid IP range" ); - } - - public function provideInvalidRanges() { - return [ - [ '116.17.184.5/33' ], - [ '0.17.184.5/130' ], - [ '16.17.184.1/-1' ], - [ '10.232.52.13/*' ], - [ '7.232.52.13/ab' ], - [ '11.232.52.13/' ], - [ '::e:f:2001/129' ], - [ '::c:f:2001/228' ], - [ '::10:f:2001/-1' ], - [ '::6d:f:2001/*' ], - [ '::86:f:2001/ab' ], - [ '::23:f:2001/' ], - ]; - } - - /** - * @covers IP::sanitizeIP - * @dataProvider provideSanitizeIP - */ - public function testSanitizeIP( $expected, $input ) { - $result = IP::sanitizeIP( $input ); - $this->assertEquals( $expected, $result ); - } - - /** - * Provider for IP::testSanitizeIP() - */ - public static function provideSanitizeIP() { - return [ - [ '0.0.0.0', '0.0.0.0' ], - [ '0.0.0.0', '00.00.00.00' ], - [ '0.0.0.0', '000.000.000.000' ], - [ '0.0.0.0/24', '000.000.000.000/24' ], - [ '141.0.11.253', '141.000.011.253' ], - [ '1.2.4.5', '1.2.4.5' ], - [ '1.2.4.5', '01.02.04.05' ], - [ '1.2.4.5', '001.002.004.005' ], - [ '10.0.0.1', '010.0.000.1' ], - [ '80.72.250.4', '080.072.250.04' ], - [ 'Foo.1000.00', 'Foo.1000.00' ], - [ 'Bar.01', 'Bar.01' ], - [ 'Bar.010', 'Bar.010' ], - [ null, '' ], - [ null, ' ' ] - ]; - } - - /** - * @covers IP::toHex - * @dataProvider provideToHex - */ - public function testToHex( $expected, $input ) { - $result = IP::toHex( $input ); - $this->assertTrue( $result === false || is_string( $result ) ); - $this->assertEquals( $expected, $result ); - } - - /** - * Provider for IP::testToHex() - */ - public static function provideToHex() { - return [ - [ '00000001', '0.0.0.1' ], - [ '01020304', '1.2.3.4' ], - [ '7F000001', '127.0.0.1' ], - [ '80000000', '128.0.0.0' ], - [ 'DEADCAFE', '222.173.202.254' ], - [ 'FFFFFFFF', '255.255.255.255' ], - [ '8D000BFD', '141.000.11.253' ], - [ false, 'IN.VA.LI.D' ], - [ 'v6-00000000000000000000000000000001', '::1' ], - [ 'v6-20010DB885A3000000008A2E03707334', '2001:0db8:85a3:0000:0000:8a2e:0370:7334' ], - [ 'v6-20010DB885A3000000008A2E03707334', '2001:db8:85a3::8a2e:0370:7334' ], - [ false, 'IN:VA::LI:D' ], - [ false, ':::1' ] - ]; - } - - /** - * @covers IP::isPublic - * @dataProvider provideIsPublic - */ - public function testIsPublic( $expected, $input ) { - $result = IP::isPublic( $input ); - $this->assertEquals( $expected, $result ); - } - - /** - * Provider for IP::testIsPublic() - */ - public static function provideIsPublic() { - return [ - [ false, 'fc00::3' ], # RFC 4193 (local) - [ false, 'fc00::ff' ], # RFC 4193 (local) - [ false, '127.1.2.3' ], # loopback - [ false, '::1' ], # loopback - [ false, 'fe80::1' ], # link-local - [ false, '169.254.1.1' ], # link-local - [ false, '10.0.0.1' ], # RFC 1918 (private) - [ false, '172.16.0.1' ], # RFC 1918 (private) - [ false, '192.168.0.1' ], # RFC 1918 (private) - [ true, '2001:5c0:1000:a::133' ], # public - [ true, 'fc::3' ], # public - [ true, '00FC::' ] # public - ]; - } - - // Private wrapper used to test CIDR Parsing. - private function assertFalseCIDR( $CIDR, $msg = '' ) { - $ff = [ false, false ]; - $this->assertEquals( $ff, IP::parseCIDR( $CIDR ), $msg ); - } - - // Private wrapper to test network shifting using only dot notation - private function assertNet( $expected, $CIDR ) { - $parse = IP::parseCIDR( $CIDR ); - $this->assertEquals( $expected, long2ip( $parse[0] ), "network shifting $CIDR" ); - } - - /** - * @covers IP::hexToQuad - * @dataProvider provideIPsAndHexes - */ - public function testHexToQuad( $ip, $hex ) { - $this->assertEquals( $ip, IP::hexToQuad( $hex ) ); - } - - /** - * Provide some IP addresses and their equivalent hex representations - */ - public function provideIPsandHexes() { - return [ - [ '0.0.0.1', '00000001' ], - [ '255.0.0.0', 'FF000000' ], - [ '255.255.255.255', 'FFFFFFFF' ], - [ '10.188.222.255', '0ABCDEFF' ], - // hex not left-padded... - [ '0.0.0.0', '0' ], - [ '0.0.0.1', '1' ], - [ '0.0.0.255', 'FF' ], - [ '0.0.255.0', 'FF00' ], - ]; - } - - /** - * @covers IP::hexToOctet - * @dataProvider provideOctetsAndHexes - */ - public function testHexToOctet( $octet, $hex ) { - $this->assertEquals( $octet, IP::hexToOctet( $hex ) ); - } - - /** - * Provide some hex and octet representations of the same IPs - */ - public function provideOctetsAndHexes() { - return [ - [ '0:0:0:0:0:0:0:1', '00000000000000000000000000000001' ], - [ '0:0:0:0:0:0:FF:3', '00000000000000000000000000FF0003' ], - [ '0:0:0:0:0:0:FF00:6', '000000000000000000000000FF000006' ], - [ '0:0:0:0:0:0:FCCF:FAFF', '000000000000000000000000FCCFFAFF' ], - [ 'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ], - // hex not left-padded... - [ '0:0:0:0:0:0:0:0', '0' ], - [ '0:0:0:0:0:0:0:1', '1' ], - [ '0:0:0:0:0:0:0:FF', 'FF' ], - [ '0:0:0:0:0:0:0:FFD0', 'FFD0' ], - [ '0:0:0:0:0:0:FA00:0', 'FA000000' ], - [ '0:0:0:0:0:0:FCCF:FAFF', 'FCCFFAFF' ], - ]; - } - - /** - * IP::parseCIDR() returns an array containing a signed IP address - * representing the network mask and the bit mask. - * @covers IP::parseCIDR - */ - public function testCIDRParsing() { - $this->assertFalseCIDR( '192.0.2.0', "missing mask" ); - $this->assertFalseCIDR( '192.0.2.0/', "missing bitmask" ); - - // Verify if statement - $this->assertFalseCIDR( '256.0.0.0/32', "invalid net" ); - $this->assertFalseCIDR( '192.0.2.0/AA', "mask not numeric" ); - $this->assertFalseCIDR( '192.0.2.0/-1', "mask < 0" ); - $this->assertFalseCIDR( '192.0.2.0/33', "mask > 32" ); - - // Check internal logic - # 0 mask always result in array(0,0) - $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '192.0.0.2/0' ) ); - $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '0.0.0.0/0' ) ); - $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '255.255.255.255/0' ) ); - - // @todo FIXME: Add more tests. - - # This part test network shifting - $this->assertNet( '192.0.0.0', '192.0.0.2/24' ); - $this->assertNet( '192.168.5.0', '192.168.5.13/24' ); - $this->assertNet( '10.0.0.160', '10.0.0.161/28' ); - $this->assertNet( '10.0.0.0', '10.0.0.3/28' ); - $this->assertNet( '10.0.0.0', '10.0.0.3/30' ); - $this->assertNet( '10.0.0.4', '10.0.0.4/30' ); - $this->assertNet( '172.17.32.0', '172.17.35.48/21' ); - $this->assertNet( '10.128.0.0', '10.135.0.0/9' ); - $this->assertNet( '134.0.0.0', '134.0.5.1/8' ); - } - - /** - * @covers IP::canonicalize - */ - public function testIPCanonicalizeOnValidIp() { - $this->assertEquals( '192.0.2.152', IP::canonicalize( '192.0.2.152' ), - 'Canonicalization of a valid IP returns it unchanged' ); - } - - /** - * @covers IP::canonicalize - */ - public function testIPCanonicalizeMappedAddress() { - $this->assertEquals( - '192.0.2.152', - IP::canonicalize( '::ffff:192.0.2.152' ) - ); - $this->assertEquals( - '192.0.2.152', - IP::canonicalize( '::192.0.2.152' ) - ); - } - - /** - * Issues there are most probably from IP::toHex() or IP::parseRange() - * @covers IP::isInRange - * @dataProvider provideIPsAndRanges - */ - public function testIPIsInRange( $expected, $addr, $range, $message = '' ) { - $this->assertEquals( - $expected, - IP::isInRange( $addr, $range ), - $message - ); - } - - /** Provider for testIPIsInRange() */ - public static function provideIPsAndRanges() { - # Format: (expected boolean, address, range, optional message) - return [ - # IPv4 - [ true, '192.0.2.0', '192.0.2.0/24', 'Network address' ], - [ true, '192.0.2.77', '192.0.2.0/24', 'Simple address' ], - [ true, '192.0.2.255', '192.0.2.0/24', 'Broadcast address' ], - - [ false, '0.0.0.0', '192.0.2.0/24' ], - [ false, '255.255.255', '192.0.2.0/24' ], - - # IPv6 - [ false, '::1', '2001:DB8::/32' ], - [ false, '::', '2001:DB8::/32' ], - [ false, 'FE80::1', '2001:DB8::/32' ], - - [ true, '2001:DB8::', '2001:DB8::/32' ], - [ true, '2001:0DB8::', '2001:DB8::/32' ], - [ true, '2001:DB8::1', '2001:DB8::/32' ], - [ true, '2001:0DB8::1', '2001:DB8::/32' ], - [ true, '2001:0DB8:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', - '2001:DB8::/32' ], - - [ false, '2001:0DB8:F::', '2001:DB8::/96' ], - ]; - } - - /** - * @covers IP::splitHostAndPort() - * @dataProvider provideSplitHostAndPort - */ - public function testSplitHostAndPort( $expected, $input, $description ) { - $this->assertEquals( $expected, IP::splitHostAndPort( $input ), $description ); - } - - /** - * Provider for IP::splitHostAndPort() - */ - public static function provideSplitHostAndPort() { - return [ - [ false, '[', 'Unclosed square bracket' ], - [ false, '[::', 'Unclosed square bracket 2' ], - [ [ '::', false ], '::', 'Bare IPv6 0' ], - [ [ '::1', false ], '::1', 'Bare IPv6 1' ], - [ [ '::', false ], '[::]', 'Bracketed IPv6 0' ], - [ [ '::1', false ], '[::1]', 'Bracketed IPv6 1' ], - [ [ '::1', 80 ], '[::1]:80', 'Bracketed IPv6 with port' ], - [ false, '::x', 'Double colon but no IPv6' ], - [ [ 'x', 80 ], 'x:80', 'Hostname and port' ], - [ false, 'x:x', 'Hostname and invalid port' ], - [ [ 'x', false ], 'x', 'Plain hostname' ] - ]; - } - - /** - * @covers IP::combineHostAndPort() - * @dataProvider provideCombineHostAndPort - */ - public function testCombineHostAndPort( $expected, $input, $description ) { - list( $host, $port, $defaultPort ) = $input; - $this->assertEquals( - $expected, - IP::combineHostAndPort( $host, $port, $defaultPort ), - $description ); - } - - /** - * Provider for IP::combineHostAndPort() - */ - public static function provideCombineHostAndPort() { - return [ - [ '[::1]', [ '::1', 2, 2 ], 'IPv6 default port' ], - [ '[::1]:2', [ '::1', 2, 3 ], 'IPv6 non-default port' ], - [ 'x', [ 'x', 2, 2 ], 'Normal default port' ], - [ 'x:2', [ 'x', 2, 3 ], 'Normal non-default port' ], - ]; - } - - /** - * @covers IP::sanitizeRange() - * @dataProvider provideIPCIDRs - */ - public function testSanitizeRange( $input, $expected, $description ) { - $this->assertEquals( $expected, IP::sanitizeRange( $input ), $description ); - } - - /** - * Provider for IP::testSanitizeRange() - */ - public static function provideIPCIDRs() { - return [ - [ '35.56.31.252/16', '35.56.0.0/16', 'IPv4 range' ], - [ '135.16.21.252/24', '135.16.21.0/24', 'IPv4 range' ], - [ '5.36.71.252/32', '5.36.71.252/32', 'IPv4 silly range' ], - [ '5.36.71.252', '5.36.71.252', 'IPv4 non-range' ], - [ '0:1:2:3:4:c5:f6:7/96', '0:1:2:3:4:C5:0:0/96', 'IPv6 range' ], - [ '0:1:2:3:4:5:6:7/120', '0:1:2:3:4:5:6:0/120', 'IPv6 range' ], - [ '0:e1:2:3:4:5:e6:7/128', '0:E1:2:3:4:5:E6:7/128', 'IPv6 silly range' ], - [ '0:c1:A2:3:4:5:c6:7', '0:C1:A2:3:4:5:C6:7', 'IPv6 non range' ], - ]; - } - - /** - * @covers IP::prettifyIP() - * @dataProvider provideIPsToPrettify - */ - public function testPrettifyIP( $ip, $prettified ) { - $this->assertEquals( $prettified, IP::prettifyIP( $ip ), "Prettify of $ip" ); - } - - /** - * Provider for IP::testPrettifyIP() - */ - public static function provideIPsToPrettify() { - return [ - [ '0:0:0:0:0:0:0:0', '::' ], - [ '0:0:0::0:0:0', '::' ], - [ '0:0:0:1:0:0:0:0', '0:0:0:1::' ], - [ '0:0::f', '::f' ], - [ '0::0:0:0:33:fef:b', '::33:fef:b' ], - [ '3f:535:0:0:0:0:e:fbb', '3f:535::e:fbb' ], - [ '0:0:fef:0:0:0:e:fbb', '0:0:fef::e:fbb' ], - [ 'abbc:2004::0:0:0:0', 'abbc:2004::' ], - [ 'cebc:2004:f:0:0:0:0:0', 'cebc:2004:f::' ], - [ '0:0:0:0:0:0:0:0/16', '::/16' ], - [ '0:0:0::0:0:0/64', '::/64' ], - [ '0:0::f/52', '::f/52' ], - [ '::0:0:33:fef:b/52', '::33:fef:b/52' ], - [ '3f:535:0:0:0:0:e:fbb/48', '3f:535::e:fbb/48' ], - [ '0:0:fef:0:0:0:e:fbb/96', '0:0:fef::e:fbb/96' ], - [ 'abbc:2004:0:0::0:0/40', 'abbc:2004::/40' ], - [ 'aebc:2004:f:0:0:0:0:0/80', 'aebc:2004:f::/80' ], - ]; - } -} diff --git a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php deleted file mode 100644 index d57d0dd553..0000000000 --- a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php +++ /dev/null @@ -1,367 +0,0 @@ -setMaxLineLength( 1000 ); - } - - private function setMaxLineLength( $val ) { - $classReflect = new ReflectionClass( JavaScriptMinifier::class ); - $propertyReflect = $classReflect->getProperty( 'maxLineLength' ); - $propertyReflect->setAccessible( true ); - $propertyReflect->setValue( JavaScriptMinifier::class, $val ); - } - - public static function provideCases() { - return [ - - // Basic whitespace and comments that should be stripped entirely - [ "\r\t\f \v\n\r", "" ], - [ "/* Foo *\n*bar\n*/", "" ], - - /** - * Slashes used inside block comments (T28931). - * At some point there was a bug that caused this comment to be ended at '* /', - * causing /M... to be left as the beginning of a regex. - */ - [ - "/**\n * Foo\n * {\n * 'bar' : {\n * " - . "//Multiple rules with configurable operators\n * 'baz' : false\n * }\n */", - "" ], - - /** - * ' Foo \' bar \ - * baz \' quox ' . - */ - [ - "' Foo \\' bar \\\n baz \\' quox ' .length", - "' Foo \\' bar \\\n baz \\' quox '.length" - ], - [ - "\" Foo \\\" bar \\\n baz \\\" quox \" .length", - "\" Foo \\\" bar \\\n baz \\\" quox \".length" - ], - [ "// Foo b/ar baz", "" ], - [ - "/ Foo \\/ bar [ / \\] / ] baz / .length", - "/ Foo \\/ bar [ / \\] / ] baz /.length" - ], - - // HTML comments - [ " bar", "" ], - [ "--> Foo", "" ], - [ "x --> y", "x-->y" ], - - // Semicolon insertion - [ "(function(){return\nx;})", "(function(){return\nx;})" ], - [ "throw\nx;", "throw\nx;" ], - [ "while(p){continue\nx;}", "while(p){continue\nx;}" ], - [ "while(p){break\nx;}", "while(p){break\nx;}" ], - [ "var\nx;", "var x;" ], - [ "x\ny;", "x\ny;" ], - [ "x\n++y;", "x\n++y;" ], - [ "x\n!y;", "x\n!y;" ], - [ "x\n{y}", "x\n{y}" ], - [ "x\n+y;", "x+y;" ], - [ "x\n(y);", "x(y);" ], - [ "5.\nx;", "5.\nx;" ], - [ "0xFF.\nx;", "0xFF.x;" ], - [ "5.3.\nx;", "5.3.x;" ], - - // Cover failure case for incomplete hex literal - [ "0x;", false, false ], - - // Cover failure case for number with no digits after E - [ "1.4E", false, false ], - - // Cover failure case for number with several E - [ "1.4EE2", false, false ], - [ "1.4EE", false, false ], - - // Cover failure case for number with several E (nonconsecutive) - // FIXME: This is invalid, but currently tolerated - [ "1.4E2E3", "1.4E2 E3", false ], - - // Semicolon insertion between an expression having an inline - // comment after it, and a statement on the next line (T29046). - [ - "var a = this //foo bar \n for ( b = 0; c < d; b++ ) {}", - "var a=this\nfor(b=0;cparse( $minified, 'minify-test.js', 1 ); - } - - $this->assertEquals( - $expectedOutput, - $minified, - "Minified output should be in the form expected." - ); - } - - public static function provideLineBreaker() { - return [ - [ - // Regression tests for T34548. - // Must not break between 'E' and '+'. - 'var name = 1.23456789E55;', - [ - 'var', - 'name', - '=', - '1.23456789E55', - ';', - ], - ], - [ - 'var name = 1.23456789E+5;', - [ - 'var', - 'name', - '=', - '1.23456789E+5', - ';', - ], - ], - [ - 'var name = 1.23456789E-5;', - [ - 'var', - 'name', - '=', - '1.23456789E-5', - ';', - ], - ], - [ - // Must not break before '++' - 'if(x++);', - [ - 'if', - '(', - 'x++', - ')', - ';', - ], - ], - [ - // Regression test for T201606. - // Must not break between 'return' and Expression. - // Was caused by bad state after '{}' in property value. - <<setMaxLineLength( 1 ); - $actual = JavaScriptMinifier::minify( $code ); - $this->assertEquals( - array_merge( [ '' ], $expectedLines ), - explode( "\n", $actual ) - ); - } -} diff --git a/tests/phpunit/includes/libs/MapCacheLRUTest.php b/tests/phpunit/includes/libs/MapCacheLRUTest.php deleted file mode 100644 index 7147c6fa22..0000000000 --- a/tests/phpunit/includes/libs/MapCacheLRUTest.php +++ /dev/null @@ -1,267 +0,0 @@ - 4, 'c' => 3, 'b' => 2, 'a' => 1 ]; - $cache = MapCacheLRU::newFromArray( $raw, 3 ); - - $this->assertEquals( 3, $cache->getMaxSize() ); - $this->assertSame( true, $cache->has( 'a' ) ); - $this->assertSame( true, $cache->has( 'b' ) ); - $this->assertSame( true, $cache->has( 'c' ) ); - $this->assertSame( 1, $cache->get( 'a' ) ); - $this->assertSame( 2, $cache->get( 'b' ) ); - $this->assertSame( 3, $cache->get( 'c' ) ); - - $this->assertSame( - [ 'a' => 1, 'b' => 2, 'c' => 3 ], - $cache->toArray() - ); - $this->assertSame( - [ 'a', 'b', 'c' ], - $cache->getAllKeys() - ); - - $cache->clear( 'a' ); - $this->assertSame( - [ 'b' => 2, 'c' => 3 ], - $cache->toArray() - ); - - $cache->clear(); - $this->assertSame( - [], - $cache->toArray() - ); - - $cache = MapCacheLRU::newFromArray( [ 'd' => 4, 'c' => 3, 'b' => 2, 'a' => 1 ], 4 ); - $cache->setMaxSize( 3 ); - $this->assertSame( - [ 'c' => 3, 'b' => 2, 'a' => 1 ], - $cache->toArray() - ); - } - - /** - * @covers MapCacheLRU::serialize() - * @covers MapCacheLRU::unserialize() - */ - function testSerialize() { - $cache = MapCacheLRU::newFromArray( [ 'd' => 4, 'c' => 3, 'b' => 2, 'a' => 1 ], 10 ); - $string = serialize( $cache ); - $ncache = unserialize( $string ); - $this->assertSame( - [ 'd' => 4, 'c' => 3, 'b' => 2, 'a' => 1 ], - $ncache->toArray() - ); - } - - /** - * @covers MapCacheLRU::has() - * @covers MapCacheLRU::get() - * @covers MapCacheLRU::set() - */ - function testLRU() { - $raw = [ 'a' => 1, 'b' => 2, 'c' => 3 ]; - $cache = MapCacheLRU::newFromArray( $raw, 3 ); - - $this->assertSame( true, $cache->has( 'c' ) ); - $this->assertSame( - [ 'a' => 1, 'b' => 2, 'c' => 3 ], - $cache->toArray() - ); - - $this->assertSame( 3, $cache->get( 'c' ) ); - $this->assertSame( - [ 'a' => 1, 'b' => 2, 'c' => 3 ], - $cache->toArray() - ); - - $this->assertSame( 1, $cache->get( 'a' ) ); - $this->assertSame( - [ 'b' => 2, 'c' => 3, 'a' => 1 ], - $cache->toArray() - ); - - $cache->set( 'a', 1 ); - $this->assertSame( - [ 'b' => 2, 'c' => 3, 'a' => 1 ], - $cache->toArray() - ); - - $cache->set( 'b', 22 ); - $this->assertSame( - [ 'c' => 3, 'a' => 1, 'b' => 22 ], - $cache->toArray() - ); - - $cache->set( 'd', 4 ); - $this->assertSame( - [ 'a' => 1, 'b' => 22, 'd' => 4 ], - $cache->toArray() - ); - - $cache->set( 'e', 5, 0.33 ); - $this->assertSame( - [ 'e' => 5, 'b' => 22, 'd' => 4 ], - $cache->toArray() - ); - - $cache->set( 'f', 6, 0.66 ); - $this->assertSame( - [ 'b' => 22, 'f' => 6, 'd' => 4 ], - $cache->toArray() - ); - - $cache->set( 'g', 7, 0.90 ); - $this->assertSame( - [ 'f' => 6, 'g' => 7, 'd' => 4 ], - $cache->toArray() - ); - - $cache->set( 'g', 7, 1.0 ); - $this->assertSame( - [ 'f' => 6, 'd' => 4, 'g' => 7 ], - $cache->toArray() - ); - } - - /** - * @covers MapCacheLRU::has() - * @covers MapCacheLRU::get() - * @covers MapCacheLRU::set() - */ - public function testExpiry() { - $raw = [ 'a' => 1, 'b' => 2, 'c' => 3 ]; - $cache = MapCacheLRU::newFromArray( $raw, 3 ); - - $now = microtime( true ); - $cache->setMockTime( $now ); - - $cache->set( 'd', 'xxx' ); - $this->assertTrue( $cache->has( 'd', 30 ) ); - $this->assertEquals( 'xxx', $cache->get( 'd' ) ); - - $now += 29; - $this->assertTrue( $cache->has( 'd', 30 ) ); - $this->assertEquals( 'xxx', $cache->get( 'd' ) ); - $this->assertEquals( 'xxx', $cache->get( 'd', 30 ) ); - - $now += 1.5; - $this->assertFalse( $cache->has( 'd', 30 ) ); - $this->assertEquals( 'xxx', $cache->get( 'd' ) ); - $this->assertNull( $cache->get( 'd', 30 ) ); - } - - /** - * @covers MapCacheLRU::hasField() - * @covers MapCacheLRU::getField() - * @covers MapCacheLRU::setField() - */ - public function testFields() { - $raw = [ 'a' => 1, 'b' => 2, 'c' => 3 ]; - $cache = MapCacheLRU::newFromArray( $raw, 3 ); - - $now = microtime( true ); - $cache->setMockTime( $now ); - - $cache->setField( 'PMs', 'Tony Blair', 'Labour' ); - $cache->setField( 'PMs', 'Margaret Thatcher', 'Tory' ); - $this->assertTrue( $cache->hasField( 'PMs', 'Tony Blair', 30 ) ); - $this->assertEquals( 'Labour', $cache->getField( 'PMs', 'Tony Blair' ) ); - $this->assertTrue( $cache->hasField( 'PMs', 'Tony Blair', 30 ) ); - - $now += 29; - $this->assertTrue( $cache->hasField( 'PMs', 'Tony Blair', 30 ) ); - $this->assertEquals( 'Labour', $cache->getField( 'PMs', 'Tony Blair' ) ); - $this->assertEquals( 'Labour', $cache->getField( 'PMs', 'Tony Blair', 30 ) ); - - $now += 1.5; - $this->assertFalse( $cache->hasField( 'PMs', 'Tony Blair', 30 ) ); - $this->assertEquals( 'Labour', $cache->getField( 'PMs', 'Tony Blair' ) ); - $this->assertNull( $cache->getField( 'PMs', 'Tony Blair', 30 ) ); - - $this->assertEquals( - [ 'Tony Blair' => 'Labour', 'Margaret Thatcher' => 'Tory' ], - $cache->get( 'PMs' ) - ); - - $cache->set( 'MPs', [ - 'Edwina Currie' => 1983, - 'Neil Kinnock' => 1970 - ] ); - $this->assertEquals( - [ - 'Edwina Currie' => 1983, - 'Neil Kinnock' => 1970 - ], - $cache->get( 'MPs' ) - ); - - $this->assertEquals( 1983, $cache->getField( 'MPs', 'Edwina Currie' ) ); - $this->assertEquals( 1970, $cache->getField( 'MPs', 'Neil Kinnock' ) ); - } - - /** - * @covers MapCacheLRU::has() - * @covers MapCacheLRU::get() - * @covers MapCacheLRU::set() - * @covers MapCacheLRU::hasField() - * @covers MapCacheLRU::getField() - * @covers MapCacheLRU::setField() - */ - public function testInvalidKeys() { - $cache = MapCacheLRU::newFromArray( [], 3 ); - - try { - $cache->has( 3.4 ); - $this->fail( "No exception" ); - } catch ( UnexpectedValueException $e ) { - $this->assertRegExp( '/must be string or integer/', $e->getMessage() ); - } - try { - $cache->get( false ); - $this->fail( "No exception" ); - } catch ( UnexpectedValueException $e ) { - $this->assertRegExp( '/must be string or integer/', $e->getMessage() ); - } - try { - $cache->set( 3.4, 'x' ); - $this->fail( "No exception" ); - } catch ( UnexpectedValueException $e ) { - $this->assertRegExp( '/must be string or integer/', $e->getMessage() ); - } - - try { - $cache->hasField( 'x', 3.4 ); - $this->fail( "No exception" ); - } catch ( UnexpectedValueException $e ) { - $this->assertRegExp( '/must be string or integer/', $e->getMessage() ); - } - try { - $cache->getField( 'x', false ); - $this->fail( "No exception" ); - } catch ( UnexpectedValueException $e ) { - $this->assertRegExp( '/must be string or integer/', $e->getMessage() ); - } - try { - $cache->setField( 'x', 3.4, 'x' ); - $this->fail( "No exception" ); - } catch ( UnexpectedValueException $e ) { - $this->assertRegExp( '/must be string or integer/', $e->getMessage() ); - } - } -} diff --git a/tests/phpunit/includes/libs/MemoizedCallableTest.php b/tests/phpunit/includes/libs/MemoizedCallableTest.php deleted file mode 100644 index 628cca0c68..0000000000 --- a/tests/phpunit/includes/libs/MemoizedCallableTest.php +++ /dev/null @@ -1,142 +0,0 @@ -getMockBuilder( stdClass::class ) - ->setMethods( [ 'reverse' ] )->getMock(); - $mock->expects( $this->any() ) - ->method( 'reverse' ) - ->will( $this->returnCallback( 'strrev' ) ); - - $memoized = new MemoizedCallable( [ $mock, 'reverse' ] ); - $this->assertEquals( 'flow', $memoized->invoke( 'wolf' ) ); - } - - /** - * Consecutive calls to the memoized callable with the same arguments - * should result in just one invocation of the underlying callable. - * - * @requires extension apcu - */ - public function testCallableMemoized() { - $observer = $this->getMockBuilder( stdClass::class ) - ->setMethods( [ 'computeSomething' ] )->getMock(); - $observer->expects( $this->once() ) - ->method( 'computeSomething' ) - ->will( $this->returnValue( 'ok' ) ); - - $memoized = new ArrayBackedMemoizedCallable( [ $observer, 'computeSomething' ] ); - - // First invocation -- delegates to $observer->computeSomething() - $this->assertEquals( 'ok', $memoized->invoke() ); - - // Second invocation -- returns memoized result - $this->assertEquals( 'ok', $memoized->invoke() ); - } - - /** - * @covers MemoizedCallable::invoke - */ - public function testInvokeVariadic() { - $memoized = new MemoizedCallable( 'sprintf' ); - $this->assertEquals( - $memoized->invokeArgs( [ 'this is %s', 'correct' ] ), - $memoized->invoke( 'this is %s', 'correct' ) - ); - } - - /** - * @covers MemoizedCallable::call - */ - public function testShortcutMethod() { - $this->assertEquals( - 'this is correct', - MemoizedCallable::call( 'sprintf', [ 'this is %s', 'correct' ] ) - ); - } - - /** - * Outlier TTL values should be coerced to range 1 - 86400. - */ - public function testTTLMaxMin() { - $memoized = new MemoizedCallable( 'abs', 100000 ); - $this->assertEquals( 86400, $this->readAttribute( $memoized, 'ttl' ) ); - - $memoized = new MemoizedCallable( 'abs', -10 ); - $this->assertEquals( 1, $this->readAttribute( $memoized, 'ttl' ) ); - } - - /** - * Closure names should be distinct. - */ - public function testMemoizedClosure() { - $a = new MemoizedCallable( function () { - return 'a'; - } ); - - $b = new MemoizedCallable( function () { - return 'b'; - } ); - - $this->assertEquals( $a->invokeArgs(), 'a' ); - $this->assertEquals( $b->invokeArgs(), 'b' ); - - $this->assertNotEquals( - $this->readAttribute( $a, 'callableName' ), - $this->readAttribute( $b, 'callableName' ) - ); - - $c = new ArrayBackedMemoizedCallable( function () { - return rand(); - } ); - $this->assertEquals( $c->invokeArgs(), $c->invokeArgs(), 'memoized random' ); - } - - /** - * @expectedExceptionMessage non-scalar argument - * @expectedException InvalidArgumentException - */ - public function testNonScalarArguments() { - $memoized = new MemoizedCallable( 'gettype' ); - $memoized->invoke( new stdClass() ); - } - - /** - * @expectedExceptionMessage must be an instance of callable - * @expectedException InvalidArgumentException - */ - public function testNotCallable() { - $memoized = new MemoizedCallable( 14 ); - } -} - -/** - * A MemoizedCallable subclass that stores function return values - * in an instance property rather than APC or APCu. - */ -class ArrayBackedMemoizedCallable extends MemoizedCallable { - private $cache = []; - - protected function fetchResult( $key, &$success ) { - if ( array_key_exists( $key, $this->cache ) ) { - $success = true; - return $this->cache[$key]; - } - $success = false; - return false; - } - - protected function storeResult( $key, $result ) { - $this->cache[$key] = $result; - } -} diff --git a/tests/phpunit/includes/libs/ProcessCacheLRUTest.php b/tests/phpunit/includes/libs/ProcessCacheLRUTest.php deleted file mode 100644 index 8e91e70cb0..0000000000 --- a/tests/phpunit/includes/libs/ProcessCacheLRUTest.php +++ /dev/null @@ -1,264 +0,0 @@ -assertEquals( 0, $cache->getEntriesCount(), $msg ); - } - - /** - * Helper to fill a cache object passed by reference - */ - protected function fillCache( &$cache, $numEntries ) { - // Fill cache with three values - for ( $i = 1; $i <= $numEntries; $i++ ) { - $cache->set( "cache-key-$i", "prop-$i", "value-$i" ); - } - } - - /** - * Generates an array of what would be expected in cache for a given cache - * size and a number of entries filled in sequentially - */ - protected function getExpectedCache( $cacheMaxEntries, $entryToFill ) { - $expected = []; - - if ( $entryToFill === 0 ) { - // The cache is empty! - return []; - } elseif ( $entryToFill <= $cacheMaxEntries ) { - // Cache is not fully filled - $firstKey = 1; - } else { - // Cache overflowed - $firstKey = 1 + $entryToFill - $cacheMaxEntries; - } - - $lastKey = $entryToFill; - - for ( $i = $firstKey; $i <= $lastKey; $i++ ) { - $expected["cache-key-$i"] = [ "prop-$i" => "value-$i" ]; - } - - return $expected; - } - - /** - * Highlight diff between assertEquals and assertNotSame - * @coversNothing - */ - public function testPhpUnitArrayEquality() { - $one = [ 'A' => 1, 'B' => 2 ]; - $two = [ 'B' => 2, 'A' => 1 ]; - // == - $this->assertEquals( $one, $two ); - // === - $this->assertNotSame( $one, $two ); - } - - /** - * @dataProvider provideInvalidConstructorArg - * @expectedException Wikimedia\Assert\ParameterAssertionException - * @covers ProcessCacheLRU::__construct - */ - public function testConstructorGivenInvalidValue( $maxSize ) { - new ProcessCacheLRUTestable( $maxSize ); - } - - /** - * Value which are forbidden by the constructor - */ - public static function provideInvalidConstructorArg() { - return [ - [ null ], - [ [] ], - [ new stdClass() ], - [ 0 ], - [ '5' ], - [ -1 ], - ]; - } - - /** - * @covers ProcessCacheLRU::get - * @covers ProcessCacheLRU::set - * @covers ProcessCacheLRU::has - */ - public function testAddAndGetAKey() { - $oneCache = new ProcessCacheLRUTestable( 1 ); - $this->assertCacheEmpty( $oneCache ); - - // First set just one value - $oneCache->set( 'cache-key', 'prop1', 'value1' ); - $this->assertEquals( 1, $oneCache->getEntriesCount() ); - $this->assertTrue( $oneCache->has( 'cache-key', 'prop1' ) ); - $this->assertEquals( 'value1', $oneCache->get( 'cache-key', 'prop1' ) ); - } - - /** - * @covers ProcessCacheLRU::set - * @covers ProcessCacheLRU::get - */ - public function testDeleteOldKey() { - $oneCache = new ProcessCacheLRUTestable( 1 ); - $this->assertCacheEmpty( $oneCache ); - - $oneCache->set( 'cache-key', 'prop1', 'value1' ); - $oneCache->set( 'cache-key', 'prop1', 'value2' ); - $this->assertEquals( 'value2', $oneCache->get( 'cache-key', 'prop1' ) ); - } - - /** - * This test that we properly overflow when filling a cache with - * a sequence of always different cache-keys. Meant to verify we correclty - * delete the older key. - * - * @covers ProcessCacheLRU::set - * @dataProvider provideCacheFilling - * @param int $cacheMaxEntries Maximum entry the created cache will hold - * @param int $entryToFill Number of entries to insert in the created cache. - */ - public function testFillingCache( $cacheMaxEntries, $entryToFill, $msg = '' ) { - $cache = new ProcessCacheLRUTestable( $cacheMaxEntries ); - $this->fillCache( $cache, $entryToFill ); - - $this->assertSame( - $this->getExpectedCache( $cacheMaxEntries, $entryToFill ), - $cache->getCache(), - "Filling a $cacheMaxEntries entries cache with $entryToFill entries" - ); - } - - /** - * Provider for testFillingCache - */ - public static function provideCacheFilling() { - // ($cacheMaxEntries, $entryToFill, $msg='') - return [ - [ 1, 0 ], - [ 1, 1 ], - // overflow - [ 1, 2 ], - // overflow - [ 5, 33 ], - ]; - } - - /** - * Create a cache with only one remaining entry then update - * the first inserted entry. Should bump it to the top. - * - * @covers ProcessCacheLRU::set - */ - public function testReplaceExistingKeyShouldBumpEntryToTop() { - $maxEntries = 3; - - $cache = new ProcessCacheLRUTestable( $maxEntries ); - // Fill cache leaving just one remaining slot - $this->fillCache( $cache, $maxEntries - 1 ); - - // Set an existing cache key - $cache->set( "cache-key-1", "prop-1", "new-value-for-1" ); - - $this->assertSame( - [ - 'cache-key-2' => [ 'prop-2' => 'value-2' ], - 'cache-key-1' => [ 'prop-1' => 'new-value-for-1' ], - ], - $cache->getCache() - ); - } - - /** - * @covers ProcessCacheLRU::get - * @covers ProcessCacheLRU::set - * @covers ProcessCacheLRU::has - */ - public function testRecentlyAccessedKeyStickIn() { - $cache = new ProcessCacheLRUTestable( 2 ); - $cache->set( 'first', 'prop1', 'value1' ); - $cache->set( 'second', 'prop2', 'value2' ); - - // Get first - $cache->get( 'first', 'prop1' ); - // Cache a third value, should invalidate the least used one - $cache->set( 'third', 'prop3', 'value3' ); - - $this->assertFalse( $cache->has( 'second', 'prop2' ) ); - } - - /** - * This first create a full cache then update the value for the 2nd - * filled entry. - * Given a cache having 1,2,3 as key, updating 2 should bump 2 to - * the top of the queue with the new value: 1,3,2* (* = updated). - * - * @covers ProcessCacheLRU::set - * @covers ProcessCacheLRU::get - */ - public function testReplaceExistingKeyInAFullCacheShouldBumpToTop() { - $maxEntries = 3; - - $cache = new ProcessCacheLRUTestable( $maxEntries ); - $this->fillCache( $cache, $maxEntries ); - - // Set an existing cache key - $cache->set( "cache-key-2", "prop-2", "new-value-for-2" ); - $this->assertSame( - [ - 'cache-key-1' => [ 'prop-1' => 'value-1' ], - 'cache-key-3' => [ 'prop-3' => 'value-3' ], - 'cache-key-2' => [ 'prop-2' => 'new-value-for-2' ], - ], - $cache->getCache() - ); - $this->assertEquals( 'new-value-for-2', - $cache->get( 'cache-key-2', 'prop-2' ) - ); - } - - /** - * @covers ProcessCacheLRU::set - */ - public function testBumpExistingKeyToTop() { - $cache = new ProcessCacheLRUTestable( 3 ); - $this->fillCache( $cache, 3 ); - - // Set the very first cache key to a new value - $cache->set( "cache-key-1", "prop-1", "new value for 1" ); - $this->assertEquals( - [ - 'cache-key-2' => [ 'prop-2' => 'value-2' ], - 'cache-key-3' => [ 'prop-3' => 'value-3' ], - 'cache-key-1' => [ 'prop-1' => 'new value for 1' ], - ], - $cache->getCache() - ); - } -} - -/** - * Overrides some ProcessCacheLRU methods and properties accessibility. - */ -class ProcessCacheLRUTestable extends ProcessCacheLRU { - public function getCache() { - return $this->cache->toArray(); - } - - public function getEntriesCount() { - return count( $this->cache->toArray() ); - } -} diff --git a/tests/phpunit/includes/libs/SamplingStatsdClientTest.php b/tests/phpunit/includes/libs/SamplingStatsdClientTest.php deleted file mode 100644 index 7bd161156d..0000000000 --- a/tests/phpunit/includes/libs/SamplingStatsdClientTest.php +++ /dev/null @@ -1,77 +0,0 @@ -getMockBuilder( SenderInterface::class )->getMock(); - $sender->expects( $this->any() )->method( 'open' )->will( $this->returnValue( true ) ); - if ( $expectWrite ) { - $sender->expects( $this->once() )->method( 'write' ) - ->with( $this->anything(), $this->equalTo( $data ) ); - } else { - $sender->expects( $this->never() )->method( 'write' ); - } - if ( defined( 'MT_RAND_PHP' ) ) { - mt_srand( $seed, MT_RAND_PHP ); - } else { - mt_srand( $seed ); - } - $client = new SamplingStatsdClient( $sender ); - $client->send( $data, $sampleRate ); - } - - public function samplingDataProvider() { - $unsampled = new StatsdData(); - $unsampled->setKey( 'foo' ); - $unsampled->setValue( 1 ); - - $sampled = new StatsdData(); - $sampled->setKey( 'foo' ); - $sampled->setValue( 1 ); - $sampled->setSampleRate( '0.1' ); - - return [ - // $data, $sampleRate, $seed, $expectWrite - [ $unsampled, 1, 0 /*0.44*/, true ], - [ $sampled, 1, 0 /*0.44*/, false ], - [ $sampled, 1, 4 /*0.03*/, true ], - [ $unsampled, 0.1, 0 /*0.44*/, false ], - [ $sampled, 0.5, 0 /*0.44*/, false ], - [ $sampled, 0.5, 4 /*0.03*/, false ], - ]; - } - - public function testSetSamplingRates() { - $matching = new StatsdData(); - $matching->setKey( 'foo.bar' ); - $matching->setValue( 1 ); - - $nonMatching = new StatsdData(); - $nonMatching->setKey( 'oof.bar' ); - $nonMatching->setValue( 1 ); - - $sender = $this->getMockBuilder( SenderInterface::class )->getMock(); - $sender->expects( $this->any() )->method( 'open' )->will( $this->returnValue( true ) ); - $sender->expects( $this->once() )->method( 'write' )->with( $this->anything(), - $this->equalTo( $nonMatching ) ); - - $client = new SamplingStatsdClient( $sender ); - $client->setSamplingRates( [ 'foo.*' => 0.2 ] ); - - mt_srand( 0 ); // next random is 0.44 - $client->send( $matching ); - mt_srand( 0 ); - $client->send( $nonMatching ); - } -} diff --git a/tests/phpunit/includes/libs/StaticArrayWriterTest.php b/tests/phpunit/includes/libs/StaticArrayWriterTest.php deleted file mode 100644 index 4bd845d0b9..0000000000 --- a/tests/phpunit/includes/libs/StaticArrayWriterTest.php +++ /dev/null @@ -1,58 +0,0 @@ - - * - * 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 Wikimedia\StaticArrayWriter; - -/** - * @covers \Wikimedia\StaticArrayWriter - */ -class StaticArrayWriterTest extends PHPUnit\Framework\TestCase { - public function testCreate() { - $data = [ - 'foo' => 'bar', - 'baz' => 'rawr', - "they're" => '"quoted properly"', - 'nested' => [ 'elements', 'work' ], - 'and' => [ 'these' => 'do too' ], - ]; - $writer = new StaticArrayWriter(); - $actual = $writer->create( $data, "Header\nWith\nNewlines" ); - $expected = << 'bar', - 'baz' => 'rawr', - 'they\'re' => '"quoted properly"', - 'nested' => [ - 0 => 'elements', - 1 => 'work', - ], - 'and' => [ - 'these' => 'do too', - ], -]; - -PHP; - $this->assertSame( $expected, $actual ); - } -} diff --git a/tests/phpunit/includes/libs/StringUtilsTest.php b/tests/phpunit/includes/libs/StringUtilsTest.php deleted file mode 100644 index fcfa53e22d..0000000000 --- a/tests/phpunit/includes/libs/StringUtilsTest.php +++ /dev/null @@ -1,128 +0,0 @@ -assertEquals( $expected, StringUtils::isUtf8( $string ), - 'Testing string "' . $this->escaped( $string ) . '"' ); - } - - /** - * Print high range characters as a hexadecimal - * @param string $string - * @return string - */ - function escaped( $string ) { - $escaped = ''; - $length = strlen( $string ); - for ( $i = 0; $i < $length; $i++ ) { - $char = $string[$i]; - $val = ord( $char ); - if ( $val > 127 ) { - $escaped .= '\x' . dechex( $val ); - } else { - $escaped .= $char; - } - } - - return $escaped; - } - - /** - * See also "UTF-8 decoder capability and stress test" by - * Markus Kuhn: - * http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt - */ - public static function provideStringsForIsUtf8Check() { - // Expected return values for StringUtils::isUtf8() - $PASS = true; - $FAIL = false; - - return [ - 'some ASCII' => [ $PASS, 'Some ASCII' ], - 'euro sign' => [ $PASS, "Euro sign €" ], - - 'first possible sequence 1 byte' => [ $PASS, "\x00" ], - 'first possible sequence 2 bytes' => [ $PASS, "\xc2\x80" ], - 'first possible sequence 3 bytes' => [ $PASS, "\xe0\xa0\x80" ], - 'first possible sequence 4 bytes' => [ $PASS, "\xf0\x90\x80\x80" ], - 'first possible sequence 5 bytes' => [ $FAIL, "\xf8\x88\x80\x80\x80" ], - 'first possible sequence 6 bytes' => [ $FAIL, "\xfc\x84\x80\x80\x80\x80" ], - - 'last possible sequence 1 byte' => [ $PASS, "\x7f" ], - 'last possible sequence 2 bytes' => [ $PASS, "\xdf\xbf" ], - 'last possible sequence 3 bytes' => [ $PASS, "\xef\xbf\xbf" ], - 'last possible sequence 4 bytes (U+1FFFFF)' => [ $FAIL, "\xf7\xbf\xbf\xbf" ], - 'last possible sequence 5 bytes' => [ $FAIL, "\xfb\xbf\xbf\xbf\xbf" ], - 'last possible sequence 6 bytes' => [ $FAIL, "\xfd\xbf\xbf\xbf\xbf\xbf" ], - - 'boundary 1' => [ $PASS, "\xed\x9f\xbf" ], - 'boundary 2' => [ $PASS, "\xee\x80\x80" ], - 'boundary 3' => [ $PASS, "\xef\xbf\xbd" ], - 'boundary 4' => [ $PASS, "\xf2\x80\x80\x80" ], - 'boundary 5 (U+FFFFF)' => [ $PASS, "\xf3\xbf\xbf\xbf" ], - 'boundary 6 (U+100000)' => [ $PASS, "\xf4\x80\x80\x80" ], - 'boundary 7 (U+10FFFF)' => [ $PASS, "\xf4\x8f\xbf\xbf" ], - 'boundary 8 (U+110000)' => [ $FAIL, "\xf4\x90\x80\x80" ], - - 'malformed 1' => [ $FAIL, "\x80" ], - 'malformed 2' => [ $FAIL, "\xbf" ], - 'malformed 3' => [ $FAIL, "\x80\xbf" ], - 'malformed 4' => [ $FAIL, "\x80\xbf\x80" ], - 'malformed 5' => [ $FAIL, "\x80\xbf\x80\xbf" ], - 'malformed 6' => [ $FAIL, "\x80\xbf\x80\xbf\x80" ], - 'malformed 7' => [ $FAIL, "\x80\xbf\x80\xbf\x80\xbf" ], - 'malformed 8' => [ $FAIL, "\x80\xbf\x80\xbf\x80\xbf\x80" ], - - 'last byte missing 1' => [ $FAIL, "\xc0" ], - 'last byte missing 2' => [ $FAIL, "\xe0\x80" ], - 'last byte missing 3' => [ $FAIL, "\xf0\x80\x80" ], - 'last byte missing 4' => [ $FAIL, "\xf8\x80\x80\x80" ], - 'last byte missing 5' => [ $FAIL, "\xfc\x80\x80\x80\x80" ], - 'last byte missing 6' => [ $FAIL, "\xdf" ], - 'last byte missing 7' => [ $FAIL, "\xef\xbf" ], - 'last byte missing 8' => [ $FAIL, "\xf7\xbf\xbf" ], - 'last byte missing 9' => [ $FAIL, "\xfb\xbf\xbf\xbf" ], - 'last byte missing 10' => [ $FAIL, "\xfd\xbf\xbf\xbf\xbf" ], - - 'extra continuation byte 1' => [ $FAIL, "e\xaf" ], - 'extra continuation byte 2' => [ $FAIL, "\xc3\x89\xaf" ], - 'extra continuation byte 3' => [ $FAIL, "\xef\xbc\xa5\xaf" ], - 'extra continuation byte 4' => [ $FAIL, "\xf0\x9d\x99\xb4\xaf" ], - - 'impossible bytes 1' => [ $FAIL, "\xfe" ], - 'impossible bytes 2' => [ $FAIL, "\xff" ], - 'impossible bytes 3' => [ $FAIL, "\xfe\xfe\xff\xff" ], - - 'overlong sequences 1' => [ $FAIL, "\xc0\xaf" ], - 'overlong sequences 2' => [ $FAIL, "\xc1\xaf" ], - 'overlong sequences 3' => [ $FAIL, "\xe0\x80\xaf" ], - 'overlong sequences 4' => [ $FAIL, "\xf0\x80\x80\xaf" ], - 'overlong sequences 5' => [ $FAIL, "\xf8\x80\x80\x80\xaf" ], - 'overlong sequences 6' => [ $FAIL, "\xfc\x80\x80\x80\x80\xaf" ], - - 'maximum overlong sequences 1' => [ $FAIL, "\xc1\xbf" ], - 'maximum overlong sequences 2' => [ $FAIL, "\xe0\x9f\xbf" ], - 'maximum overlong sequences 3' => [ $FAIL, "\xf0\x8f\xbf\xbf" ], - 'maximum overlong sequences 4' => [ $FAIL, "\xf8\x87\xbf\xbf" ], - 'maximum overlong sequences 5' => [ $FAIL, "\xfc\x83\xbf\xbf\xbf\xbf" ], - - 'surrogates 1 (U+D799)' => [ $PASS, "\xed\x9f\xbf" ], - 'surrogates 2 (U+E000)' => [ $PASS, "\xee\x80\x80" ], - 'surrogates 3 (U+D800)' => [ $FAIL, "\xed\xa0\x80" ], - 'surrogates 4 (U+DBFF)' => [ $FAIL, "\xed\xaf\xbf" ], - 'surrogates 5 (U+DC00)' => [ $FAIL, "\xed\xb0\x80" ], - 'surrogates 6 (U+DFFF)' => [ $FAIL, "\xed\xbf\xbf" ], - 'surrogates 7 (U+D800 U+DC00)' => [ $FAIL, "\xed\xa0\x80\xed\xb0\x80" ], - - 'noncharacters 1' => [ $PASS, "\xef\xbf\xbe" ], - 'noncharacters 2' => [ $PASS, "\xef\xbf\xbf" ], - ]; - } -} diff --git a/tests/phpunit/includes/libs/TimingTest.php b/tests/phpunit/includes/libs/TimingTest.php deleted file mode 100644 index 581a518626..0000000000 --- a/tests/phpunit/includes/libs/TimingTest.php +++ /dev/null @@ -1,115 +0,0 @@ - - */ - -class TimingTest extends PHPUnit\Framework\TestCase { - - use MediaWikiCoversValidator; - - /** - * @covers Timing::clearMarks - * @covers Timing::getEntries - */ - public function testClearMarks() { - $timing = new Timing; - $this->assertCount( 1, $timing->getEntries() ); - - $timing->mark( 'a' ); - $timing->mark( 'b' ); - $this->assertCount( 3, $timing->getEntries() ); - - $timing->clearMarks( 'a' ); - $this->assertNull( $timing->getEntryByName( 'a' ) ); - $this->assertNotNull( $timing->getEntryByName( 'b' ) ); - - $timing->clearMarks(); - $this->assertCount( 1, $timing->getEntries() ); - } - - /** - * @covers Timing::mark - * @covers Timing::getEntryByName - */ - public function testMark() { - $timing = new Timing; - $timing->mark( 'a' ); - - $entry = $timing->getEntryByName( 'a' ); - $this->assertEquals( 'a', $entry['name'] ); - $this->assertEquals( 'mark', $entry['entryType'] ); - $this->assertArrayHasKey( 'startTime', $entry ); - $this->assertEquals( 0, $entry['duration'] ); - - usleep( 100 ); - $timing->mark( 'a' ); - $newEntry = $timing->getEntryByName( 'a' ); - $this->assertGreaterThan( $entry['startTime'], $newEntry['startTime'] ); - } - - /** - * @covers Timing::measure - */ - public function testMeasure() { - $timing = new Timing; - - $timing->mark( 'a' ); - usleep( 100 ); - $timing->mark( 'b' ); - - $a = $timing->getEntryByName( 'a' ); - $b = $timing->getEntryByName( 'b' ); - - $timing->measure( 'a_to_b', 'a', 'b' ); - - $entry = $timing->getEntryByName( 'a_to_b' ); - $this->assertEquals( 'a_to_b', $entry['name'] ); - $this->assertEquals( 'measure', $entry['entryType'] ); - $this->assertEquals( $a['startTime'], $entry['startTime'] ); - $this->assertEquals( $b['startTime'] - $a['startTime'], $entry['duration'] ); - } - - /** - * @covers Timing::getEntriesByType - */ - public function testGetEntriesByType() { - $timing = new Timing; - - $timing->mark( 'mark_a' ); - usleep( 100 ); - $timing->mark( 'mark_b' ); - usleep( 100 ); - $timing->mark( 'mark_c' ); - - $timing->measure( 'measure_a', 'mark_a', 'mark_b' ); - $timing->measure( 'measure_b', 'mark_b', 'mark_c' ); - - $marks = array_map( function ( $entry ) { - return $entry['name']; - }, $timing->getEntriesByType( 'mark' ) ); - - $this->assertEquals( [ 'requestStart', 'mark_a', 'mark_b', 'mark_c' ], $marks ); - - $measures = array_map( function ( $entry ) { - return $entry['name']; - }, $timing->getEntriesByType( 'measure' ) ); - - $this->assertEquals( [ 'measure_a', 'measure_b' ], $measures ); - } -} diff --git a/tests/phpunit/includes/libs/XhprofDataTest.php b/tests/phpunit/includes/libs/XhprofDataTest.php deleted file mode 100644 index 3e9379456d..0000000000 --- a/tests/phpunit/includes/libs/XhprofDataTest.php +++ /dev/null @@ -1,274 +0,0 @@ -assertSame( $expect, XhprofData::splitKey( $key ) ); - } - - public function provideSplitKey() { - return [ - [ 'main()', [ null, 'main()' ] ], - [ 'foo==>bar', [ 'foo', 'bar' ] ], - [ 'bar@1==>bar@2', [ 'bar@1', 'bar@2' ] ], - [ 'foo==>bar==>baz', [ 'foo', 'bar==>baz' ] ], - [ '==>bar', [ '', 'bar' ] ], - [ '', [ null, '' ] ], - ]; - } - - /** - * @covers XhprofData::pruneData - */ - public function testInclude() { - $xhprofData = $this->getXhprofDataFixture( [ - 'include' => [ 'main()' ], - ] ); - $raw = $xhprofData->getRawData(); - $this->assertArrayHasKey( 'main()', $raw ); - $this->assertArrayHasKey( 'main()==>foo', $raw ); - $this->assertArrayHasKey( 'main()==>xhprof_disable', $raw ); - $this->assertSame( 3, count( $raw ) ); - } - - /** - * Validate the structure of data returned by - * Xhprof::getInclusiveMetrics(). This acts as a guard against unexpected - * structural changes to the returned data in lieu of using a more heavy - * weight typed response object. - * - * @covers XhprofData::getInclusiveMetrics - */ - public function testInclusiveMetricsStructure() { - $metricStruct = [ - 'ct' => 'int', - 'wt' => 'array', - 'cpu' => 'array', - 'mu' => 'array', - 'pmu' => 'array', - ]; - $statStruct = [ - 'total' => 'numeric', - 'min' => 'numeric', - 'mean' => 'numeric', - 'max' => 'numeric', - 'variance' => 'numeric', - 'percent' => 'numeric', - ]; - - $xhprofData = $this->getXhprofDataFixture(); - $metrics = $xhprofData->getInclusiveMetrics(); - - foreach ( $metrics as $name => $metric ) { - $this->assertArrayStructure( $metricStruct, $metric ); - - foreach ( $metricStruct as $key => $type ) { - if ( $type === 'array' ) { - $this->assertArrayStructure( $statStruct, $metric[$key] ); - if ( $name === 'main()' ) { - $this->assertEquals( 100, $metric[$key]['percent'] ); - } - } - } - } - } - - /** - * Validate the structure of data returned by - * Xhprof::getCompleteMetrics(). This acts as a guard against unexpected - * structural changes to the returned data in lieu of using a more heavy - * weight typed response object. - * - * @covers XhprofData::getCompleteMetrics - */ - public function testCompleteMetricsStructure() { - $metricStruct = [ - 'ct' => 'int', - 'wt' => 'array', - 'cpu' => 'array', - 'mu' => 'array', - 'pmu' => 'array', - 'calls' => 'array', - 'subcalls' => 'array', - ]; - $statsMetrics = [ 'wt', 'cpu', 'mu', 'pmu' ]; - $statStruct = [ - 'total' => 'numeric', - 'min' => 'numeric', - 'mean' => 'numeric', - 'max' => 'numeric', - 'variance' => 'numeric', - 'percent' => 'numeric', - 'exclusive' => 'numeric', - ]; - - $xhprofData = $this->getXhprofDataFixture(); - $metrics = $xhprofData->getCompleteMetrics(); - - foreach ( $metrics as $name => $metric ) { - $this->assertArrayStructure( $metricStruct, $metric, $name ); - - foreach ( $metricStruct as $key => $type ) { - if ( in_array( $key, $statsMetrics ) ) { - $this->assertArrayStructure( - $statStruct, $metric[$key], $key - ); - $this->assertLessThanOrEqual( - $metric[$key]['total'], $metric[$key]['exclusive'] - ); - } - } - } - } - - /** - * @covers XhprofData::getCallers - * @covers XhprofData::getCallees - */ - public function testEdges() { - $xhprofData = $this->getXhprofDataFixture(); - $this->assertSame( [], $xhprofData->getCallers( 'main()' ) ); - $this->assertSame( [ 'foo', 'xhprof_disable' ], - $xhprofData->getCallees( 'main()' ) - ); - $this->assertSame( [ 'main()' ], - $xhprofData->getCallers( 'foo' ) - ); - $this->assertSame( [], $xhprofData->getCallees( 'strlen' ) ); - } - - /** - * @covers XhprofData::getCriticalPath - */ - public function testCriticalPath() { - $xhprofData = $this->getXhprofDataFixture(); - $path = $xhprofData->getCriticalPath(); - - $last = null; - foreach ( $path as $key => $value ) { - list( $func, $call ) = XhprofData::splitKey( $key ); - $this->assertSame( $last, $func ); - $last = $call; - } - $this->assertSame( $last, 'bar@1' ); - } - - /** - * Get an Xhprof instance that has been primed with a set of known testing - * data. Tests for the Xhprof class should laregly be concerned with - * evaluating the manipulations of the data collected by xhprof rather - * than the data collection process itself. - * - * The returned Xhprof instance primed will be with a data set created by - * running this trivial program using the PECL xhprof implementation: - * @code - * function bar( $x ) { - * if ( $x > 0 ) { - * bar($x - 1); - * } - * } - * function foo() { - * for ( $idx = 0; $idx < 2; $idx++ ) { - * bar( $idx ); - * $x = strlen( 'abc' ); - * } - * } - * xhprof_enable( XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY ); - * foo(); - * $x = xhprof_disable(); - * var_export( $x ); - * @endcode - * - * @return Xhprof - */ - protected function getXhprofDataFixture( array $opts = [] ) { - return new XhprofData( [ - 'foo==>bar' => [ - 'ct' => 2, - 'wt' => 57, - 'cpu' => 92, - 'mu' => 1896, - 'pmu' => 0, - ], - 'foo==>strlen' => [ - 'ct' => 2, - 'wt' => 21, - 'cpu' => 141, - 'mu' => 752, - 'pmu' => 0, - ], - 'bar==>bar@1' => [ - 'ct' => 1, - 'wt' => 18, - 'cpu' => 19, - 'mu' => 752, - 'pmu' => 0, - ], - 'main()==>foo' => [ - 'ct' => 1, - 'wt' => 304, - 'cpu' => 307, - 'mu' => 4008, - 'pmu' => 0, - ], - 'main()==>xhprof_disable' => [ - 'ct' => 1, - 'wt' => 8, - 'cpu' => 10, - 'mu' => 768, - 'pmu' => 392, - ], - 'main()' => [ - 'ct' => 1, - 'wt' => 353, - 'cpu' => 351, - 'mu' => 6112, - 'pmu' => 1424, - ], - ], $opts ); - } - - /** - * Assert that the given array has the described structure. - * - * @param array $struct Array of key => type mappings - * @param array $actual Array to check - * @param string $label - */ - protected function assertArrayStructure( $struct, $actual, $label = null ) { - $this->assertInternalType( 'array', $actual, $label ); - $this->assertCount( count( $struct ), $actual, $label ); - foreach ( $struct as $key => $type ) { - $this->assertArrayHasKey( $key, $actual ); - $this->assertInternalType( $type, $actual[$key] ); - } - } -} diff --git a/tests/phpunit/includes/libs/XhprofTest.php b/tests/phpunit/includes/libs/XhprofTest.php deleted file mode 100644 index ccad4a43d6..0000000000 --- a/tests/phpunit/includes/libs/XhprofTest.php +++ /dev/null @@ -1,113 +0,0 @@ -getProperty( 'enabled' ); - $enabled->setAccessible( true ); - $enabled->setValue( true ); - $xhprof->getMethod( 'enable' )->invoke( null ); - } - - /** - * callAny() calls the first function of the list. - * - * @covers Xhprof::callAny - * @dataProvider provideCallAny - */ - public function testCallAny( array $functions, array $args, $expectedResult ) { - $xhprof = new ReflectionClass( Xhprof::class ); - $callAny = $xhprof->getMethod( 'callAny' ); - $callAny->setAccessible( true ); - - $this->assertEquals( $expectedResult, - $callAny->invoke( null, $functions, $args ) ); - } - - /** - * Data provider for testCallAny(). - */ - public function provideCallAny() { - return [ - [ - [ 'wfTestCallAny_func1', 'wfTestCallAny_func2', 'wfTestCallAny_func3' ], - [ 3, 4 ], - 12 - ], - [ - [ 'wfTestCallAny_nosuchfunc1', 'wfTestCallAny_func2', 'wfTestCallAny_func3' ], - [ 3, 4 ], - 7 - ], - [ - [ 'wfTestCallAny_nosuchfunc1', 'wfTestCallAny_nosuchfunc2', 'wfTestCallAny_func3' ], - [ 3, 4 ], - -1 - ] - - ]; - } - - /** - * callAny() throws an exception when all functions are unavailable. - * - * @expectedException Exception - * @expectedExceptionMessage Neither xhprof nor tideways are installed - * @covers Xhprof::callAny - */ - public function testCallAnyNoneAvailable() { - $xhprof = new ReflectionClass( Xhprof::class ); - $callAny = $xhprof->getMethod( 'callAny' ); - $callAny->setAccessible( true ); - - $callAny->invoke( $xhprof, [ - 'wfTestCallAny_nosuchfunc1', - 'wfTestCallAny_nosuchfunc2', - 'wfTestCallAny_nosuchfunc3' - ] ); - } -} - -/** Test function #1 for XhprofTest::testCallAny */ -function wfTestCallAny_func1( $a, $b ) { - return $a * $b; -} - -/** Test function #2 for XhprofTest::testCallAny */ -function wfTestCallAny_func2( $a, $b ) { - return $a + $b; -} - -/** Test function #3 for XhprofTest::testCallAny */ -function wfTestCallAny_func3( $a, $b ) { - return $a - $b; -} diff --git a/tests/phpunit/includes/libs/XmlTypeCheckTest.php b/tests/phpunit/includes/libs/XmlTypeCheckTest.php deleted file mode 100644 index 8616b41922..0000000000 --- a/tests/phpunit/includes/libs/XmlTypeCheckTest.php +++ /dev/null @@ -1,79 +0,0 @@ -"; - const MAL_FORMED_XML = ""; - // phpcs:ignore Generic.Files.LineLength - const XML_WITH_PIH = ''; - - /** - * @covers XMLTypeCheck::newFromString - * @covers XMLTypeCheck::getRootElement - */ - public function testWellFormedXML() { - $testXML = XmlTypeCheck::newFromString( self::WELL_FORMED_XML ); - $this->assertTrue( $testXML->wellFormed ); - $this->assertEquals( 'root', $testXML->getRootElement() ); - } - - /** - * @covers XMLTypeCheck::newFromString - */ - public function testMalFormedXML() { - $testXML = XmlTypeCheck::newFromString( self::MAL_FORMED_XML ); - $this->assertFalse( $testXML->wellFormed ); - } - - /** - * Verify we check for recursive entity DOS - * - * (If the DOS isn't properly handled, the test runner will probably go OOM...) - */ - public function testRecursiveEntity() { - $xml = <<<'XML' - - - - - - - - - -]> - -&test; - -XML; - $check = XmlTypeCheck::newFromString( $xml ); - $this->assertFalse( $check->wellFormed ); - } - - /** - * @covers XMLTypeCheck::processingInstructionHandler - */ - public function testProcessingInstructionHandler() { - $called = false; - $testXML = new XmlTypeCheck( - self::XML_WITH_PIH, - null, - false, - [ - 'processing_instruction_handler' => function () use ( &$called ) { - $called = true; - } - ] - ); - $this->assertTrue( $called ); - } - -} diff --git a/tests/phpunit/includes/libs/composer/ComposerInstalledTest.php b/tests/phpunit/includes/libs/composer/ComposerInstalledTest.php deleted file mode 100644 index 58e617ca82..0000000000 --- a/tests/phpunit/includes/libs/composer/ComposerInstalledTest.php +++ /dev/null @@ -1,498 +0,0 @@ -installed = __DIR__ . "/../../../data/composer/installed.json"; - } - - /** - * @covers ComposerInstalled::__construct - * @covers ComposerInstalled::getInstalledDependencies - */ - public function testGetInstalledDependencies() { - $installed = new ComposerInstalled( $this->installed ); - $this->assertEquals( [ - 'leafo/lessphp' => [ - 'version' => '0.5.0', - 'type' => 'library', - 'licenses' => [ 'MIT', 'GPL-3.0-only' ], - 'authors' => [ - [ - 'name' => 'Leaf Corcoran', - 'email' => 'leafot@gmail.com', - 'homepage' => 'http://leafo.net', - ], - ], - 'description' => 'lessphp is a compiler for LESS written in PHP.', - ], - 'psr/log' => [ - 'version' => '1.0.0', - 'type' => 'library', - 'licenses' => [ 'MIT' ], - 'authors' => [ - [ - 'name' => 'PHP-FIG', - 'homepage' => 'http://www.php-fig.org/', - ], - ], - 'description' => 'Common interface for logging libraries', - ], - 'cssjanus/cssjanus' => [ - 'version' => '1.1.1', - 'type' => 'library', - 'licenses' => [ 'Apache-2.0' ], - 'authors' => [ - ], - 'description' => 'Convert CSS stylesheets between left-to-right ' . - 'and right-to-left.', - ], - 'cdb/cdb' => [ - 'version' => '1.0.0', - 'type' => 'library', - 'licenses' => [ 'GPLv2' ], - 'authors' => [ - [ - 'name' => 'Tim Starling', - 'email' => 'tstarling@wikimedia.org', - ], - [ - 'name' => 'Chad Horohoe', - 'email' => 'chad@wikimedia.org', - ], - ], - 'description' => 'Constant Database (CDB) wrapper library for PHP. ' . - 'Provides pure-PHP fallback when dba_* functions are absent.', - ], - 'sebastian/version' => [ - 'version' => '2.0.1', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sebastian@phpunit.de', - 'role' => 'lead', - ], - ], - 'description' => 'Library that helps with managing the version ' . - 'number of Git-hosted PHP projects', - ], - 'sebastian/resource-operations' => [ - 'version' => '1.0.0', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sebastian@phpunit.de', - ], - ], - 'description' => 'Provides a list of PHP built-in functions that ' . - 'operate on resources', - ], - 'sebastian/recursion-context' => [ - 'version' => '3.0.0', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Jeff Welch', - 'email' => 'whatthejeff@gmail.com', - ], - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sebastian@phpunit.de', - ], - [ - 'name' => 'Adam Harvey', - 'email' => 'aharvey@php.net', - ], - ], - 'description' => 'Provides functionality to recursively process PHP ' . - 'variables', - ], - 'sebastian/object-reflector' => [ - 'version' => '1.1.1', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sebastian@phpunit.de', - ], - ], - 'description' => 'Allows reflection of object attributes, including ' . - 'inherited and non-public ones', - ], - 'sebastian/object-enumerator' => [ - 'version' => '3.0.3', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sebastian@phpunit.de', - ], - ], - 'description' => 'Traverses array structures and object graphs ' . - 'to enumerate all referenced objects', - ], - 'sebastian/global-state' => [ - 'version' => '2.0.0', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sebastian@phpunit.de', - ], - ], - 'description' => 'Snapshotting of global state', - ], - 'sebastian/exporter' => [ - 'version' => '3.1.0', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Jeff Welch', - 'email' => 'whatthejeff@gmail.com', - ], - [ - 'name' => 'Volker Dusch', - 'email' => 'github@wallbash.com', - ], - [ - 'name' => 'Bernhard Schussek', - 'email' => 'bschussek@2bepublished.at', - ], - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sebastian@phpunit.de', - ], - [ - 'name' => 'Adam Harvey', - 'email' => 'aharvey@php.net', - ], - ], - 'description' => 'Provides the functionality to export PHP ' . - 'variables for visualization', - ], - 'sebastian/environment' => [ - 'version' => '3.1.0', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sebastian@phpunit.de', - ], - ], - 'description' => 'Provides functionality to handle HHVM/PHP ' . - 'environments', - ], - 'sebastian/diff' => [ - 'version' => '2.0.1', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Kore Nordmann', - 'email' => 'mail@kore-nordmann.de', - ], - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sebastian@phpunit.de', - ], - ], - 'description' => 'Diff implementation', - ], - 'sebastian/comparator' => [ - 'version' => '2.1.1', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Jeff Welch', - 'email' => 'whatthejeff@gmail.com', - ], - [ - 'name' => 'Volker Dusch', - 'email' => 'github@wallbash.com', - ], - [ - 'name' => 'Bernhard Schussek', - 'email' => 'bschussek@2bepublished.at', - ], - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sebastian@phpunit.de', - ], - ], - 'description' => 'Provides the functionality to compare PHP ' . - 'values for equality', - ], - 'doctrine/instantiator' => [ - 'version' => '1.1.0', - 'type' => 'library', - 'licenses' => [ 'MIT' ], - 'authors' => [ - [ - 'name' => 'Marco Pivetta', - 'email' => 'ocramius@gmail.com', - 'homepage' => 'http://ocramius.github.com/', - ], - ], - 'description' => 'A small, lightweight utility to instantiate ' . - 'objects in PHP without invoking their constructors', - ], - 'phpunit/php-text-template' => [ - 'version' => '1.2.1', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sebastian@phpunit.de', - 'role' => 'lead', - ], - ], - 'description' => 'Simple template engine.', - ], - 'phpunit/phpunit-mock-objects' => [ - 'version' => '5.0.6', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sebastian@phpunit.de', - 'role' => 'lead', - ], - ], - 'description' => 'Mock Object library for PHPUnit', - ], - 'phpunit/php-timer' => [ - 'version' => '1.0.9', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sb@sebastian-bergmann.de', - 'role' => 'lead', - ], - ], - 'description' => 'Utility class for timing', - ], - 'phpunit/php-file-iterator' => [ - 'version' => '1.4.5', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sb@sebastian-bergmann.de', - 'role' => 'lead', - ], - ], - 'description' => 'FilterIterator implementation that filters ' . - 'files based on a list of suffixes.', - ], - 'theseer/tokenizer' => [ - 'version' => '1.1.0', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Arne Blankerts', - 'email' => 'arne@blankerts.de', - 'role' => 'Developer', - ], - ], - 'description' => 'A small library for converting tokenized PHP ' . - 'source code into XML and potentially other formats', - ], - 'sebastian/code-unit-reverse-lookup' => [ - 'version' => '1.0.1', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sebastian@phpunit.de', - ], - ], - 'description' => 'Looks up which function or method a line of ' . - 'code belongs to', - ], - 'phpunit/php-token-stream' => [ - 'version' => '2.0.2', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sebastian@phpunit.de', - ], - ], - 'description' => 'Wrapper around PHP\'s tokenizer extension.', - ], - 'phpunit/php-code-coverage' => [ - 'version' => '5.3.0', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sebastian@phpunit.de', - 'role' => 'lead', - ], - ], - 'description' => 'Library that provides collection, processing, ' . - 'and rendering functionality for PHP code coverage information.', - ], - 'webmozart/assert' => [ - 'version' => '1.2.0', - 'type' => 'library', - 'licenses' => [ 'MIT' ], - 'authors' => [ - [ - 'name' => 'Bernhard Schussek', - 'email' => 'bschussek@gmail.com', - ], - ], - 'description' => 'Assertions to validate method input/output with ' . - 'nice error messages.', - ], - 'phpdocumentor/reflection-common' => [ - 'version' => '1.0.1', - 'type' => 'library', - 'licenses' => [ 'MIT' ], - 'authors' => [ - [ - 'name' => 'Jaap van Otterdijk', - 'email' => 'opensource@ijaap.nl', - ], - ], - 'description' => 'Common reflection classes used by phpdocumentor to ' . - 'reflect the code structure', - ], - 'phpdocumentor/type-resolver' => [ - 'version' => '0.4.0', - 'type' => 'library', - 'licenses' => [ 'MIT' ], - 'authors' => [ - [ - 'name' => 'Mike van Riel', - 'email' => 'me@mikevanriel.com', - ], - ], - 'description' => '', - ], - 'phpdocumentor/reflection-docblock' => [ - 'version' => '4.2.0', - 'type' => 'library', - 'licenses' => [ 'MIT' ], - 'authors' => [ - [ - 'name' => 'Mike van Riel', - 'email' => 'me@mikevanriel.com', - ], - ], - 'description' => 'With this component, a library can provide support for ' . - 'annotations via DocBlocks or otherwise retrieve information that ' . - 'is embedded in a DocBlock.', - ], - 'phpspec/prophecy' => [ - 'version' => '1.7.3', - 'type' => 'library', - 'licenses' => [ 'MIT' ], - 'authors' => [ - [ - 'name' => 'Konstantin Kudryashov', - 'email' => 'ever.zet@gmail.com', - 'homepage' => 'http://everzet.com', - ], - [ - 'name' => 'Marcello Duarte', - 'email' => 'marcello.duarte@gmail.com', - ], - ], - 'description' => 'Highly opinionated mocking framework for PHP 5.3+', - ], - 'phar-io/version' => [ - 'version' => '1.0.1', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Arne Blankerts', - 'email' => 'arne@blankerts.de', - 'role' => 'Developer', - ], - [ - 'name' => 'Sebastian Heuer', - 'email' => 'sebastian@phpeople.de', - 'role' => 'Developer', - ], - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sebastian@phpunit.de', - 'role' => 'Developer', - ], - ], - 'description' => 'Library for handling version information and constraints', - ], - 'phar-io/manifest' => [ - 'version' => '1.0.1', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Arne Blankerts', - 'email' => 'arne@blankerts.de', - 'role' => 'Developer', - ], - [ - 'name' => 'Sebastian Heuer', - 'email' => 'sebastian@phpeople.de', - 'role' => 'Developer', - ], - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sebastian@phpunit.de', - 'role' => 'Developer', - ], - ], - 'description' => 'Component for reading phar.io manifest ' . - 'information from a PHP Archive (PHAR)', - ], - 'myclabs/deep-copy' => [ - 'version' => '1.7.0', - 'type' => 'library', - 'licenses' => [ 'MIT' ], - 'authors' => [ - ], - 'description' => 'Create deep copies (clones) of your objects', - ], - 'phpunit/phpunit' => [ - 'version' => '6.5.5', - 'type' => 'library', - 'licenses' => [ 'BSD-3-Clause' ], - 'authors' => [ - [ - 'name' => 'Sebastian Bergmann', - 'email' => 'sebastian@phpunit.de', - 'role' => 'lead', - ], - ], - 'description' => 'The PHP Unit Testing framework.', - ], - ], $installed->getInstalledDependencies() ); - } -} diff --git a/tests/phpunit/includes/libs/composer/ComposerJsonTest.php b/tests/phpunit/includes/libs/composer/ComposerJsonTest.php deleted file mode 100644 index 720fa6e8fd..0000000000 --- a/tests/phpunit/includes/libs/composer/ComposerJsonTest.php +++ /dev/null @@ -1,41 +0,0 @@ -json = __DIR__ . "/../../../data/composer/composer.json"; - $this->json2 = __DIR__ . "/../../../data/composer/new-composer.json"; - } - - /** - * @covers ComposerJson::__construct - * @covers ComposerJson::getRequiredDependencies - */ - public function testGetRequiredDependencies() { - $json = new ComposerJson( $this->json ); - $this->assertEquals( [ - 'cdb/cdb' => '1.0.0', - 'cssjanus/cssjanus' => '1.1.1', - 'leafo/lessphp' => '0.5.0', - 'psr/log' => '1.0.0', - ], $json->getRequiredDependencies() ); - } - - public static function provideNormalizeVersion() { - return [ - [ 'v1.0.0', '1.0.0' ], - [ '0.0.5', '0.0.5' ], - ]; - } - - /** - * @dataProvider provideNormalizeVersion - * @covers ComposerJson::normalizeVersion - */ - public function testNormalizeVersion( $input, $expected ) { - $this->assertEquals( $expected, ComposerJson::normalizeVersion( $input ) ); - } -} diff --git a/tests/phpunit/includes/libs/composer/ComposerLockTest.php b/tests/phpunit/includes/libs/composer/ComposerLockTest.php deleted file mode 100644 index f5fcdbe018..0000000000 --- a/tests/phpunit/includes/libs/composer/ComposerLockTest.php +++ /dev/null @@ -1,120 +0,0 @@ -lock = __DIR__ . "/../../../data/composer/composer.lock"; - } - - /** - * @covers ComposerLock::__construct - * @covers ComposerLock::getInstalledDependencies - */ - public function testGetInstalledDependencies() { - $lock = new ComposerLock( $this->lock ); - $this->assertEquals( [ - 'wikimedia/cdb' => [ - 'version' => '1.0.1', - 'type' => 'library', - 'licenses' => [ 'GPL-2.0-only' ], - 'authors' => [ - [ - 'name' => 'Tim Starling', - 'email' => 'tstarling@wikimedia.org', - ], - [ - 'name' => 'Chad Horohoe', - 'email' => 'chad@wikimedia.org', - ], - ], - 'description' => 'Constant Database (CDB) wrapper library for PHP. ' . - 'Provides pure-PHP fallback when dba_* functions are absent.', - ], - 'cssjanus/cssjanus' => [ - 'version' => '1.1.1', - 'type' => 'library', - 'licenses' => [ 'Apache-2.0' ], - 'authors' => [], - 'description' => 'Convert CSS stylesheets between left-to-right and right-to-left.', - ], - 'leafo/lessphp' => [ - 'version' => '0.5.0', - 'type' => 'library', - 'licenses' => [ 'MIT', 'GPL-3.0-only' ], - 'authors' => [ - [ - 'name' => 'Leaf Corcoran', - 'email' => 'leafot@gmail.com', - 'homepage' => 'http://leafo.net', - ], - ], - 'description' => 'lessphp is a compiler for LESS written in PHP.', - ], - 'psr/log' => [ - 'version' => '1.0.0', - 'type' => 'library', - 'licenses' => [ 'MIT' ], - 'authors' => [ - [ - 'name' => 'PHP-FIG', - 'homepage' => 'http://www.php-fig.org/', - ], - ], - 'description' => 'Common interface for logging libraries', - ], - 'oojs/oojs-ui' => [ - 'version' => '0.6.0', - 'type' => 'library', - 'licenses' => [ 'MIT' ], - 'authors' => [], - 'description' => '', - ], - 'composer/installers' => [ - 'version' => '1.0.19', - 'type' => 'composer-installer', - 'licenses' => [ 'MIT' ], - 'authors' => [ - [ - 'name' => 'Kyle Robinson Young', - 'email' => 'kyle@dontkry.com', - 'homepage' => 'https://github.com/shama', - ], - ], - 'description' => 'A multi-framework Composer library installer', - ], - 'mediawiki/translate' => [ - 'version' => '2014.12', - 'type' => 'mediawiki-extension', - 'licenses' => [ 'GPL-2.0-or-later' ], - 'authors' => [ - [ - 'name' => 'Niklas Laxström', - 'email' => 'niklas.laxstrom@gmail.com', - 'role' => 'Lead nitpicker', - ], - [ - 'name' => 'Siebrand Mazeland', - 'email' => 's.mazeland@xs4all.nl', - 'role' => 'Developer', - ], - ], - 'description' => 'The only standard solution to translate any kind ' . - 'of text with an avant-garde web interface within MediaWiki, ' . - 'including your documentation and software', - ], - 'mediawiki/universal-language-selector' => [ - 'version' => '2014.12', - 'type' => 'mediawiki-extension', - 'licenses' => [ 'GPL-2.0-or-later', 'MIT' ], - 'authors' => [], - 'description' => 'The primary aim is to allow users to select a language ' . - 'and configure its support in an easy way. ' . - 'Main features are language selection, input methods and web fonts.', - ], - ], $lock->getInstalledDependencies() ); - } - -} diff --git a/tests/phpunit/includes/libs/http/HttpAcceptNegotiatorTest.php b/tests/phpunit/includes/libs/http/HttpAcceptNegotiatorTest.php deleted file mode 100644 index 02eac11887..0000000000 --- a/tests/phpunit/includes/libs/http/HttpAcceptNegotiatorTest.php +++ /dev/null @@ -1,150 +0,0 @@ -getFirstSupportedValue( $accepted, $default ); - - $this->assertEquals( $expected, $actual ); - } - - public function provideGetBestSupportedKey() { - return [ - [ // #0: empty - [], // supported - [], // accepted - null, // default - null, // expected - ], - [ // #1: simple - [ 'text/foo', 'text/BAR', 'application/zuul' ], // supported - [ 'text/xzy' => 1, 'text/bar' => 0.5 ], // accepted - null, // default - 'text/BAR', // expected - ], - [ // #2: default - [ 'text/foo', 'text/BAR', 'application/zuul' ], // supported - [ 'text/xzy' => 1, 'text/xoo' => 0.5 ], // accepted - 'X', // default - 'X', // expected - ], - [ // #3: weighted - [ 'text/foo', 'text/BAR', 'application/zuul' ], // supported - [ 'text/foo' => 0.3, 'text/BAR' => 0.8, 'application/zuul' => 0.5 ], // accepted - null, // default - 'text/BAR', // expected - ], - [ // #4: zero weight - [ 'text/foo', 'text/BAR', 'application/zuul' ], // supported - [ 'text/foo' => 0, 'text/xoo' => 1 ], // accepted - null, // default - null, // expected - ], - [ // #5: * wildcard - [ 'text/foo', 'text/BAR', 'application/zuul' ], // supported - [ 'text/xoo' => 0.5, '*' => 0.1 ], // accepted - null, // default - 'text/foo', // expected - ], - [ // #6: */* wildcard - [ 'text/foo', 'text/BAR', 'application/zuul' ], // supported - [ 'text/xoo' => 0.5, '*/*' => 0.1 ], // accepted - null, // default - 'text/foo', // expected - ], - [ // #7: text/* wildcard - [ 'text/foo', 'text/BAR', 'application/zuul' ], // supported - [ 'text/foo' => 0.3, 'application/*' => 0.8 ], // accepted - null, // default - 'application/zuul', // expected - ], - [ // #8: Test specific format preferred over wildcard (T133314) - [ 'application/rdf+xml', 'text/json', 'text/html' ], // supported - [ '*/*' => 1, 'text/html' => 1 ], // accepted - null, // default - 'text/html', // expected - ], - [ // #9: Test specific format preferred over range (T133314) - [ 'application/rdf+xml', 'text/json', 'text/html' ], // supported - [ 'text/*' => 1, 'text/html' => 1 ], // accepted - null, // default - 'text/html', // expected - ], - [ // #10: Test range preferred over wildcard (T133314) - [ 'application/rdf+xml', 'text/html' ], // supported - [ '*/*' => 1, 'text/*' => 1 ], // accepted - null, // default - 'text/html', // expected - ], - ]; - } - - /** - * @dataProvider provideGetBestSupportedKey - */ - public function testGetBestSupportedKey( $supported, $accepted, $default, $expected ) { - $negotiator = new HttpAcceptNegotiator( $supported ); - $actual = $negotiator->getBestSupportedKey( $accepted, $default ); - - $this->assertEquals( $expected, $actual ); - } - -} diff --git a/tests/phpunit/includes/libs/http/HttpAcceptParserTest.php b/tests/phpunit/includes/libs/http/HttpAcceptParserTest.php deleted file mode 100644 index e4b47b46d5..0000000000 --- a/tests/phpunit/includes/libs/http/HttpAcceptParserTest.php +++ /dev/null @@ -1,56 +0,0 @@ - 1 ] - ], - [ // #2 - 'Accept: text/plain', - [ 'text/plain' => 1 ] - ], - [ // #3 - 'Accept: application/vnd.php.serialized, application/rdf+xml', - [ 'application/vnd.php.serialized' => 1, 'application/rdf+xml' => 1 ] - ], - [ // #4 - 'foo; q=0.2, xoo; q=0,text/n3', - [ 'text/n3' => 1, 'foo' => 0.2 ] - ], - [ // #5 - '*; q=0.2, */*; q=0.1,text/*', - [ 'text/*' => 1, '*' => 0.2, '*/*' => 0.1 ] - ], - // TODO: nicely ignore additional type paramerters - //[ // #6 - // 'Foo; q=0.2, Xoo; level=3, Bar; charset=xyz; q=0.4', - // [ 'xoo' => 1, 'bar' => 0.4, 'foo' => 0.1 ] - //], - ]; - } - - /** - * @dataProvider provideParseWeights - */ - public function testParseWeights( $header, $expected ) { - $parser = new HttpAcceptParser(); - $actual = $parser->parseWeights( $header ); - - $this->assertEquals( $expected, $actual ); // shouldn't be sensitive to order - } - -} diff --git a/tests/phpunit/includes/libs/mime/MSCompoundFileReaderTest.php b/tests/phpunit/includes/libs/mime/MSCompoundFileReaderTest.php deleted file mode 100644 index 4509a61eb7..0000000000 --- a/tests/phpunit/includes/libs/mime/MSCompoundFileReaderTest.php +++ /dev/null @@ -1,60 +0,0 @@ -assertTrue( $info['valid'] ); - $this->assertSame( $expectedMime, $info['mime'] ); - } - - public static function provideInvalid() { - return [ - [ 'dir-beyond-end.xls', 'ERROR_READ_PAST_END' ], - [ 'fat-loop.xls', 'ERROR_INVALID_FORMAT' ], - [ 'invalid-signature.xls', 'ERROR_INVALID_SIGNATURE' ], - ]; - } - - /** @dataProvider provideInvalid */ - public function testReadFileInvalid( $fileName, $expectedError ) { - global $IP; - - $info = MSCompoundFileReader::readFile( "$IP/tests/phpunit/data/MSCompoundFileReader/$fileName" ); - $this->assertFalse( $info['valid'] ); - $this->assertSame( constant( MSCompoundFileReader::class . '::' . $expectedError ), - $info['errorCode'] ); - } -} diff --git a/tests/phpunit/includes/libs/mime/MimeAnalyzerTest.php b/tests/phpunit/includes/libs/mime/MimeAnalyzerTest.php deleted file mode 100644 index 194781207e..0000000000 --- a/tests/phpunit/includes/libs/mime/MimeAnalyzerTest.php +++ /dev/null @@ -1,140 +0,0 @@ -mimeAnalyzer = new MimeAnalyzer( [ - 'infoFile' => $IP . "/includes/libs/mime/mime.info", - 'typeFile' => $IP . "/includes/libs/mime/mime.types", - 'xmlTypes' => [ - 'http://www.w3.org/2000/svg:svg' => 'image/svg+xml', - 'svg' => 'image/svg+xml', - 'http://www.lysator.liu.se/~alla/dia/:diagram' => 'application/x-dia-diagram', - 'http://www.w3.org/1999/xhtml:html' => 'text/html', // application/xhtml+xml? - 'html' => 'text/html', // application/xhtml+xml? - ] - ] ); - parent::setUp(); - } - - function doGuessMimeType( array $parameters = [] ) { - $class = new ReflectionClass( get_class( $this->mimeAnalyzer ) ); - $method = $class->getMethod( 'doGuessMimeType' ); - $method->setAccessible( true ); - return $method->invokeArgs( $this->mimeAnalyzer, $parameters ); - } - - /** - * @dataProvider providerImproveTypeFromExtension - * @param string $ext File extension (no leading dot) - * @param string $oldMime Initially detected MIME - * @param string $expectedMime MIME type after taking extension into account - */ - function testImproveTypeFromExtension( $ext, $oldMime, $expectedMime ) { - $actualMime = $this->mimeAnalyzer->improveTypeFromExtension( $oldMime, $ext ); - $this->assertEquals( $expectedMime, $actualMime ); - } - - function providerImproveTypeFromExtension() { - return [ - [ 'gif', 'image/gif', 'image/gif' ], - [ 'gif', 'unknown/unknown', 'unknown/unknown' ], - [ 'wrl', 'unknown/unknown', 'model/vrml' ], - [ 'txt', 'text/plain', 'text/plain' ], - [ 'csv', 'text/plain', 'text/csv' ], - [ 'tsv', 'text/plain', 'text/tab-separated-values' ], - [ 'js', 'text/javascript', 'application/javascript' ], - [ 'js', 'application/x-javascript', 'application/javascript' ], - [ 'json', 'text/plain', 'application/json' ], - [ 'foo', 'application/x-opc+zip', 'application/zip' ], - [ 'docx', 'application/x-opc+zip', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ], - [ 'djvu', 'image/x-djvu', 'image/vnd.djvu' ], - [ 'wav', 'audio/wav', 'audio/wav' ], - ]; - } - - /** - * Test to make sure that encoder=ffmpeg2theora doesn't trigger - * MEDIATYPE_VIDEO (T65584) - */ - function testOggRecognize() { - $oggFile = __DIR__ . '/../../../data/media/say-test.ogg'; - $actualType = $this->mimeAnalyzer->getMediaType( $oggFile, 'application/ogg' ); - $this->assertEquals( MEDIATYPE_AUDIO, $actualType ); - } - - /** - * Test to make sure that Opus audio files don't trigger - * MEDIATYPE_MULTIMEDIA (bug T151352) - */ - function testOpusRecognize() { - $oggFile = __DIR__ . '/../../../data/media/say-test.opus'; - $actualType = $this->mimeAnalyzer->getMediaType( $oggFile, 'application/ogg' ); - $this->assertEquals( MEDIATYPE_AUDIO, $actualType ); - } - - /** - * Test to make sure that mp3 files are detected as audio type - */ - function testMP3AsAudio() { - $file = __DIR__ . '/../../../data/media/say-test-with-id3.mp3'; - $actualType = $this->mimeAnalyzer->getMediaType( $file ); - $this->assertEquals( MEDIATYPE_AUDIO, $actualType ); - } - - /** - * Test to make sure that MP3 with id3 tag is recognized - */ - function testMP3WithID3Recognize() { - $file = __DIR__ . '/../../../data/media/say-test-with-id3.mp3'; - $actualType = $this->doGuessMimeType( [ $file, 'mp3' ] ); - $this->assertEquals( 'audio/mpeg', $actualType ); - } - - /** - * Test to make sure that MP3 without id3 tag is recognized (MPEG-1 sample rates) - */ - function testMP3NoID3RecognizeMPEG1() { - $file = __DIR__ . '/../../../data/media/say-test-mpeg1.mp3'; - $actualType = $this->doGuessMimeType( [ $file, 'mp3' ] ); - $this->assertEquals( 'audio/mpeg', $actualType ); - } - - /** - * Test to make sure that MP3 without id3 tag is recognized (MPEG-2 sample rates) - */ - function testMP3NoID3RecognizeMPEG2() { - $file = __DIR__ . '/../../../data/media/say-test-mpeg2.mp3'; - $actualType = $this->doGuessMimeType( [ $file, 'mp3' ] ); - $this->assertEquals( 'audio/mpeg', $actualType ); - } - - /** - * Test to make sure that MP3 without id3 tag is recognized (MPEG-2.5 sample rates) - */ - function testMP3NoID3RecognizeMPEG2_5() { - $file = __DIR__ . '/../../../data/media/say-test-mpeg2.5.mp3'; - $actualType = $this->doGuessMimeType( [ $file, 'mp3' ] ); - $this->assertEquals( 'audio/mpeg', $actualType ); - } - - /** - * A ZIP file embedded in the middle of a .doc file is still a Word Document. - */ - function testZipInDoc() { - $file = __DIR__ . '/../../../data/media/zip-in-doc.doc'; - $actualType = $this->doGuessMimeType( [ $file, 'doc' ] ); - $this->assertEquals( 'application/msword', $actualType ); - } -} diff --git a/tests/phpunit/includes/libs/objectcache/CachedBagOStuffTest.php b/tests/phpunit/includes/libs/objectcache/CachedBagOStuffTest.php deleted file mode 100644 index f953319e67..0000000000 --- a/tests/phpunit/includes/libs/objectcache/CachedBagOStuffTest.php +++ /dev/null @@ -1,159 +0,0 @@ -set( 'foo', 'bar' ); - $this->assertEquals( 'bar', $cache->get( 'foo' ) ); - - $backend->set( 'foo', 'baz' ); - $this->assertEquals( 'bar', $cache->get( 'foo' ), 'cached' ); - } - - /** - * @covers CachedBagOStuff::set - * @covers CachedBagOStuff::delete - */ - public function testSetAndDelete() { - $backend = new HashBagOStuff; - $cache = new CachedBagOStuff( $backend ); - - for ( $i = 0; $i < 10; $i++ ) { - $cache->set( "key$i", 1 ); - $this->assertEquals( 1, $cache->get( "key$i" ) ); - $this->assertEquals( 1, $backend->get( "key$i" ) ); - - $cache->delete( "key$i" ); - $this->assertEquals( false, $cache->get( "key$i" ) ); - $this->assertEquals( false, $backend->get( "key$i" ) ); - } - } - - /** - * @covers CachedBagOStuff::set - * @covers CachedBagOStuff::delete - */ - public function testWriteCacheOnly() { - $backend = new HashBagOStuff; - $cache = new CachedBagOStuff( $backend ); - - $cache->set( 'foo', 'bar', 0, CachedBagOStuff::WRITE_CACHE_ONLY ); - $this->assertEquals( 'bar', $cache->get( 'foo' ) ); - $this->assertFalse( $backend->get( 'foo' ) ); - - $cache->set( 'foo', 'old' ); - $this->assertEquals( 'old', $cache->get( 'foo' ) ); - $this->assertEquals( 'old', $backend->get( 'foo' ) ); - - $cache->set( 'foo', 'new', 0, CachedBagOStuff::WRITE_CACHE_ONLY ); - $this->assertEquals( 'new', $cache->get( 'foo' ) ); - $this->assertEquals( 'old', $backend->get( 'foo' ) ); - - $cache->delete( 'foo', CachedBagOStuff::WRITE_CACHE_ONLY ); - $this->assertEquals( 'old', $cache->get( 'foo' ) ); // Reloaded from backend - } - - /** - * @covers CachedBagOStuff::get - */ - public function testCacheBackendMisses() { - $backend = new HashBagOStuff; - $cache = new CachedBagOStuff( $backend ); - - // First hit primes the cache with miss from the backend - $this->assertEquals( false, $cache->get( 'foo' ) ); - - // Change the value in the backend - $backend->set( 'foo', true ); - - // Second hit returns the cached miss - $this->assertEquals( false, $cache->get( 'foo' ) ); - - // But a fresh value is read from the backend - $backend->set( 'bar', true ); - $this->assertEquals( true, $cache->get( 'bar' ) ); - } - - /** - * @covers CachedBagOStuff::setDebug - */ - public function testSetDebug() { - $backend = new HashBagOStuff(); - $cache = new CachedBagOStuff( $backend ); - // Access private property 'debugMode' - $backend = TestingAccessWrapper::newFromObject( $backend ); - $cache = TestingAccessWrapper::newFromObject( $cache ); - $this->assertFalse( $backend->debugMode ); - $this->assertFalse( $cache->debugMode ); - - $cache->setDebug( true ); - // Should have set both - $this->assertTrue( $backend->debugMode, 'sets backend' ); - $this->assertTrue( $cache->debugMode, 'sets self' ); - } - - /** - * @covers CachedBagOStuff::deleteObjectsExpiringBefore - */ - public function testExpire() { - $backend = $this->getMockBuilder( HashBagOStuff::class ) - ->setMethods( [ 'deleteObjectsExpiringBefore' ] ) - ->getMock(); - $backend->expects( $this->once() ) - ->method( 'deleteObjectsExpiringBefore' ) - ->willReturn( false ); - - $cache = new CachedBagOStuff( $backend ); - $cache->deleteObjectsExpiringBefore( '20110401000000' ); - } - - /** - * @covers CachedBagOStuff::makeKey - */ - public function testMakeKey() { - $backend = $this->getMockBuilder( HashBagOStuff::class ) - ->setMethods( [ 'makeKey' ] ) - ->getMock(); - $backend->method( 'makeKey' ) - ->willReturn( 'special/logic' ); - - // CachedBagOStuff wraps any backend with a process cache - // using HashBagOStuff. Hash has no special key limitations, - // but backends often do. Make sure it uses the backend's - // makeKey() logic, not the one inherited from HashBagOStuff - $cache = new CachedBagOStuff( $backend ); - - $this->assertEquals( 'special/logic', $backend->makeKey( 'special', 'logic' ) ); - $this->assertEquals( 'special/logic', $cache->makeKey( 'special', 'logic' ) ); - } - - /** - * @covers CachedBagOStuff::makeGlobalKey - */ - public function testMakeGlobalKey() { - $backend = $this->getMockBuilder( HashBagOStuff::class ) - ->setMethods( [ 'makeGlobalKey' ] ) - ->getMock(); - $backend->method( 'makeGlobalKey' ) - ->willReturn( 'special/logic' ); - - $cache = new CachedBagOStuff( $backend ); - - $this->assertEquals( 'special/logic', $backend->makeGlobalKey( 'special', 'logic' ) ); - $this->assertEquals( 'special/logic', $cache->makeGlobalKey( 'special', 'logic' ) ); - } -} diff --git a/tests/phpunit/includes/libs/objectcache/HashBagOStuffTest.php b/tests/phpunit/includes/libs/objectcache/HashBagOStuffTest.php deleted file mode 100644 index 332e23b25b..0000000000 --- a/tests/phpunit/includes/libs/objectcache/HashBagOStuffTest.php +++ /dev/null @@ -1,163 +0,0 @@ -assertInstanceOf( - HashBagOStuff::class, - new HashBagOStuff() - ); - } - - /** - * @covers HashBagOStuff::__construct - * @expectedException InvalidArgumentException - */ - public function testConstructBadZero() { - $cache = new HashBagOStuff( [ 'maxKeys' => 0 ] ); - } - - /** - * @covers HashBagOStuff::__construct - * @expectedException InvalidArgumentException - */ - public function testConstructBadNeg() { - $cache = new HashBagOStuff( [ 'maxKeys' => -1 ] ); - } - - /** - * @covers HashBagOStuff::__construct - * @expectedException InvalidArgumentException - */ - public function testConstructBadType() { - $cache = new HashBagOStuff( [ 'maxKeys' => 'x' ] ); - } - - /** - * @covers HashBagOStuff::delete - */ - public function testDelete() { - $cache = new HashBagOStuff(); - for ( $i = 0; $i < 10; $i++ ) { - $cache->set( "key$i", 1 ); - $this->assertEquals( 1, $cache->get( "key$i" ) ); - $cache->delete( "key$i" ); - $this->assertEquals( false, $cache->get( "key$i" ) ); - } - } - - /** - * @covers HashBagOStuff::clear - */ - public function testClear() { - $cache = new HashBagOStuff(); - for ( $i = 0; $i < 10; $i++ ) { - $cache->set( "key$i", 1 ); - $this->assertEquals( 1, $cache->get( "key$i" ) ); - } - $cache->clear(); - for ( $i = 0; $i < 10; $i++ ) { - $this->assertEquals( false, $cache->get( "key$i" ) ); - } - } - - /** - * @covers HashBagOStuff::doGet - * @covers HashBagOStuff::expire - */ - public function testExpire() { - $cache = new HashBagOStuff(); - $cacheInternal = TestingAccessWrapper::newFromObject( $cache ); - $cache->set( 'foo', 1 ); - $cache->set( 'bar', 1, 10 ); - $cache->set( 'baz', 1, -10 ); - - $this->assertEquals( 0, $cacheInternal->bag['foo'][$cache::KEY_EXP], 'Indefinite' ); - // 2 seconds tolerance - $this->assertEquals( time() + 10, $cacheInternal->bag['bar'][$cache::KEY_EXP], 'Future', 2 ); - $this->assertEquals( time() - 10, $cacheInternal->bag['baz'][$cache::KEY_EXP], 'Past', 2 ); - - $this->assertEquals( 1, $cache->get( 'bar' ), 'Key not expired' ); - $this->assertEquals( false, $cache->get( 'baz' ), 'Key expired' ); - } - - /** - * Ensure maxKeys eviction prefers keeping new keys. - * - * @covers HashBagOStuff::set - */ - public function testEvictionAdd() { - $cache = new HashBagOStuff( [ 'maxKeys' => 10 ] ); - for ( $i = 0; $i < 10; $i++ ) { - $cache->set( "key$i", 1 ); - $this->assertEquals( 1, $cache->get( "key$i" ) ); - } - for ( $i = 10; $i < 20; $i++ ) { - $cache->set( "key$i", 1 ); - $this->assertEquals( 1, $cache->get( "key$i" ) ); - $this->assertEquals( false, $cache->get( "key" . ( $i - 10 ) ) ); - } - } - - /** - * Ensure maxKeys eviction prefers recently set keys - * even if the keys pre-exist. - * - * @covers HashBagOStuff::set - */ - public function testEvictionSet() { - $cache = new HashBagOStuff( [ 'maxKeys' => 3 ] ); - - foreach ( [ 'foo', 'bar', 'baz' ] as $key ) { - $cache->set( $key, 1 ); - } - - // Set existing key - $cache->set( 'foo', 1 ); - - // Add a 4th key (beyond the allowed maximum) - $cache->set( 'quux', 1 ); - - // Foo's life should have been extended over Bar - foreach ( [ 'foo', 'baz', 'quux' ] as $key ) { - $this->assertEquals( 1, $cache->get( $key ), "Kept $key" ); - } - $this->assertEquals( false, $cache->get( 'bar' ), 'Evicted bar' ); - } - - /** - * Ensure maxKeys eviction prefers recently retrieved keys (LRU). - * - * @covers HashBagOStuff::doGet - * @covers HashBagOStuff::hasKey - */ - public function testEvictionGet() { - $cache = new HashBagOStuff( [ 'maxKeys' => 3 ] ); - - foreach ( [ 'foo', 'bar', 'baz' ] as $key ) { - $cache->set( $key, 1 ); - } - - // Get existing key - $cache->get( 'foo', 1 ); - - // Add a 4th key (beyond the allowed maximum) - $cache->set( 'quux', 1 ); - - // Foo's life should have been extended over Bar - foreach ( [ 'foo', 'baz', 'quux' ] as $key ) { - $this->assertEquals( 1, $cache->get( $key ), "Kept $key" ); - } - $this->assertEquals( false, $cache->get( 'bar' ), 'Evicted bar' ); - } -} diff --git a/tests/phpunit/includes/libs/objectcache/ReplicatedBagOStuffTest.php b/tests/phpunit/includes/libs/objectcache/ReplicatedBagOStuffTest.php deleted file mode 100644 index 550ec0bd09..0000000000 --- a/tests/phpunit/includes/libs/objectcache/ReplicatedBagOStuffTest.php +++ /dev/null @@ -1,62 +0,0 @@ -writeCache = new HashBagOStuff(); - $this->readCache = new HashBagOStuff(); - $this->cache = new ReplicatedBagOStuff( [ - 'writeFactory' => $this->writeCache, - 'readFactory' => $this->readCache, - ] ); - } - - /** - * @covers ReplicatedBagOStuff::set - */ - public function testSet() { - $key = 'a key'; - $value = 'a value'; - $this->cache->set( $key, $value ); - - // Write to master. - $this->assertEquals( $value, $this->writeCache->get( $key ) ); - // Don't write to replica. Replication is deferred to backend. - $this->assertFalse( $this->readCache->get( $key ) ); - } - - /** - * @covers ReplicatedBagOStuff::get - */ - public function testGet() { - $key = 'a key'; - - $write = 'one value'; - $this->writeCache->set( $key, $write ); - $read = 'another value'; - $this->readCache->set( $key, $read ); - - // Read from replica. - $this->assertEquals( $read, $this->cache->get( $key ) ); - } - - /** - * @covers ReplicatedBagOStuff::get - */ - public function testGetAbsent() { - $key = 'a key'; - $value = 'a value'; - $this->writeCache->set( $key, $value ); - - // Don't read from master. No failover if value is absent. - $this->assertFalse( $this->cache->get( $key ) ); - } -} diff --git a/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php b/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php deleted file mode 100644 index 017d745e49..0000000000 --- a/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php +++ /dev/null @@ -1,1867 +0,0 @@ -cache = new WANObjectCache( [ - 'cache' => new HashBagOStuff() - ] ); - - $wanCache = TestingAccessWrapper::newFromObject( $this->cache ); - /** @noinspection PhpUndefinedFieldInspection */ - $this->internalCache = $wanCache->cache; - } - - /** - * @dataProvider provideSetAndGet - * @covers WANObjectCache::set() - * @covers WANObjectCache::get() - * @covers WANObjectCache::makeKey() - * @param mixed $value - * @param int $ttl - */ - public function testSetAndGet( $value, $ttl ) { - $curTTL = null; - $asOf = null; - $key = $this->cache->makeKey( 'x', wfRandomString() ); - - $this->cache->get( $key, $curTTL, [], $asOf ); - $this->assertNull( $curTTL, "Current TTL is null" ); - $this->assertNull( $asOf, "Current as-of-time is infinite" ); - - $t = microtime( true ); - $this->cache->set( $key, $value, $ttl ); - - $this->assertEquals( $value, $this->cache->get( $key, $curTTL, [], $asOf ) ); - if ( is_infinite( $ttl ) || $ttl == 0 ) { - $this->assertTrue( is_infinite( $curTTL ), "Current TTL is infinite" ); - } else { - $this->assertGreaterThan( 0, $curTTL, "Current TTL > 0" ); - $this->assertLessThanOrEqual( $ttl, $curTTL, "Current TTL < nominal TTL" ); - } - $this->assertGreaterThanOrEqual( $t - 1, $asOf, "As-of-time in range of set() time" ); - $this->assertLessThanOrEqual( $t + 1, $asOf, "As-of-time in range of set() time" ); - } - - public static function provideSetAndGet() { - return [ - [ 14141, 3 ], - [ 3535.666, 3 ], - [ [], 3 ], - [ null, 3 ], - [ '0', 3 ], - [ (object)[ 'meow' ], 3 ], - [ INF, 3 ], - [ '', 3 ], - [ 'pizzacat', INF ], - ]; - } - - /** - * @covers WANObjectCache::get() - * @covers WANObjectCache::makeGlobalKey() - */ - public function testGetNotExists() { - $key = $this->cache->makeGlobalKey( 'y', wfRandomString(), 'p' ); - $curTTL = null; - $value = $this->cache->get( $key, $curTTL ); - - $this->assertFalse( $value, "Non-existing key has false value" ); - $this->assertNull( $curTTL, "Non-existing key has null current TTL" ); - } - - /** - * @covers WANObjectCache::set() - */ - public function testSetOver() { - $key = wfRandomString(); - for ( $i = 0; $i < 3; ++$i ) { - $value = wfRandomString(); - $this->cache->set( $key, $value, 3 ); - - $this->assertEquals( $this->cache->get( $key ), $value ); - } - } - - /** - * @covers WANObjectCache::set() - */ - public function testStaleSet() { - $key = wfRandomString(); - $value = wfRandomString(); - $this->cache->set( $key, $value, 3, [ 'since' => microtime( true ) - 30 ] ); - - $this->assertFalse( $this->cache->get( $key ), "Stale set() value ignored" ); - } - - public function testProcessCache() { - $mockWallClock = 1549343530.2053; - $this->cache->setMockTime( $mockWallClock ); - - $hit = 0; - $callback = function () use ( &$hit ) { - ++$hit; - return 42; - }; - $keys = [ wfRandomString(), wfRandomString(), wfRandomString() ]; - $groups = [ 'thiscache:1', 'thatcache:1', 'somecache:1' ]; - - foreach ( $keys as $i => $key ) { - $this->cache->getWithSetCallback( - $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] ); - } - $this->assertEquals( 3, $hit ); - - foreach ( $keys as $i => $key ) { - $this->cache->getWithSetCallback( - $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] ); - } - $this->assertEquals( 3, $hit, "Values cached" ); - - foreach ( $keys as $i => $key ) { - $this->cache->getWithSetCallback( - "$key-2", 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] ); - } - $this->assertEquals( 6, $hit ); - - foreach ( $keys as $i => $key ) { - $this->cache->getWithSetCallback( - "$key-2", 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] ); - } - $this->assertEquals( 6, $hit, "New values cached" ); - - foreach ( $keys as $i => $key ) { - // Should evict from process cache - $this->cache->delete( $key ); - $mockWallClock += 0.001; // cached values will be newer than tombstone - // Get into cache (specific process cache group) - $this->cache->getWithSetCallback( - $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] ); - } - $this->assertEquals( 9, $hit, "Values evicted by delete()" ); - - // Get into cache (default process cache group) - $key = reset( $keys ); - $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] ); - $this->assertEquals( 9, $hit, "Value recently interim-cached" ); - - $mockWallClock += 0.2; // interim key not brand new - $this->cache->clearProcessCache(); - $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] ); - $this->assertEquals( 10, $hit, "Value calculated (interim key not recent and reset)" ); - $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] ); - $this->assertEquals( 10, $hit, "Value process cached" ); - - $mockWallClock += 0.2; // interim key not brand new - $outerCallback = function () use ( &$callback, $key ) { - $v = $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] ); - - return 43 + $v; - }; - // Outer key misses and refuses inner key process cache value - $this->cache->getWithSetCallback( "$key-miss-outer", 100, $outerCallback ); - $this->assertEquals( 11, $hit, "Nested callback value process cache skipped" ); - } - - /** - * @dataProvider getWithSetCallback_provider - * @covers WANObjectCache::getWithSetCallback() - * @covers WANObjectCache::doGetWithSetCallback() - * @param array $extOpts - * @param bool $versioned - */ - public function testGetWithSetCallback( array $extOpts, $versioned ) { - $cache = $this->cache; - - $key = wfRandomString(); - $value = wfRandomString(); - $cKey1 = wfRandomString(); - $cKey2 = wfRandomString(); - - $priorValue = null; - $priorAsOf = null; - $wasSet = 0; - $func = function ( $old, &$ttl, &$opts, $asOf ) - use ( &$wasSet, &$priorValue, &$priorAsOf, $value ) { - ++$wasSet; - $priorValue = $old; - $priorAsOf = $asOf; - $ttl = 20; // override with another value - return $value; - }; - - $mockWallClock = 1549343530.2053; - $priorTime = $mockWallClock; // reference time - $cache->setMockTime( $mockWallClock ); - - $wasSet = 0; - $v = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] + $extOpts ); - $this->assertEquals( $value, $v, "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value regenerated" ); - $this->assertFalse( $priorValue, "No prior value" ); - $this->assertNull( $priorAsOf, "No prior value" ); - - $curTTL = null; - $cache->get( $key, $curTTL ); - $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' ); - $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' ); - - $wasSet = 0; - $v = $cache->getWithSetCallback( - $key, 30, $func, [ 'lowTTL' => 0, 'lockTSE' => 5 ] + $extOpts ); - $this->assertEquals( $value, $v, "Value returned" ); - $this->assertEquals( 0, $wasSet, "Value not regenerated" ); - - $mockWallClock += 1; - - $wasSet = 0; - $v = $cache->getWithSetCallback( - $key, 30, $func, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts - ); - $this->assertEquals( $value, $v, "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" ); - $this->assertEquals( $value, $priorValue, "Has prior value" ); - $this->assertInternalType( 'float', $priorAsOf, "Has prior value" ); - $t1 = $cache->getCheckKeyTime( $cKey1 ); - $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' ); - $t2 = $cache->getCheckKeyTime( $cKey2 ); - $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' ); - - $mockWallClock += 0.2; // interim key is not brand new and check keys have past values - $priorTime = $mockWallClock; // reference time - $wasSet = 0; - $v = $cache->getWithSetCallback( - $key, 30, $func, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts - ); - $this->assertEquals( $value, $v, "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" ); - $t1 = $cache->getCheckKeyTime( $cKey1 ); - $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' ); - $t2 = $cache->getCheckKeyTime( $cKey2 ); - $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' ); - - $curTTL = null; - $v = $cache->get( $key, $curTTL, [ $cKey1, $cKey2 ] ); - if ( $versioned ) { - $this->assertEquals( $value, $v[$cache::VFLD_DATA], "Value returned" ); - } else { - $this->assertEquals( $value, $v, "Value returned" ); - } - $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" ); - - $wasSet = 0; - $key = wfRandomString(); - $v = $cache->getWithSetCallback( $key, 30, $func, [ 'pcTTL' => 5 ] + $extOpts ); - $this->assertEquals( $value, $v, "Value returned" ); - $cache->delete( $key ); - $v = $cache->getWithSetCallback( $key, 30, $func, [ 'pcTTL' => 5 ] + $extOpts ); - $this->assertEquals( $value, $v, "Value still returned after deleted" ); - $this->assertEquals( 1, $wasSet, "Value process cached while deleted" ); - - $oldValReceived = -1; - $oldAsOfReceived = -1; - $checkFunc = function ( $oldVal, &$ttl, array $setOpts, $oldAsOf ) - use ( &$oldValReceived, &$oldAsOfReceived, &$wasSet ) { - ++$wasSet; - $oldValReceived = $oldVal; - $oldAsOfReceived = $oldAsOf; - - return 'xxx' . $wasSet; - }; - - $mockWallClock = 1549343530.2053; - $priorTime = $mockWallClock; // reference time - - $wasSet = 0; - $key = wfRandomString(); - $v = $cache->getWithSetCallback( - $key, 30, $checkFunc, [ 'staleTTL' => 50 ] + $extOpts ); - $this->assertEquals( 'xxx1', $v, "Value returned" ); - $this->assertEquals( false, $oldValReceived, "Callback got no stale value" ); - $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" ); - - $mockWallClock += 40; - $v = $cache->getWithSetCallback( - $key, 30, $checkFunc, [ 'staleTTL' => 50 ] + $extOpts ); - $this->assertEquals( 'xxx2', $v, "Value still returned after expired" ); - $this->assertEquals( 2, $wasSet, "Value recalculated while expired" ); - $this->assertEquals( 'xxx1', $oldValReceived, "Callback got stale value" ); - $this->assertNotEquals( null, $oldAsOfReceived, "Callback got stale value" ); - - $mockWallClock += 260; - $v = $cache->getWithSetCallback( - $key, 30, $checkFunc, [ 'staleTTL' => 50 ] + $extOpts ); - $this->assertEquals( 'xxx3', $v, "Value still returned after expired" ); - $this->assertEquals( 3, $wasSet, "Value recalculated while expired" ); - $this->assertEquals( false, $oldValReceived, "Callback got no stale value" ); - $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" ); - - $mockWallClock = ( $priorTime - $cache::HOLDOFF_TTL - 1 ); - $wasSet = 0; - $key = wfRandomString(); - $checkKey = $cache->makeKey( 'template', 'X' ); - $cache->touchCheckKey( $checkKey ); // init check key - $mockWallClock = $priorTime; - $v = $cache->getWithSetCallback( - $key, - $cache::TTL_INDEFINITE, - $checkFunc, - [ 'graceTTL' => $cache::TTL_WEEK, 'checkKeys' => [ $checkKey ] ] + $extOpts - ); - $this->assertEquals( 'xxx1', $v, "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value computed" ); - $this->assertEquals( false, $oldValReceived, "Callback got no stale value" ); - $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" ); - - $mockWallClock += $cache::TTL_HOUR; // some time passes - $v = $cache->getWithSetCallback( - $key, - $cache::TTL_INDEFINITE, - $checkFunc, - [ 'graceTTL' => $cache::TTL_WEEK, 'checkKeys' => [ $checkKey ] ] + $extOpts - ); - $this->assertEquals( 'xxx1', $v, "Cached value returned" ); - $this->assertEquals( 1, $wasSet, "Cached value returned" ); - - $cache->touchCheckKey( $checkKey ); // make key stale - $mockWallClock += 0.01; // ~1 week left of grace (barely stale to avoid refreshes) - - $v = $cache->getWithSetCallback( - $key, - $cache::TTL_INDEFINITE, - $checkFunc, - [ 'graceTTL' => $cache::TTL_WEEK, 'checkKeys' => [ $checkKey ] ] + $extOpts - ); - $this->assertEquals( 'xxx1', $v, "Value still returned after expired (in grace)" ); - $this->assertEquals( 1, $wasSet, "Value still returned after expired (in grace)" ); - - // Chance of refresh increase to unity as staleness approaches graceTTL - $mockWallClock += $cache::TTL_WEEK; // 8 days of being stale - $v = $cache->getWithSetCallback( - $key, - $cache::TTL_INDEFINITE, - $checkFunc, - [ 'graceTTL' => $cache::TTL_WEEK, 'checkKeys' => [ $checkKey ] ] + $extOpts - ); - $this->assertEquals( 'xxx2', $v, "Value was recomputed (past grace)" ); - $this->assertEquals( 2, $wasSet, "Value was recomputed (past grace)" ); - $this->assertEquals( 'xxx1', $oldValReceived, "Callback got post-grace stale value" ); - $this->assertNotEquals( null, $oldAsOfReceived, "Callback got post-grace stale value" ); - } - - /** - * @dataProvider getWithSetCallback_provider - * @covers WANObjectCache::getWithSetCallback() - * @covers WANObjectCache::doGetWithSetCallback() - * @param array $extOpts - * @param bool $versioned - */ - function testGetWithSetcallback_touched( array $extOpts, $versioned ) { - $cache = $this->cache; - - $mockWallClock = 1549343530.2053; - $cache->setMockTime( $mockWallClock ); - - $checkFunc = function ( $oldVal, &$ttl, array $setOpts, $oldAsOf ) - use ( &$wasSet ) { - ++$wasSet; - - return 'xxx' . $wasSet; - }; - - $key = wfRandomString(); - $wasSet = 0; - $touched = null; - $touchedCallback = function () use ( &$touched ) { - return $touched; - }; - $v = $cache->getWithSetCallback( - $key, - $cache::TTL_INDEFINITE, - $checkFunc, - [ 'touchedCallback' => $touchedCallback ] + $extOpts - ); - $mockWallClock += 60; - $v = $cache->getWithSetCallback( - $key, - $cache::TTL_INDEFINITE, - $checkFunc, - [ 'touchedCallback' => $touchedCallback ] + $extOpts - ); - $this->assertEquals( 'xxx1', $v, "Value was computed once" ); - $this->assertEquals( 1, $wasSet, "Value was computed once" ); - - $touched = $mockWallClock - 10; - $v = $cache->getWithSetCallback( - $key, - $cache::TTL_INDEFINITE, - $checkFunc, - [ 'touchedCallback' => $touchedCallback ] + $extOpts - ); - $v = $cache->getWithSetCallback( - $key, - $cache::TTL_INDEFINITE, - $checkFunc, - [ 'touchedCallback' => $touchedCallback ] + $extOpts - ); - $this->assertEquals( 'xxx2', $v, "Value was recomputed once" ); - $this->assertEquals( 2, $wasSet, "Value was recomputed once" ); - } - - public static function getWithSetCallback_provider() { - return [ - [ [], false ], - [ [ 'version' => 1 ], true ] - ]; - } - - public function testPreemtiveRefresh() { - $value = 'KatCafe'; - $wasSet = 0; - $func = function ( $old, &$ttl, &$opts, $asOf ) use ( &$wasSet, &$value ) - { - ++$wasSet; - return $value; - }; - - $cache = new NearExpiringWANObjectCache( [ 'cache' => new HashBagOStuff() ] ); - $mockWallClock = 1549343530.2053; - $cache->setMockTime( $mockWallClock ); - - $wasSet = 0; - $key = wfRandomString(); - $opts = [ 'lowTTL' => 30 ]; - $v = $cache->getWithSetCallback( $key, 20, $func, $opts ); - $this->assertEquals( $value, $v, "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value calculated" ); - - $mockWallClock += 0.2; // interim key is not brand new - $v = $cache->getWithSetCallback( $key, 20, $func, $opts ); - $this->assertEquals( 2, $wasSet, "Value re-calculated" ); - - $wasSet = 0; - $key = wfRandomString(); - $opts = [ 'lowTTL' => 1 ]; - $v = $cache->getWithSetCallback( $key, 30, $func, $opts ); - $this->assertEquals( $value, $v, "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value calculated" ); - $v = $cache->getWithSetCallback( $key, 30, $func, $opts ); - $this->assertEquals( 1, $wasSet, "Value cached" ); - - $asycList = []; - $asyncHandler = function ( $callback ) use ( &$asycList ) { - $asycList[] = $callback; - }; - $cache = new NearExpiringWANObjectCache( [ - 'cache' => new HashBagOStuff(), - 'asyncHandler' => $asyncHandler - ] ); - - $mockWallClock = 1549343530.2053; - $priorTime = $mockWallClock; // reference time - $cache->setMockTime( $mockWallClock ); - - $wasSet = 0; - $key = wfRandomString(); - $opts = [ 'lowTTL' => 100 ]; - $v = $cache->getWithSetCallback( $key, 300, $func, $opts ); - $this->assertEquals( $value, $v, "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value calculated" ); - $v = $cache->getWithSetCallback( $key, 300, $func, $opts ); - $this->assertEquals( 1, $wasSet, "Cached value used" ); - $this->assertEquals( $v, $value, "Value cached" ); - - $mockWallClock += 250; - $v = $cache->getWithSetCallback( $key, 300, $func, $opts ); - $this->assertEquals( $value, $v, "Value returned" ); - $this->assertEquals( 1, $wasSet, "Stale value used" ); - $this->assertEquals( 1, count( $asycList ), "Refresh deferred." ); - $value = 'NewCatsInTown'; // change callback return value - $asycList[0](); // run the refresh callback - $asycList = []; - $this->assertEquals( 2, $wasSet, "Value calculated at later time" ); - $this->assertEquals( 0, count( $asycList ), "No deferred refreshes added." ); - $v = $cache->getWithSetCallback( $key, 300, $func, $opts ); - $this->assertEquals( $value, $v, "New value stored" ); - - $cache = new PopularityRefreshingWANObjectCache( [ - 'cache' => new HashBagOStuff() - ] ); - - $mockWallClock = $priorTime; - $cache->setMockTime( $mockWallClock ); - - $wasSet = 0; - $key = wfRandomString(); - $opts = [ 'hotTTR' => 900 ]; - $v = $cache->getWithSetCallback( $key, 60, $func, $opts ); - $this->assertEquals( $value, $v, "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value calculated" ); - - $mockWallClock += 30; - - $v = $cache->getWithSetCallback( $key, 60, $func, $opts ); - $this->assertEquals( 1, $wasSet, "Value cached" ); - - $mockWallClock = $priorTime; - $wasSet = 0; - $key = wfRandomString(); - $opts = [ 'hotTTR' => 10 ]; - $v = $cache->getWithSetCallback( $key, 60, $func, $opts ); - $this->assertEquals( $value, $v, "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value calculated" ); - - $mockWallClock += 30; - - $v = $cache->getWithSetCallback( $key, 60, $func, $opts ); - $this->assertEquals( $value, $v, "Value returned" ); - $this->assertEquals( 2, $wasSet, "Value re-calculated" ); - } - - /** - * @covers WANObjectCache::getWithSetCallback() - * @covers WANObjectCache::doGetWithSetCallback() - */ - public function testGetWithSetCallback_invalidCallback() { - $this->setExpectedException( InvalidArgumentException::class ); - $this->cache->getWithSetCallback( 'key', 30, 'invalid callback' ); - } - - /** - * @dataProvider getMultiWithSetCallback_provider - * @covers WANObjectCache::getMultiWithSetCallback - * @covers WANObjectCache::makeMultiKeys - * @covers WANObjectCache::getMulti - * @param array $extOpts - * @param bool $versioned - */ - public function testGetMultiWithSetCallback( array $extOpts, $versioned ) { - $cache = $this->cache; - - $keyA = wfRandomString(); - $keyB = wfRandomString(); - $keyC = wfRandomString(); - $cKey1 = wfRandomString(); - $cKey2 = wfRandomString(); - - $priorValue = null; - $priorAsOf = null; - $wasSet = 0; - $genFunc = function ( $id, $old, &$ttl, &$opts, $asOf ) use ( - &$wasSet, &$priorValue, &$priorAsOf - ) { - ++$wasSet; - $priorValue = $old; - $priorAsOf = $asOf; - $ttl = 20; // override with another value - return "@$id$"; - }; - - $mockWallClock = 1549343530.2053; - $priorTime = $mockWallClock; // reference time - $cache->setMockTime( $mockWallClock ); - - $wasSet = 0; - $keyedIds = new ArrayIterator( [ $keyA => 3353 ] ); - $value = "@3353$"; - $v = $cache->getMultiWithSetCallback( - $keyedIds, 30, $genFunc, [ 'lockTSE' => 5 ] + $extOpts ); - $this->assertEquals( $value, $v[$keyA], "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value regenerated" ); - $this->assertFalse( $priorValue, "No prior value" ); - $this->assertNull( $priorAsOf, "No prior value" ); - - $curTTL = null; - $cache->get( $keyA, $curTTL ); - $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' ); - $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' ); - - $wasSet = 0; - $value = "@efef$"; - $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] ); - $v = $cache->getMultiWithSetCallback( - $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5, ] + $extOpts ); - $this->assertEquals( $value, $v[$keyB], "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value regenerated" ); - $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed yet in process cache" ); - $v = $cache->getMultiWithSetCallback( - $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5, ] + $extOpts ); - $this->assertEquals( $value, $v[$keyB], "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value not regenerated" ); - $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in process cache" ); - - $mockWallClock += 1; - - $wasSet = 0; - $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] ); - $v = $cache->getMultiWithSetCallback( - $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts - ); - $this->assertEquals( $value, $v[$keyB], "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" ); - $this->assertEquals( $value, $priorValue, "Has prior value" ); - $this->assertInternalType( 'float', $priorAsOf, "Has prior value" ); - $t1 = $cache->getCheckKeyTime( $cKey1 ); - $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' ); - $t2 = $cache->getCheckKeyTime( $cKey2 ); - $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' ); - - $mockWallClock += 0.01; - $priorTime = $mockWallClock; - $value = "@43636$"; - $wasSet = 0; - $keyedIds = new ArrayIterator( [ $keyC => 43636 ] ); - $v = $cache->getMultiWithSetCallback( - $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts - ); - $this->assertEquals( $value, $v[$keyC], "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" ); - $t1 = $cache->getCheckKeyTime( $cKey1 ); - $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' ); - $t2 = $cache->getCheckKeyTime( $cKey2 ); - $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' ); - - $curTTL = null; - $v = $cache->get( $keyC, $curTTL, [ $cKey1, $cKey2 ] ); - if ( $versioned ) { - $this->assertEquals( $value, $v[$cache::VFLD_DATA], "Value returned" ); - } else { - $this->assertEquals( $value, $v, "Value returned" ); - } - $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" ); - - $wasSet = 0; - $key = wfRandomString(); - $keyedIds = new ArrayIterator( [ $key => 242424 ] ); - $v = $cache->getMultiWithSetCallback( - $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts ); - $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value returned" ); - $cache->delete( $key ); - $keyedIds = new ArrayIterator( [ $key => 242424 ] ); - $v = $cache->getMultiWithSetCallback( - $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts ); - $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value still returned after deleted" ); - $this->assertEquals( 1, $wasSet, "Value process cached while deleted" ); - - $calls = 0; - $ids = [ 1, 2, 3, 4, 5, 6 ]; - $keyFunc = function ( $id, WANObjectCache $wanCache ) { - return $wanCache->makeKey( 'test', $id ); - }; - $keyedIds = $cache->makeMultiKeys( $ids, $keyFunc ); - $genFunc = function ( $id, $oldValue, &$ttl, array &$setops ) use ( &$calls ) { - ++$calls; - - return "val-{$id}"; - }; - $values = $cache->getMultiWithSetCallback( $keyedIds, 10, $genFunc ); - - $this->assertEquals( - [ "val-1", "val-2", "val-3", "val-4", "val-5", "val-6" ], - array_values( $values ), - "Correct values in correct order" - ); - $this->assertEquals( - array_map( $keyFunc, $ids, array_fill( 0, count( $ids ), $this->cache ) ), - array_keys( $values ), - "Correct keys in correct order" - ); - $this->assertEquals( count( $ids ), $calls ); - - $cache->getMultiWithSetCallback( $keyedIds, 10, $genFunc ); - $this->assertEquals( count( $ids ), $calls, "Values cached" ); - - // Mock the BagOStuff to assure only one getMulti() call given process caching - $localBag = $this->getMockBuilder( HashBagOStuff::class ) - ->setMethods( [ 'getMulti' ] )->getMock(); - $localBag->expects( $this->exactly( 1 ) )->method( 'getMulti' )->willReturn( [ - WANObjectCache::VALUE_KEY_PREFIX . 'k1' => 'val-id1', - WANObjectCache::VALUE_KEY_PREFIX . 'k2' => 'val-id2' - ] ); - $wanCache = new WANObjectCache( [ 'cache' => $localBag ] ); - - // Warm the process cache - $keyedIds = new ArrayIterator( [ 'k1' => 'id1', 'k2' => 'id2' ] ); - $this->assertEquals( - [ 'k1' => 'val-id1', 'k2' => 'val-id2' ], - $wanCache->getMultiWithSetCallback( $keyedIds, 10, $genFunc, [ 'pcTTL' => 5 ] ) - ); - // Use the process cache - $this->assertEquals( - [ 'k1' => 'val-id1', 'k2' => 'val-id2' ], - $wanCache->getMultiWithSetCallback( $keyedIds, 10, $genFunc, [ 'pcTTL' => 5 ] ) - ); - } - - public static function getMultiWithSetCallback_provider() { - return [ - [ [], false ], - [ [ 'version' => 1 ], true ] - ]; - } - - /** - * @dataProvider getMultiWithUnionSetCallback_provider - * @covers WANObjectCache::getMultiWithUnionSetCallback() - * @covers WANObjectCache::makeMultiKeys() - * @param array $extOpts - * @param bool $versioned - */ - public function testGetMultiWithUnionSetCallback( array $extOpts, $versioned ) { - $cache = $this->cache; - - $keyA = wfRandomString(); - $keyB = wfRandomString(); - $keyC = wfRandomString(); - $cKey1 = wfRandomString(); - $cKey2 = wfRandomString(); - - $wasSet = 0; - $genFunc = function ( array $ids, array &$ttls, array &$setOpts ) use ( - &$wasSet, &$priorValue, &$priorAsOf - ) { - $newValues = []; - foreach ( $ids as $id ) { - ++$wasSet; - $newValues[$id] = "@$id$"; - $ttls[$id] = 20; // override with another value - } - - return $newValues; - }; - - $mockWallClock = 1549343530.2053; - $priorTime = $mockWallClock; // reference time - $cache->setMockTime( $mockWallClock ); - - $wasSet = 0; - $keyedIds = new ArrayIterator( [ $keyA => 3353 ] ); - $value = "@3353$"; - $v = $cache->getMultiWithUnionSetCallback( - $keyedIds, 30, $genFunc, $extOpts ); - $this->assertEquals( $value, $v[$keyA], "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value regenerated" ); - - $curTTL = null; - $cache->get( $keyA, $curTTL ); - $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' ); - $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' ); - - $wasSet = 0; - $value = "@efef$"; - $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] ); - $v = $cache->getMultiWithUnionSetCallback( - $keyedIds, 30, $genFunc, [ 'lowTTL' => 0 ] + $extOpts ); - $this->assertEquals( $value, $v[$keyB], "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value regenerated" ); - $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed yet in process cache" ); - $v = $cache->getMultiWithUnionSetCallback( - $keyedIds, 30, $genFunc, [ 'lowTTL' => 0 ] + $extOpts ); - $this->assertEquals( $value, $v[$keyB], "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value not regenerated" ); - $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in process cache" ); - - $mockWallClock += 1; - - $wasSet = 0; - $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] ); - $v = $cache->getMultiWithUnionSetCallback( - $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts - ); - $this->assertEquals( $value, $v[$keyB], "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" ); - $t1 = $cache->getCheckKeyTime( $cKey1 ); - $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' ); - $t2 = $cache->getCheckKeyTime( $cKey2 ); - $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' ); - - $mockWallClock += 0.01; - $priorTime = $mockWallClock; - $value = "@43636$"; - $wasSet = 0; - $keyedIds = new ArrayIterator( [ $keyC => 43636 ] ); - $v = $cache->getMultiWithUnionSetCallback( - $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts - ); - $this->assertEquals( $value, $v[$keyC], "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" ); - $t1 = $cache->getCheckKeyTime( $cKey1 ); - $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' ); - $t2 = $cache->getCheckKeyTime( $cKey2 ); - $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' ); - - $curTTL = null; - $v = $cache->get( $keyC, $curTTL, [ $cKey1, $cKey2 ] ); - if ( $versioned ) { - $this->assertEquals( $value, $v[$cache::VFLD_DATA], "Value returned" ); - } else { - $this->assertEquals( $value, $v, "Value returned" ); - } - $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" ); - - $wasSet = 0; - $key = wfRandomString(); - $keyedIds = new ArrayIterator( [ $key => 242424 ] ); - $v = $cache->getMultiWithUnionSetCallback( - $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts ); - $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value returned" ); - $cache->delete( $key ); - $keyedIds = new ArrayIterator( [ $key => 242424 ] ); - $v = $cache->getMultiWithUnionSetCallback( - $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts ); - $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value still returned after deleted" ); - $this->assertEquals( 1, $wasSet, "Value process cached while deleted" ); - - $calls = 0; - $ids = [ 1, 2, 3, 4, 5, 6 ]; - $keyFunc = function ( $id, WANObjectCache $wanCache ) { - return $wanCache->makeKey( 'test', $id ); - }; - $keyedIds = $cache->makeMultiKeys( $ids, $keyFunc ); - $genFunc = function ( array $ids, array &$ttls, array &$setOpts ) use ( &$calls ) { - $newValues = []; - foreach ( $ids as $id ) { - ++$calls; - $newValues[$id] = "val-{$id}"; - } - - return $newValues; - }; - $values = $cache->getMultiWithUnionSetCallback( $keyedIds, 10, $genFunc ); - - $this->assertEquals( - [ "val-1", "val-2", "val-3", "val-4", "val-5", "val-6" ], - array_values( $values ), - "Correct values in correct order" - ); - $this->assertEquals( - array_map( $keyFunc, $ids, array_fill( 0, count( $ids ), $this->cache ) ), - array_keys( $values ), - "Correct keys in correct order" - ); - $this->assertEquals( count( $ids ), $calls ); - - $cache->getMultiWithUnionSetCallback( $keyedIds, 10, $genFunc ); - $this->assertEquals( count( $ids ), $calls, "Values cached" ); - } - - public static function getMultiWithUnionSetCallback_provider() { - return [ - [ [], false ], - [ [ 'version' => 1 ], true ] - ]; - } - - /** - * @covers WANObjectCache::getWithSetCallback() - * @covers WANObjectCache::doGetWithSetCallback() - */ - public function testLockTSE() { - $cache = $this->cache; - $key = wfRandomString(); - $value = wfRandomString(); - - $mockWallClock = 1549343530.2053; - $cache->setMockTime( $mockWallClock ); - - $calls = 0; - $func = function () use ( &$calls, $value, $cache, $key ) { - ++$calls; - return $value; - }; - - $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] ); - $this->assertEquals( $value, $ret ); - $this->assertEquals( 1, $calls, 'Value was populated' ); - - // Acquire the mutex to verify that getWithSetCallback uses lockTSE properly - $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 ); - - $checkKeys = [ wfRandomString() ]; // new check keys => force misses - $ret = $cache->getWithSetCallback( $key, 30, $func, - [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] ); - $this->assertEquals( $value, $ret, 'Old value used' ); - $this->assertEquals( 1, $calls, 'Callback was not used' ); - - $cache->delete( $key ); - $mockWallClock += 0.001; // cached values will be newer than tombstone - $ret = $cache->getWithSetCallback( $key, 30, $func, - [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] ); - $this->assertEquals( $value, $ret, 'Callback was used; interim saved' ); - $this->assertEquals( 2, $calls, 'Callback was used; interim saved' ); - - $ret = $cache->getWithSetCallback( $key, 30, $func, - [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] ); - $this->assertEquals( $value, $ret, 'Callback was not used; used interim (mutex failed)' ); - $this->assertEquals( 2, $calls, 'Callback was not used; used interim (mutex failed)' ); - } - - /** - * @covers WANObjectCache::getWithSetCallback() - * @covers WANObjectCache::doGetWithSetCallback() - * @covers WANObjectCache::set() - */ - public function testLockTSESlow() { - $cache = $this->cache; - $key = wfRandomString(); - $key2 = wfRandomString(); - $value = wfRandomString(); - - $mockWallClock = 1549343530.2053; - $cache->setMockTime( $mockWallClock ); - - $calls = 0; - $func = function ( $oldValue, &$ttl, &$setOpts ) use ( &$calls, $value, &$mockWallClock ) { - ++$calls; - $setOpts['since'] = $mockWallClock - 10; - return $value; - }; - - // Value should be given a low logical TTL due to snapshot lag - $curTTL = null; - $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] ); - $this->assertEquals( $value, $ret ); - $this->assertEquals( $value, $cache->get( $key, $curTTL ), 'Value was populated' ); - $this->assertEquals( 1, $curTTL, 'Value has reduced logical TTL', 0.01 ); - $this->assertEquals( 1, $calls, 'Value was generated' ); - - $mockWallClock += 2; // low logical TTL expired - - $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] ); - $this->assertEquals( $value, $ret ); - $this->assertEquals( 2, $calls, 'Callback used (mutex acquired)' ); - - $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] ); - $this->assertEquals( $value, $ret ); - $this->assertEquals( 2, $calls, 'Callback was not used (interim value used)' ); - - $mockWallClock += 2; // low logical TTL expired - // Acquire a lock to verify that getWithSetCallback uses lockTSE properly - $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 ); - - $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] ); - $this->assertEquals( $value, $ret ); - $this->assertEquals( 2, $calls, 'Callback was not used (mutex not acquired)' ); - - $mockWallClock += 301; // physical TTL expired - // Acquire a lock to verify that getWithSetCallback uses lockTSE properly - $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 ); - - $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] ); - $this->assertEquals( $value, $ret ); - $this->assertEquals( 3, $calls, 'Callback was used (mutex not acquired, not in cache)' ); - - $calls = 0; - $func2 = function ( $oldValue, &$ttl, &$setOpts ) use ( &$calls, $value ) { - ++$calls; - $setOpts['lag'] = 15; - return $value; - }; - - // Value should be given a low logical TTL due to replication lag - $curTTL = null; - $ret = $cache->getWithSetCallback( $key2, 300, $func2, [ 'lockTSE' => 5 ] ); - $this->assertEquals( $value, $ret ); - $this->assertEquals( $value, $cache->get( $key2, $curTTL ), 'Value was populated' ); - $this->assertEquals( 30, $curTTL, 'Value has reduced logical TTL', 0.01 ); - $this->assertEquals( 1, $calls, 'Value was generated' ); - - $ret = $cache->getWithSetCallback( $key2, 300, $func2, [ 'lockTSE' => 5 ] ); - $this->assertEquals( $value, $ret ); - $this->assertEquals( 1, $calls, 'Callback was used (not expired)' ); - - $mockWallClock += 31; - - $ret = $cache->getWithSetCallback( $key2, 300, $func2, [ 'lockTSE' => 5 ] ); - $this->assertEquals( $value, $ret ); - $this->assertEquals( 2, $calls, 'Callback was used (mutex acquired)' ); - } - - /** - * @covers WANObjectCache::getWithSetCallback() - * @covers WANObjectCache::doGetWithSetCallback() - */ - public function testBusyValue() { - $cache = $this->cache; - $key = wfRandomString(); - $value = wfRandomString(); - $busyValue = wfRandomString(); - - $mockWallClock = 1549343530.2053; - $cache->setMockTime( $mockWallClock ); - - $calls = 0; - $func = function () use ( &$calls, $value, $cache, $key ) { - ++$calls; - return $value; - }; - - $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'busyValue' => $busyValue ] ); - $this->assertEquals( $value, $ret ); - $this->assertEquals( 1, $calls, 'Value was populated' ); - - $mockWallClock += 0.2; // interim keys not brand new - - // Acquire a lock to verify that getWithSetCallback uses busyValue properly - $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 ); - - $checkKeys = [ wfRandomString() ]; // new check keys => force misses - $ret = $cache->getWithSetCallback( $key, 30, $func, - [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] ); - $this->assertEquals( $value, $ret, 'Callback used' ); - $this->assertEquals( 2, $calls, 'Callback used' ); - - $ret = $cache->getWithSetCallback( $key, 30, $func, - [ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] ); - $this->assertEquals( $value, $ret, 'Old value used' ); - $this->assertEquals( 2, $calls, 'Callback was not used' ); - - $cache->delete( $key ); // no value at all anymore and still locked - $ret = $cache->getWithSetCallback( $key, 30, $func, - [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] ); - $this->assertEquals( $busyValue, $ret, 'Callback was not used; used busy value' ); - $this->assertEquals( 2, $calls, 'Callback was not used; used busy value' ); - - $this->internalCache->delete( $cache::MUTEX_KEY_PREFIX . $key ); - $mockWallClock += 0.001; // cached values will be newer than tombstone - $ret = $cache->getWithSetCallback( $key, 30, $func, - [ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] ); - $this->assertEquals( $value, $ret, 'Callback was used; saved interim' ); - $this->assertEquals( 3, $calls, 'Callback was used; saved interim' ); - - $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 ); - $ret = $cache->getWithSetCallback( $key, 30, $func, - [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] ); - $this->assertEquals( $value, $ret, 'Callback was not used; used interim' ); - $this->assertEquals( 3, $calls, 'Callback was not used; used interim' ); - } - - /** - * @covers WANObjectCache::getMulti() - */ - public function testGetMulti() { - $cache = $this->cache; - - $value1 = [ 'this' => 'is', 'a' => 'test' ]; - $value2 = [ 'this' => 'is', 'another' => 'test' ]; - - $key1 = wfRandomString(); - $key2 = wfRandomString(); - $key3 = wfRandomString(); - - $mockWallClock = 1549343530.2053; - $priorTime = $mockWallClock; // reference time - $cache->setMockTime( $mockWallClock ); - - $cache->set( $key1, $value1, 5 ); - $cache->set( $key2, $value2, 10 ); - - $curTTLs = []; - $this->assertEquals( - [ $key1 => $value1, $key2 => $value2 ], - $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs ), - 'Result array populated' - ); - - $this->assertEquals( 2, count( $curTTLs ), "Two current TTLs in array" ); - $this->assertGreaterThan( 0, $curTTLs[$key1], "Key 1 has current TTL > 0" ); - $this->assertGreaterThan( 0, $curTTLs[$key2], "Key 2 has current TTL > 0" ); - - $cKey1 = wfRandomString(); - $cKey2 = wfRandomString(); - - $mockWallClock += 1; - - $curTTLs = []; - $this->assertEquals( - [ $key1 => $value1, $key2 => $value2 ], - $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs, [ $cKey1, $cKey2 ] ), - "Result array populated even with new check keys" - ); - $t1 = $cache->getCheckKeyTime( $cKey1 ); - $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key 1 generated on miss' ); - $t2 = $cache->getCheckKeyTime( $cKey2 ); - $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check key 2 generated on miss' ); - $this->assertEquals( 2, count( $curTTLs ), "Current TTLs array set" ); - $this->assertLessThanOrEqual( 0, $curTTLs[$key1], 'Key 1 has current TTL <= 0' ); - $this->assertLessThanOrEqual( 0, $curTTLs[$key2], 'Key 2 has current TTL <= 0' ); - - $mockWallClock += 1; - - $curTTLs = []; - $this->assertEquals( - [ $key1 => $value1, $key2 => $value2 ], - $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs, [ $cKey1, $cKey2 ] ), - "Result array still populated even with new check keys" - ); - $this->assertEquals( 2, count( $curTTLs ), "Current TTLs still array set" ); - $this->assertLessThan( 0, $curTTLs[$key1], 'Key 1 has negative current TTL' ); - $this->assertLessThan( 0, $curTTLs[$key2], 'Key 2 has negative current TTL' ); - } - - /** - * @covers WANObjectCache::getMulti() - * @covers WANObjectCache::processCheckKeys() - */ - public function testGetMultiCheckKeys() { - $cache = $this->cache; - - $checkAll = wfRandomString(); - $check1 = wfRandomString(); - $check2 = wfRandomString(); - $check3 = wfRandomString(); - $value1 = wfRandomString(); - $value2 = wfRandomString(); - - $mockWallClock = 1549343530.2053; - $cache->setMockTime( $mockWallClock ); - - // Fake initial check key to be set in the past. Otherwise we'd have to sleep for - // several seconds during the test to assert the behaviour. - foreach ( [ $checkAll, $check1, $check2 ] as $checkKey ) { - $cache->touchCheckKey( $checkKey, WANObjectCache::HOLDOFF_NONE ); - } - - $mockWallClock += 0.100; - - $cache->set( 'key1', $value1, 10 ); - $cache->set( 'key2', $value2, 10 ); - - $curTTLs = []; - $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [ - 'key1' => $check1, - $checkAll, - 'key2' => $check2, - 'key3' => $check3, - ] ); - $this->assertEquals( - [ 'key1' => $value1, 'key2' => $value2 ], - $result, - 'Initial values' - ); - $this->assertGreaterThanOrEqual( 9.5, $curTTLs['key1'], 'Initial ttls' ); - $this->assertLessThanOrEqual( 10.5, $curTTLs['key1'], 'Initial ttls' ); - $this->assertGreaterThanOrEqual( 9.5, $curTTLs['key2'], 'Initial ttls' ); - $this->assertLessThanOrEqual( 10.5, $curTTLs['key2'], 'Initial ttls' ); - - $mockWallClock += 0.100; - $cache->touchCheckKey( $check1 ); - - $curTTLs = []; - $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [ - 'key1' => $check1, - $checkAll, - 'key2' => $check2, - 'key3' => $check3, - ] ); - $this->assertEquals( - [ 'key1' => $value1, 'key2' => $value2 ], - $result, - 'key1 expired by check1, but value still provided' - ); - $this->assertLessThan( 0, $curTTLs['key1'], 'key1 TTL expired' ); - $this->assertGreaterThan( 0, $curTTLs['key2'], 'key2 still valid' ); - - $cache->touchCheckKey( $checkAll ); - - $curTTLs = []; - $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [ - 'key1' => $check1, - $checkAll, - 'key2' => $check2, - 'key3' => $check3, - ] ); - $this->assertEquals( - [ 'key1' => $value1, 'key2' => $value2 ], - $result, - 'All keys expired by checkAll, but value still provided' - ); - $this->assertLessThan( 0, $curTTLs['key1'], 'key1 expired by checkAll' ); - $this->assertLessThan( 0, $curTTLs['key2'], 'key2 expired by checkAll' ); - } - - /** - * @covers WANObjectCache::get() - * @covers WANObjectCache::processCheckKeys() - */ - public function testCheckKeyInitHoldoff() { - $cache = $this->cache; - - for ( $i = 0; $i < 500; ++$i ) { - $key = wfRandomString(); - $checkKey = wfRandomString(); - // miss, set, hit - $cache->get( $key, $curTTL, [ $checkKey ] ); - $cache->set( $key, 'val', 10 ); - $curTTL = null; - $v = $cache->get( $key, $curTTL, [ $checkKey ] ); - - $this->assertEquals( 'val', $v ); - $this->assertLessThan( 0, $curTTL, "Step $i: CTL < 0 (miss/set/hit)" ); - } - - for ( $i = 0; $i < 500; ++$i ) { - $key = wfRandomString(); - $checkKey = wfRandomString(); - // set, hit - $cache->set( $key, 'val', 10 ); - $curTTL = null; - $v = $cache->get( $key, $curTTL, [ $checkKey ] ); - - $this->assertEquals( 'val', $v ); - $this->assertLessThan( 0, $curTTL, "Step $i: CTL < 0 (set/hit)" ); - } - } - - /** - * @covers WANObjectCache::delete - * @covers WANObjectCache::relayDelete - * @covers WANObjectCache::relayPurge - */ - public function testDelete() { - $key = wfRandomString(); - $value = wfRandomString(); - $this->cache->set( $key, $value ); - - $curTTL = null; - $v = $this->cache->get( $key, $curTTL ); - $this->assertEquals( $value, $v, "Key was created with value" ); - $this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" ); - - $this->cache->delete( $key ); - - $curTTL = null; - $v = $this->cache->get( $key, $curTTL ); - $this->assertFalse( $v, "Deleted key has false value" ); - $this->assertLessThan( 0, $curTTL, "Deleted key has current TTL < 0" ); - - $this->cache->set( $key, $value . 'more' ); - $v = $this->cache->get( $key, $curTTL ); - $this->assertFalse( $v, "Deleted key is tombstoned and has false value" ); - $this->assertLessThan( 0, $curTTL, "Deleted key is tombstoned and has current TTL < 0" ); - - $this->cache->set( $key, $value ); - $this->cache->delete( $key, WANObjectCache::HOLDOFF_NONE ); - - $curTTL = null; - $v = $this->cache->get( $key, $curTTL ); - $this->assertFalse( $v, "Deleted key has false value" ); - $this->assertNull( $curTTL, "Deleted key has null current TTL" ); - - $this->cache->set( $key, $value ); - $v = $this->cache->get( $key, $curTTL ); - $this->assertEquals( $value, $v, "Key was created with value" ); - $this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" ); - } - - /** - * @dataProvider getWithSetCallback_versions_provider - * @covers WANObjectCache::getWithSetCallback() - * @covers WANObjectCache::doGetWithSetCallback() - * @param array $extOpts - * @param bool $versioned - */ - public function testGetWithSetCallback_versions( array $extOpts, $versioned ) { - $cache = $this->cache; - - $key = wfRandomString(); - $valueV1 = wfRandomString(); - $valueV2 = [ wfRandomString() ]; - - $wasSet = 0; - $funcV1 = function () use ( &$wasSet, $valueV1 ) { - ++$wasSet; - - return $valueV1; - }; - - $priorValue = false; - $priorAsOf = null; - $funcV2 = function ( $oldValue, &$ttl, $setOpts, $oldAsOf ) - use ( &$wasSet, $valueV2, &$priorValue, &$priorAsOf ) { - $priorValue = $oldValue; - $priorAsOf = $oldAsOf; - ++$wasSet; - - return $valueV2; // new array format - }; - - // Set the main key (version N if versioned) - $wasSet = 0; - $v = $cache->getWithSetCallback( $key, 30, $funcV1, $extOpts ); - $this->assertEquals( $valueV1, $v, "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value regenerated" ); - $cache->getWithSetCallback( $key, 30, $funcV1, $extOpts ); - $this->assertEquals( 1, $wasSet, "Value not regenerated" ); - $this->assertEquals( $valueV1, $v, "Value not regenerated" ); - - if ( $versioned ) { - // Set the key for version N+1 format - $verOpts = [ 'version' => $extOpts['version'] + 1 ]; - } else { - // Start versioning now with the unversioned key still there - $verOpts = [ 'version' => 1 ]; - } - - // Value goes to secondary key since V1 already used $key - $wasSet = 0; - $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts ); - $this->assertEquals( $valueV2, $v, "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value regenerated" ); - $this->assertEquals( false, $priorValue, "Old value not given due to old format" ); - $this->assertEquals( null, $priorAsOf, "Old value not given due to old format" ); - - $wasSet = 0; - $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts ); - $this->assertEquals( $valueV2, $v, "Value not regenerated (secondary key)" ); - $this->assertEquals( 0, $wasSet, "Value not regenerated (secondary key)" ); - - // Clear out the older or unversioned key - $cache->delete( $key, 0 ); - - // Set the key for next/first versioned format - $wasSet = 0; - $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts ); - $this->assertEquals( $valueV2, $v, "Value returned" ); - $this->assertEquals( 1, $wasSet, "Value regenerated" ); - - $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts ); - $this->assertEquals( $valueV2, $v, "Value not regenerated (main key)" ); - $this->assertEquals( 1, $wasSet, "Value not regenerated (main key)" ); - } - - public static function getWithSetCallback_versions_provider() { - return [ - [ [], false ], - [ [ 'version' => 1 ], true ] - ]; - } - - /** - * @covers WANObjectCache::useInterimHoldOffCaching - * @covers WANObjectCache::getInterimValue - */ - public function testInterimHoldOffCaching() { - $cache = $this->cache; - - $mockWallClock = 1549343530.2053; - $cache->setMockTime( $mockWallClock ); - - $value = 'CRL-40-940'; - $wasCalled = 0; - $func = function () use ( &$wasCalled, $value ) { - $wasCalled++; - - return $value; - }; - - $cache->useInterimHoldOffCaching( true ); - - $key = wfRandomString( 32 ); - $v = $cache->getWithSetCallback( $key, 60, $func ); - $v = $cache->getWithSetCallback( $key, 60, $func ); - $this->assertEquals( 1, $wasCalled, 'Value cached' ); - - $cache->delete( $key ); - $mockWallClock += 0.001; // cached values will be newer than tombstone - $v = $cache->getWithSetCallback( $key, 60, $func ); - $this->assertEquals( 2, $wasCalled, 'Value regenerated (got mutex)' ); // sets interim - $v = $cache->getWithSetCallback( $key, 60, $func ); - $this->assertEquals( 2, $wasCalled, 'Value interim cached' ); // reuses interim - - $mockWallClock += 0.2; // interim key not brand new - $v = $cache->getWithSetCallback( $key, 60, $func ); - $this->assertEquals( 3, $wasCalled, 'Value regenerated (got mutex)' ); // sets interim - // Lock up the mutex so interim cache is used - $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 ); - $v = $cache->getWithSetCallback( $key, 60, $func ); - $this->assertEquals( 3, $wasCalled, 'Value interim cached (failed mutex)' ); - $this->internalCache->delete( $cache::MUTEX_KEY_PREFIX . $key ); - - $cache->useInterimHoldOffCaching( false ); - - $wasCalled = 0; - $key = wfRandomString( 32 ); - $v = $cache->getWithSetCallback( $key, 60, $func ); - $v = $cache->getWithSetCallback( $key, 60, $func ); - $this->assertEquals( 1, $wasCalled, 'Value cached' ); - $cache->delete( $key ); - $v = $cache->getWithSetCallback( $key, 60, $func ); - $this->assertEquals( 2, $wasCalled, 'Value regenerated (got mutex)' ); - $v = $cache->getWithSetCallback( $key, 60, $func ); - $this->assertEquals( 3, $wasCalled, 'Value still regenerated (got mutex)' ); - $v = $cache->getWithSetCallback( $key, 60, $func ); - $this->assertEquals( 4, $wasCalled, 'Value still regenerated (got mutex)' ); - // Lock up the mutex so interim cache is used - $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 ); - $v = $cache->getWithSetCallback( $key, 60, $func ); - $this->assertEquals( 5, $wasCalled, 'Value still regenerated (failed mutex)' ); - } - - /** - * @covers WANObjectCache::touchCheckKey - * @covers WANObjectCache::resetCheckKey - * @covers WANObjectCache::getCheckKeyTime - * @covers WANObjectCache::getMultiCheckKeyTime - * @covers WANObjectCache::makePurgeValue - * @covers WANObjectCache::parsePurgeValue - */ - public function testTouchKeys() { - $cache = $this->cache; - $key = wfRandomString(); - - $mockWallClock = 1549343530.2053; - $priorTime = $mockWallClock; // reference time - $cache->setMockTime( $mockWallClock ); - - $mockWallClock += 0.100; - $t0 = $cache->getCheckKeyTime( $key ); - $this->assertGreaterThanOrEqual( $priorTime, $t0, 'Check key auto-created' ); - - $priorTime = $mockWallClock; - $mockWallClock += 0.100; - $cache->touchCheckKey( $key ); - $t1 = $cache->getCheckKeyTime( $key ); - $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key created' ); - - $t2 = $cache->getCheckKeyTime( $key ); - $this->assertEquals( $t1, $t2, 'Check key time did not change' ); - - $mockWallClock += 0.100; - $cache->touchCheckKey( $key ); - $t3 = $cache->getCheckKeyTime( $key ); - $this->assertGreaterThan( $t2, $t3, 'Check key time increased' ); - - $t4 = $cache->getCheckKeyTime( $key ); - $this->assertEquals( $t3, $t4, 'Check key time did not change' ); - - $mockWallClock += 0.100; - $cache->resetCheckKey( $key ); - $t5 = $cache->getCheckKeyTime( $key ); - $this->assertGreaterThan( $t4, $t5, 'Check key time increased' ); - - $t6 = $cache->getCheckKeyTime( $key ); - $this->assertEquals( $t5, $t6, 'Check key time did not change' ); - } - - /** - * @covers WANObjectCache::getMulti() - */ - public function testGetWithSeveralCheckKeys() { - $key = wfRandomString(); - $tKey1 = wfRandomString(); - $tKey2 = wfRandomString(); - $value = 'meow'; - - $mockWallClock = 1549343530.2053; - $priorTime = $mockWallClock; // reference time - $this->cache->setMockTime( $mockWallClock ); - - // Two check keys are newer (given hold-off) than $key, another is older - $this->internalCache->set( - WANObjectCache::TIME_KEY_PREFIX . $tKey2, - WANObjectCache::PURGE_VAL_PREFIX . ( $priorTime - 3 ) - ); - $this->internalCache->set( - WANObjectCache::TIME_KEY_PREFIX . $tKey2, - WANObjectCache::PURGE_VAL_PREFIX . ( $priorTime - 5 ) - ); - $this->internalCache->set( - WANObjectCache::TIME_KEY_PREFIX . $tKey1, - WANObjectCache::PURGE_VAL_PREFIX . ( $priorTime - 30 ) - ); - $this->cache->set( $key, $value, 30 ); - - $curTTL = null; - $v = $this->cache->get( $key, $curTTL, [ $tKey1, $tKey2 ] ); - $this->assertEquals( $value, $v, "Value matches" ); - $this->assertLessThan( -4.9, $curTTL, "Correct CTL" ); - $this->assertGreaterThan( -5.1, $curTTL, "Correct CTL" ); - } - - /** - * @covers WANObjectCache::reap() - * @covers WANObjectCache::reapCheckKey() - */ - public function testReap() { - $vKey1 = wfRandomString(); - $vKey2 = wfRandomString(); - $tKey1 = wfRandomString(); - $tKey2 = wfRandomString(); - $value = 'moo'; - - $knownPurge = time() - 60; - $goodTime = microtime( true ) - 5; - $badTime = microtime( true ) - 300; - - $this->internalCache->set( - WANObjectCache::VALUE_KEY_PREFIX . $vKey1, - [ - WANObjectCache::FLD_VERSION => WANObjectCache::VERSION, - WANObjectCache::FLD_VALUE => $value, - WANObjectCache::FLD_TTL => 3600, - WANObjectCache::FLD_TIME => $goodTime - ] - ); - $this->internalCache->set( - WANObjectCache::VALUE_KEY_PREFIX . $vKey2, - [ - WANObjectCache::FLD_VERSION => WANObjectCache::VERSION, - WANObjectCache::FLD_VALUE => $value, - WANObjectCache::FLD_TTL => 3600, - WANObjectCache::FLD_TIME => $badTime - ] - ); - $this->internalCache->set( - WANObjectCache::TIME_KEY_PREFIX . $tKey1, - WANObjectCache::PURGE_VAL_PREFIX . $goodTime - ); - $this->internalCache->set( - WANObjectCache::TIME_KEY_PREFIX . $tKey2, - WANObjectCache::PURGE_VAL_PREFIX . $badTime - ); - - $this->assertEquals( $value, $this->cache->get( $vKey1 ) ); - $this->assertEquals( $value, $this->cache->get( $vKey2 ) ); - $this->cache->reap( $vKey1, $knownPurge, $bad1 ); - $this->cache->reap( $vKey2, $knownPurge, $bad2 ); - - $this->assertFalse( $bad1 ); - $this->assertTrue( $bad2 ); - - $this->cache->reapCheckKey( $tKey1, $knownPurge, $tBad1 ); - $this->cache->reapCheckKey( $tKey2, $knownPurge, $tBad2 ); - $this->assertFalse( $tBad1 ); - $this->assertTrue( $tBad2 ); - } - - /** - * @covers WANObjectCache::reap() - */ - public function testReap_fail() { - $backend = $this->getMockBuilder( EmptyBagOStuff::class ) - ->setMethods( [ 'get', 'changeTTL' ] )->getMock(); - $backend->expects( $this->once() )->method( 'get' ) - ->willReturn( [ - WANObjectCache::FLD_VERSION => WANObjectCache::VERSION, - WANObjectCache::FLD_VALUE => 'value', - WANObjectCache::FLD_TTL => 3600, - WANObjectCache::FLD_TIME => 300, - ] ); - $backend->expects( $this->once() )->method( 'changeTTL' ) - ->willReturn( false ); - - $wanCache = new WANObjectCache( [ - 'cache' => $backend - ] ); - - $isStale = null; - $ret = $wanCache->reap( 'key', 360, $isStale ); - $this->assertTrue( $isStale, 'value was stale' ); - $this->assertFalse( $ret, 'changeTTL failed' ); - } - - /** - * @covers WANObjectCache::set() - */ - public function testSetWithLag() { - $value = 1; - - $key = wfRandomString(); - $opts = [ 'lag' => 300, 'since' => microtime( true ) ]; - $this->cache->set( $key, $value, 30, $opts ); - $this->assertEquals( $value, $this->cache->get( $key ), "Rep-lagged value written." ); - - $key = wfRandomString(); - $opts = [ 'lag' => 0, 'since' => microtime( true ) - 300 ]; - $this->cache->set( $key, $value, 30, $opts ); - $this->assertEquals( false, $this->cache->get( $key ), "Trx-lagged value not written." ); - - $key = wfRandomString(); - $opts = [ 'lag' => 5, 'since' => microtime( true ) - 5 ]; - $this->cache->set( $key, $value, 30, $opts ); - $this->assertEquals( false, $this->cache->get( $key ), "Lagged value not written." ); - } - - /** - * @covers WANObjectCache::set() - */ - public function testWritePending() { - $value = 1; - - $key = wfRandomString(); - $opts = [ 'pending' => true ]; - $this->cache->set( $key, $value, 30, $opts ); - $this->assertEquals( false, $this->cache->get( $key ), "Pending value not written." ); - } - - public function testMcRouterSupport() { - $localBag = $this->getMockBuilder( EmptyBagOStuff::class ) - ->setMethods( [ 'set', 'delete' ] )->getMock(); - $localBag->expects( $this->never() )->method( 'set' ); - $localBag->expects( $this->never() )->method( 'delete' ); - $wanCache = new WANObjectCache( [ - 'cache' => $localBag, - 'mcrouterAware' => true, - 'region' => 'pmtpa', - 'cluster' => 'mw-wan' - ] ); - $valFunc = function () { - return 1; - }; - - // None of these should use broadcasting commands (e.g. SET, DELETE) - $wanCache->get( 'x' ); - $wanCache->get( 'x', $ctl, [ 'check1' ] ); - $wanCache->getMulti( [ 'x', 'y' ] ); - $wanCache->getMulti( [ 'x', 'y' ], $ctls, [ 'check2' ] ); - $wanCache->getWithSetCallback( 'p', 30, $valFunc ); - $wanCache->getCheckKeyTime( 'zzz' ); - $wanCache->reap( 'x', time() - 300 ); - $wanCache->reap( 'zzz', time() - 300 ); - } - - public function testMcRouterSupportBroadcastDelete() { - $localBag = $this->getMockBuilder( EmptyBagOStuff::class ) - ->setMethods( [ 'set' ] )->getMock(); - $wanCache = new WANObjectCache( [ - 'cache' => $localBag, - 'mcrouterAware' => true, - 'region' => 'pmtpa', - 'cluster' => 'mw-wan' - ] ); - - $localBag->expects( $this->once() )->method( 'set' ) - ->with( "/*/mw-wan/" . $wanCache::VALUE_KEY_PREFIX . "test" ); - - $wanCache->delete( 'test' ); - } - - public function testMcRouterSupportBroadcastTouchCK() { - $localBag = $this->getMockBuilder( EmptyBagOStuff::class ) - ->setMethods( [ 'set' ] )->getMock(); - $wanCache = new WANObjectCache( [ - 'cache' => $localBag, - 'mcrouterAware' => true, - 'region' => 'pmtpa', - 'cluster' => 'mw-wan' - ] ); - - $localBag->expects( $this->once() )->method( 'set' ) - ->with( "/*/mw-wan/" . $wanCache::TIME_KEY_PREFIX . "test" ); - - $wanCache->touchCheckKey( 'test' ); - } - - public function testMcRouterSupportBroadcastResetCK() { - $localBag = $this->getMockBuilder( EmptyBagOStuff::class ) - ->setMethods( [ 'delete' ] )->getMock(); - $wanCache = new WANObjectCache( [ - 'cache' => $localBag, - 'mcrouterAware' => true, - 'region' => 'pmtpa', - 'cluster' => 'mw-wan' - ] ); - - $localBag->expects( $this->once() )->method( 'delete' ) - ->with( "/*/mw-wan/" . $wanCache::TIME_KEY_PREFIX . "test" ); - - $wanCache->resetCheckKey( 'test' ); - } - - public function testEpoch() { - $bag = new HashBagOStuff(); - $cache = new WANObjectCache( [ 'cache' => $bag ] ); - $key = $cache->makeGlobalKey( 'The whole of the Law' ); - - $now = microtime( true ); - $cache->setMockTime( $now ); - - $cache->set( $key, 'Do what thou Wilt' ); - $cache->touchCheckKey( $key ); - - $then = $now; - $now += 30; - $this->assertEquals( 'Do what thou Wilt', $cache->get( $key ) ); - $this->assertEquals( $then, $cache->getCheckKeyTime( $key ), 'Check key init', 0.01 ); - - $cache = new WANObjectCache( [ - 'cache' => $bag, - 'epoch' => $now - 3600 - ] ); - $cache->setMockTime( $now ); - - $this->assertEquals( 'Do what thou Wilt', $cache->get( $key ) ); - $this->assertEquals( $then, $cache->getCheckKeyTime( $key ), 'Check key kept', 0.01 ); - - $now += 30; - $cache = new WANObjectCache( [ - 'cache' => $bag, - 'epoch' => $now + 3600 - ] ); - $cache->setMockTime( $now ); - - $this->assertFalse( $cache->get( $key ), 'Key rejected due to epoch' ); - $this->assertEquals( $now, $cache->getCheckKeyTime( $key ), 'Check key reset', 0.01 ); - } - - /** - * @dataProvider provideAdaptiveTTL - * @covers WANObjectCache::adaptiveTTL() - * @param float|int $ago - * @param int $maxTTL - * @param int $minTTL - * @param float $factor - * @param int $adaptiveTTL - */ - public function testAdaptiveTTL( $ago, $maxTTL, $minTTL, $factor, $adaptiveTTL ) { - $mtime = $ago ? time() - $ago : $ago; - $margin = 5; - $ttl = $this->cache->adaptiveTTL( $mtime, $maxTTL, $minTTL, $factor ); - - $this->assertGreaterThanOrEqual( $adaptiveTTL - $margin, $ttl ); - $this->assertLessThanOrEqual( $adaptiveTTL + $margin, $ttl ); - - $ttl = $this->cache->adaptiveTTL( (string)$mtime, $maxTTL, $minTTL, $factor ); - - $this->assertGreaterThanOrEqual( $adaptiveTTL - $margin, $ttl ); - $this->assertLessThanOrEqual( $adaptiveTTL + $margin, $ttl ); - } - - public static function provideAdaptiveTTL() { - return [ - [ 3600, 900, 30, 0.2, 720 ], - [ 3600, 500, 30, 0.2, 500 ], - [ 3600, 86400, 800, 0.2, 800 ], - [ false, 86400, 800, 0.2, 800 ], - [ null, 86400, 800, 0.2, 800 ] - ]; - } - - /** - * @covers WANObjectCache::__construct - * @covers WANObjectCache::newEmpty - */ - public function testNewEmpty() { - $this->assertInstanceOf( - WANObjectCache::class, - WANObjectCache::newEmpty() - ); - } - - /** - * @covers WANObjectCache::setLogger - */ - public function testSetLogger() { - $this->assertSame( null, $this->cache->setLogger( new Psr\Log\NullLogger ) ); - } - - /** - * @covers WANObjectCache::getQoS - */ - public function testGetQoS() { - $backend = $this->getMockBuilder( HashBagOStuff::class ) - ->setMethods( [ 'getQoS' ] )->getMock(); - $backend->expects( $this->once() )->method( 'getQoS' ) - ->willReturn( BagOStuff::QOS_UNKNOWN ); - $wanCache = new WANObjectCache( [ 'cache' => $backend ] ); - - $this->assertSame( - $wanCache::QOS_UNKNOWN, - $wanCache->getQoS( $wanCache::ATTR_EMULATION ) - ); - } - - /** - * @covers WANObjectCache::makeKey - */ - public function testMakeKey() { - $backend = $this->getMockBuilder( HashBagOStuff::class ) - ->setMethods( [ 'makeKey' ] )->getMock(); - $backend->expects( $this->once() )->method( 'makeKey' ) - ->willReturn( 'special' ); - - $wanCache = new WANObjectCache( [ - 'cache' => $backend - ] ); - - $this->assertSame( 'special', $wanCache->makeKey( 'a', 'b' ) ); - } - - /** - * @covers WANObjectCache::makeGlobalKey - */ - public function testMakeGlobalKey() { - $backend = $this->getMockBuilder( HashBagOStuff::class ) - ->setMethods( [ 'makeGlobalKey' ] )->getMock(); - $backend->expects( $this->once() )->method( 'makeGlobalKey' ) - ->willReturn( 'special' ); - - $wanCache = new WANObjectCache( [ - 'cache' => $backend - ] ); - - $this->assertSame( 'special', $wanCache->makeGlobalKey( 'a', 'b' ) ); - } - - public static function statsKeyProvider() { - return [ - [ 'domain:page:5', 'page' ], - [ 'domain:main-key', 'main-key' ], - [ 'domain:page:history', 'page' ], - [ 'missingdomainkey', 'missingdomainkey' ] - ]; - } - - /** - * @dataProvider statsKeyProvider - * @covers WANObjectCache::determineKeyClassForStats - */ - public function testStatsKeyClass( $key, $class ) { - $wanCache = TestingAccessWrapper::newFromObject( new WANObjectCache( [ - 'cache' => new HashBagOStuff - ] ) ); - - $this->assertEquals( $class, $wanCache->determineKeyClassForStats( $key ) ); - } -} - -class NearExpiringWANObjectCache extends WANObjectCache { - const CLOCK_SKEW = 1; - - protected function worthRefreshExpiring( $curTTL, $lowTTL ) { - return ( $curTTL > 0 && ( $curTTL + self::CLOCK_SKEW ) < $lowTTL ); - } -} - -class PopularityRefreshingWANObjectCache extends WANObjectCache { - protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now ) { - return ( ( $now - $asOf ) > $timeTillRefresh ); - } -} diff --git a/tests/phpunit/includes/libs/rdbms/ChronologyProtectorTest.php b/tests/phpunit/includes/libs/rdbms/ChronologyProtectorTest.php deleted file mode 100644 index 5901bc108c..0000000000 --- a/tests/phpunit/includes/libs/rdbms/ChronologyProtectorTest.php +++ /dev/null @@ -1,81 +0,0 @@ -assertEquals( $expectedId, $cp->getClientId() ); - } - - public function clientIdProvider() { - return [ - [ - [ - 'ip' => '127.0.0.1', - 'agent' => "Totally-Not-FireFox" - ], - '', - '45e93a9c215c031d38b7c42d8e4700ca', - ], - [ - [ - 'ip' => '127.0.0.7', - 'agent' => "Totally-Not-FireFox" - ], - '', - 'b1d604117b51746c35c3df9f293c84dc' - ], - [ - [ - 'ip' => '127.0.0.1', - 'agent' => "Totally-FireFox" - ], - '', - '731b4e06a65e2346b497fc811571c4d7' - ], - [ - [ - 'ip' => '127.0.0.1', - 'agent' => "Totally-Not-FireFox" - ], - 'secret', - 'defff51ded73cd901253d874c9b2077d' - ] - ]; - } -} diff --git a/tests/phpunit/includes/libs/rdbms/TransactionProfilerTest.php b/tests/phpunit/includes/libs/rdbms/TransactionProfilerTest.php deleted file mode 100644 index 538d625cc2..0000000000 --- a/tests/phpunit/includes/libs/rdbms/TransactionProfilerTest.php +++ /dev/null @@ -1,147 +0,0 @@ -getMockBuilder( LoggerInterface::class )->getMock(); - $logger->expects( $this->exactly( 3 ) )->method( 'warning' ); - - $tp = new TransactionProfiler(); - $tp->setLogger( $logger ); - $tp->setExpectation( 'maxAffected', 100, __METHOD__ ); - - $tp->transactionWritingIn( 'srv1', 'db1', '123' ); - $tp->recordQueryCompletion( "SQL 1", microtime( true ) - 3, true, 200 ); - $tp->recordQueryCompletion( "SQL 2", microtime( true ) - 3, true, 200 ); - $tp->transactionWritingOut( 'srv1', 'db1', '123', 1, 400 ); - } - - public function testReadTime() { - $logger = $this->getMockBuilder( LoggerInterface::class )->getMock(); - // 1 per query - $logger->expects( $this->exactly( 2 ) )->method( 'warning' ); - - $tp = new TransactionProfiler(); - $tp->setLogger( $logger ); - $tp->setExpectation( 'readQueryTime', 5, __METHOD__ ); - - $tp->transactionWritingIn( 'srv1', 'db1', '123' ); - $tp->recordQueryCompletion( "SQL 1", microtime( true ) - 10, false, 1 ); - $tp->recordQueryCompletion( "SQL 2", microtime( true ) - 10, false, 1 ); - $tp->transactionWritingOut( 'srv1', 'db1', '123', 0, 0 ); - } - - public function testWriteTime() { - $logger = $this->getMockBuilder( LoggerInterface::class )->getMock(); - // 1 per query, 1 per trx, and one "sub-optimal trx" entry - $logger->expects( $this->exactly( 4 ) )->method( 'warning' ); - - $tp = new TransactionProfiler(); - $tp->setLogger( $logger ); - $tp->setExpectation( 'writeQueryTime', 5, __METHOD__ ); - - $tp->transactionWritingIn( 'srv1', 'db1', '123' ); - $tp->recordQueryCompletion( "SQL 1", microtime( true ) - 10, true, 1 ); - $tp->recordQueryCompletion( "SQL 2", microtime( true ) - 10, true, 1 ); - $tp->transactionWritingOut( 'srv1', 'db1', '123', 20, 1 ); - } - - public function testAffectedTrx() { - $logger = $this->getMockBuilder( LoggerInterface::class )->getMock(); - $logger->expects( $this->exactly( 1 ) )->method( 'warning' ); - - $tp = new TransactionProfiler(); - $tp->setLogger( $logger ); - $tp->setExpectation( 'maxAffected', 100, __METHOD__ ); - - $tp->transactionWritingIn( 'srv1', 'db1', '123' ); - $tp->transactionWritingOut( 'srv1', 'db1', '123', 1, 200 ); - } - - public function testWriteTimeTrx() { - $logger = $this->getMockBuilder( LoggerInterface::class )->getMock(); - // 1 per trx, and one "sub-optimal trx" entry - $logger->expects( $this->exactly( 2 ) )->method( 'warning' ); - - $tp = new TransactionProfiler(); - $tp->setLogger( $logger ); - $tp->setExpectation( 'writeQueryTime', 5, __METHOD__ ); - - $tp->transactionWritingIn( 'srv1', 'db1', '123' ); - $tp->transactionWritingOut( 'srv1', 'db1', '123', 10, 1 ); - } - - public function testConns() { - $logger = $this->getMockBuilder( LoggerInterface::class )->getMock(); - $logger->expects( $this->exactly( 2 ) )->method( 'warning' ); - - $tp = new TransactionProfiler(); - $tp->setLogger( $logger ); - $tp->setExpectation( 'conns', 2, __METHOD__ ); - - $tp->recordConnection( 'srv1', 'db1', false ); - $tp->recordConnection( 'srv1', 'db2', false ); - $tp->recordConnection( 'srv1', 'db3', false ); // warn - $tp->recordConnection( 'srv1', 'db4', false ); // warn - } - - public function testMasterConns() { - $logger = $this->getMockBuilder( LoggerInterface::class )->getMock(); - $logger->expects( $this->exactly( 2 ) )->method( 'warning' ); - - $tp = new TransactionProfiler(); - $tp->setLogger( $logger ); - $tp->setExpectation( 'masterConns', 2, __METHOD__ ); - - $tp->recordConnection( 'srv1', 'db1', false ); - $tp->recordConnection( 'srv1', 'db2', false ); - - $tp->recordConnection( 'srv1', 'db1', true ); - $tp->recordConnection( 'srv1', 'db2', true ); - $tp->recordConnection( 'srv1', 'db3', true ); // warn - $tp->recordConnection( 'srv1', 'db4', true ); // warn - } - - public function testReadQueryCount() { - $logger = $this->getMockBuilder( LoggerInterface::class )->getMock(); - $logger->expects( $this->exactly( 2 ) )->method( 'warning' ); - - $tp = new TransactionProfiler(); - $tp->setLogger( $logger ); - $tp->setExpectation( 'queries', 2, __METHOD__ ); - - $tp->recordQueryCompletion( "SQL 1", microtime( true ) - 0.01, false, 0 ); - $tp->recordQueryCompletion( "SQL 2", microtime( true ) - 0.01, false, 0 ); - $tp->recordQueryCompletion( "SQL 3", microtime( true ) - 0.01, false, 0 ); // warn - $tp->recordQueryCompletion( "SQL 4", microtime( true ) - 0.01, false, 0 ); // warn - } - - public function testWriteQueryCount() { - $logger = $this->getMockBuilder( LoggerInterface::class )->getMock(); - $logger->expects( $this->exactly( 2 ) )->method( 'warning' ); - - $tp = new TransactionProfiler(); - $tp->setLogger( $logger ); - $tp->setExpectation( 'writes', 2, __METHOD__ ); - - $tp->recordQueryCompletion( "SQL 1", microtime( true ) - 0.01, false, 0 ); - $tp->recordQueryCompletion( "SQL 2", microtime( true ) - 0.01, false, 0 ); - $tp->recordQueryCompletion( "SQL 3", microtime( true ) - 0.01, false, 0 ); - $tp->recordQueryCompletion( "SQL 4", microtime( true ) - 0.01, false, 0 ); - - $tp->transactionWritingIn( 'srv1', 'db1', '123' ); - $tp->recordQueryCompletion( "SQL 1w", microtime( true ) - 0.01, true, 2 ); - $tp->recordQueryCompletion( "SQL 2w", microtime( true ) - 0.01, true, 5 ); - $tp->recordQueryCompletion( "SQL 3w", microtime( true ) - 0.01, true, 3 ); - $tp->recordQueryCompletion( "SQL 4w", microtime( true ) - 0.01, true, 1 ); - $tp->transactionWritingOut( 'srv1', 'db1', '123', 1, 1 ); - } -} diff --git a/tests/phpunit/includes/libs/rdbms/connectionmanager/ConnectionManagerTest.php b/tests/phpunit/includes/libs/rdbms/connectionmanager/ConnectionManagerTest.php deleted file mode 100644 index dd86a73eca..0000000000 --- a/tests/phpunit/includes/libs/rdbms/connectionmanager/ConnectionManagerTest.php +++ /dev/null @@ -1,139 +0,0 @@ -getMockBuilder( IDatabase::class ) - ->getMock(); - } - - /** - * @return LoadBalancer|PHPUnit_Framework_MockObject_MockObject - */ - private function getLoadBalancerMock() { - $lb = $this->getMockBuilder( LoadBalancer::class ) - ->disableOriginalConstructor() - ->getMock(); - - return $lb; - } - - public function testGetReadConnection_nullGroups() { - $database = $this->getIDatabaseMock(); - $lb = $this->getLoadBalancerMock(); - - $lb->expects( $this->once() ) - ->method( 'getConnection' ) - ->with( DB_REPLICA, [ 'group1' ], 'someDbName' ) - ->will( $this->returnValue( $database ) ); - - $manager = new ConnectionManager( $lb, 'someDbName', [ 'group1' ] ); - $actual = $manager->getReadConnection(); - - $this->assertSame( $database, $actual ); - } - - public function testGetReadConnection_withGroups() { - $database = $this->getIDatabaseMock(); - $lb = $this->getLoadBalancerMock(); - - $lb->expects( $this->once() ) - ->method( 'getConnection' ) - ->with( DB_REPLICA, [ 'group2' ], 'someDbName' ) - ->will( $this->returnValue( $database ) ); - - $manager = new ConnectionManager( $lb, 'someDbName', [ 'group1' ] ); - $actual = $manager->getReadConnection( [ 'group2' ] ); - - $this->assertSame( $database, $actual ); - } - - public function testGetWriteConnection() { - $database = $this->getIDatabaseMock(); - $lb = $this->getLoadBalancerMock(); - - $lb->expects( $this->once() ) - ->method( 'getConnection' ) - ->with( DB_MASTER, [ 'group1' ], 'someDbName' ) - ->will( $this->returnValue( $database ) ); - - $manager = new ConnectionManager( $lb, 'someDbName', [ 'group1' ] ); - $actual = $manager->getWriteConnection(); - - $this->assertSame( $database, $actual ); - } - - public function testReleaseConnection() { - $database = $this->getIDatabaseMock(); - $lb = $this->getLoadBalancerMock(); - - $lb->expects( $this->once() ) - ->method( 'reuseConnection' ) - ->with( $database ) - ->will( $this->returnValue( null ) ); - - $manager = new ConnectionManager( $lb ); - $manager->releaseConnection( $database ); - } - - public function testGetReadConnectionRef_nullGroups() { - $database = $this->getIDatabaseMock(); - $lb = $this->getLoadBalancerMock(); - - $lb->expects( $this->once() ) - ->method( 'getConnectionRef' ) - ->with( DB_REPLICA, [ 'group1' ], 'someDbName' ) - ->will( $this->returnValue( $database ) ); - - $manager = new ConnectionManager( $lb, 'someDbName', [ 'group1' ] ); - $actual = $manager->getReadConnectionRef(); - - $this->assertSame( $database, $actual ); - } - - public function testGetReadConnectionRef_withGroups() { - $database = $this->getIDatabaseMock(); - $lb = $this->getLoadBalancerMock(); - - $lb->expects( $this->once() ) - ->method( 'getConnectionRef' ) - ->with( DB_REPLICA, [ 'group2' ], 'someDbName' ) - ->will( $this->returnValue( $database ) ); - - $manager = new ConnectionManager( $lb, 'someDbName', [ 'group1' ] ); - $actual = $manager->getReadConnectionRef( [ 'group2' ] ); - - $this->assertSame( $database, $actual ); - } - - public function testGetWriteConnectionRef() { - $database = $this->getIDatabaseMock(); - $lb = $this->getLoadBalancerMock(); - - $lb->expects( $this->once() ) - ->method( 'getConnectionRef' ) - ->with( DB_MASTER, [ 'group1' ], 'someDbName' ) - ->will( $this->returnValue( $database ) ); - - $manager = new ConnectionManager( $lb, 'someDbName', [ 'group1' ] ); - $actual = $manager->getWriteConnectionRef(); - - $this->assertSame( $database, $actual ); - } - -} diff --git a/tests/phpunit/includes/libs/rdbms/connectionmanager/SessionConsistentConnectionManagerTest.php b/tests/phpunit/includes/libs/rdbms/connectionmanager/SessionConsistentConnectionManagerTest.php deleted file mode 100644 index 8d7d104c1e..0000000000 --- a/tests/phpunit/includes/libs/rdbms/connectionmanager/SessionConsistentConnectionManagerTest.php +++ /dev/null @@ -1,108 +0,0 @@ -getMockBuilder( IDatabase::class ) - ->getMock(); - } - - /** - * @return LoadBalancer|PHPUnit_Framework_MockObject_MockObject - */ - private function getLoadBalancerMock() { - $lb = $this->getMockBuilder( LoadBalancer::class ) - ->disableOriginalConstructor() - ->getMock(); - - return $lb; - } - - public function testGetReadConnection() { - $database = $this->getIDatabaseMock(); - $lb = $this->getLoadBalancerMock(); - - $lb->expects( $this->once() ) - ->method( 'getConnection' ) - ->with( DB_REPLICA ) - ->will( $this->returnValue( $database ) ); - - $manager = new SessionConsistentConnectionManager( $lb ); - $actual = $manager->getReadConnection(); - - $this->assertSame( $database, $actual ); - } - - public function testGetReadConnectionReturnsWriteDbOnForceMatser() { - $database = $this->getIDatabaseMock(); - $lb = $this->getLoadBalancerMock(); - - $lb->expects( $this->once() ) - ->method( 'getConnection' ) - ->with( DB_MASTER ) - ->will( $this->returnValue( $database ) ); - - $manager = new SessionConsistentConnectionManager( $lb ); - $manager->prepareForUpdates(); - $actual = $manager->getReadConnection(); - - $this->assertSame( $database, $actual ); - } - - public function testGetWriteConnection() { - $database = $this->getIDatabaseMock(); - $lb = $this->getLoadBalancerMock(); - - $lb->expects( $this->once() ) - ->method( 'getConnection' ) - ->with( DB_MASTER ) - ->will( $this->returnValue( $database ) ); - - $manager = new SessionConsistentConnectionManager( $lb ); - $actual = $manager->getWriteConnection(); - - $this->assertSame( $database, $actual ); - } - - public function testForceMaster() { - $database = $this->getIDatabaseMock(); - $lb = $this->getLoadBalancerMock(); - - $lb->expects( $this->once() ) - ->method( 'getConnection' ) - ->with( DB_MASTER ) - ->will( $this->returnValue( $database ) ); - - $manager = new SessionConsistentConnectionManager( $lb ); - $manager->prepareForUpdates(); - $manager->getReadConnection(); - } - - public function testReleaseConnection() { - $database = $this->getIDatabaseMock(); - $lb = $this->getLoadBalancerMock(); - - $lb->expects( $this->once() ) - ->method( 'reuseConnection' ) - ->with( $database ) - ->will( $this->returnValue( null ) ); - - $manager = new SessionConsistentConnectionManager( $lb ); - $manager->releaseConnection( $database ); - } -} diff --git a/tests/phpunit/includes/libs/rdbms/database/DBConnRefTest.php b/tests/phpunit/includes/libs/rdbms/database/DBConnRefTest.php deleted file mode 100644 index 33e5c3b3fb..0000000000 --- a/tests/phpunit/includes/libs/rdbms/database/DBConnRefTest.php +++ /dev/null @@ -1,223 +0,0 @@ -getMock( ILoadBalancer::class ); - - $lb->method( 'getConnection' )->willReturnCallback( - function () { - return $this->getDatabaseMock(); - } - ); - - $lb->method( 'getConnectionRef' )->willReturnCallback( - function () use ( $lb ) { - return $this->getDBConnRef( $lb ); - } - ); - - return $lb; - } - - /** - * @return IDatabase - */ - private function getDatabaseMock() { - $db = $this->getMockBuilder( Database::class ) - ->disableOriginalConstructor() - ->getMock(); - - $open = true; - $db->method( 'select' )->willReturnCallback( function () use ( &$open ) { - if ( !$open ) { - throw new LogicException( "Not open" ); - } - - return new FakeResultWrapper( [] ); - } ); - $db->method( 'close' )->willReturnCallback( function () use ( &$open ) { - $open = false; - - return true; - } ); - $db->method( 'isOpen' )->willReturnCallback( function () use ( &$open ) { - return $open; - } ); - $db->method( 'open' )->willReturnCallback( function () use ( &$open ) { - $open = true; - - return $open; - } ); - $db->method( '__toString' )->willReturn( 'MOCK_DB' ); - - return $db; - } - - /** - * @return IDatabase - */ - private function getDBConnRef( ILoadBalancer $lb = null ) { - $lb = $lb ?: $this->getLoadBalancerMock(); - return new DBConnRef( $lb, $this->getDatabaseMock(), DB_MASTER ); - } - - public function testConstruct() { - $lb = $this->getLoadBalancerMock(); - $ref = new DBConnRef( $lb, $this->getDatabaseMock(), DB_MASTER ); - - $this->assertInstanceOf( ResultWrapper::class, $ref->select( 'whatever', '*' ) ); - } - - public function testConstruct_params() { - $lb = $this->getMock( ILoadBalancer::class ); - - $lb->expects( $this->once() ) - ->method( 'getConnection' ) - ->with( DB_MASTER, [ 'test' ], 'dummy', ILoadBalancer::CONN_TRX_AUTOCOMMIT ) - ->willReturnCallback( - function () { - return $this->getDatabaseMock(); - } - ); - - $ref = new DBConnRef( - $lb, - [ DB_MASTER, [ 'test' ], 'dummy', ILoadBalancer::CONN_TRX_AUTOCOMMIT ], - DB_MASTER - ); - - $this->assertInstanceOf( ResultWrapper::class, $ref->select( 'whatever', '*' ) ); - $this->assertEquals( DB_MASTER, $ref->getReferenceRole() ); - - $ref2 = new DBConnRef( - $lb, - [ DB_MASTER, [ 'test' ], 'dummy', ILoadBalancer::CONN_TRX_AUTOCOMMIT ], - DB_REPLICA - ); - $this->assertEquals( DB_REPLICA, $ref2->getReferenceRole() ); - } - - public function testDestruct() { - $lb = $this->getLoadBalancerMock(); - - $lb->expects( $this->once() ) - ->method( 'reuseConnection' ); - - $this->innerMethodForTestDestruct( $lb ); - } - - private function innerMethodForTestDestruct( ILoadBalancer $lb ) { - $ref = $lb->getConnectionRef( DB_REPLICA ); - - $this->assertInstanceOf( ResultWrapper::class, $ref->select( 'whatever', '*' ) ); - } - - public function testConstruct_failure() { - $this->setExpectedException( InvalidArgumentException::class, '' ); - - $lb = $this->getLoadBalancerMock(); - new DBConnRef( $lb, 17, DB_REPLICA ); // bad constructor argument - } - - /** - * @covers Wikimedia\Rdbms\DBConnRef::getDomainId - */ - public function testGetDomainID() { - $lb = $this->getMock( ILoadBalancer::class ); - - // getDomainID is optimized to not create a connection - $lb->expects( $this->never() ) - ->method( 'getConnection' ); - - $ref = new DBConnRef( $lb, [ DB_REPLICA, [], 'dummy', 0 ], DB_REPLICA ); - - $this->assertSame( 'dummy', $ref->getDomainID() ); - } - - /** - * @covers Wikimedia\Rdbms\DBConnRef::select - */ - public function testSelect() { - // select should get passed through normally - $ref = $this->getDBConnRef(); - $this->assertInstanceOf( ResultWrapper::class, $ref->select( 'whatever', '*' ) ); - } - - public function testToString() { - $ref = $this->getDBConnRef(); - $this->assertInternalType( 'string', $ref->__toString() ); - - $lb = $this->getLoadBalancerMock(); - $ref = new DBConnRef( $lb, [ DB_MASTER, [], 'test', 0 ], DB_MASTER ); - $this->assertInternalType( 'string', $ref->__toString() ); - } - - /** - * @covers Wikimedia\Rdbms\DBConnRef::close - * @expectedException \Wikimedia\Rdbms\DBUnexpectedError - */ - public function testClose() { - $lb = $this->getLoadBalancerMock(); - $ref = new DBConnRef( $lb, [ DB_REPLICA, [], 'dummy', 0 ], DB_MASTER ); - $ref->close(); - } - - /** - * @covers Wikimedia\Rdbms\DBConnRef::getReferenceRole - */ - public function testGetReferenceRole() { - $lb = $this->getLoadBalancerMock(); - $ref = new DBConnRef( $lb, [ DB_REPLICA, [], 'dummy', 0 ], DB_REPLICA ); - $this->assertSame( DB_REPLICA, $ref->getReferenceRole() ); - - $ref = new DBConnRef( $lb, [ DB_MASTER, [], 'dummy', 0 ], DB_MASTER ); - $this->assertSame( DB_MASTER, $ref->getReferenceRole() ); - - $ref = new DBConnRef( $lb, [ 1, [], 'dummy', 0 ], DB_REPLICA ); - $this->assertSame( DB_REPLICA, $ref->getReferenceRole() ); - - $ref = new DBConnRef( $lb, [ 0, [], 'dummy', 0 ], DB_MASTER ); - $this->assertSame( DB_MASTER, $ref->getReferenceRole() ); - } - - /** - * @covers Wikimedia\Rdbms\DBConnRef::getReferenceRole - * @expectedException Wikimedia\Rdbms\DBReadOnlyRoleError - * @dataProvider provideRoleExceptions - */ - public function testRoleExceptions( $method, $args ) { - $lb = $this->getLoadBalancerMock(); - $ref = new DBConnRef( $lb, [ DB_REPLICA, [], 'dummy', 0 ], DB_REPLICA ); - $ref->$method( ...$args ); - } - - function provideRoleExceptions() { - return [ - [ 'insert', [ 'table', [ 'a' => 1 ] ] ], - [ 'update', [ 'table', [ 'a' => 1 ], [ 'a' => 2 ] ] ], - [ 'delete', [ 'table', [ 'a' => 1 ] ] ], - [ 'replace', [ 'table', [ 'a' ], [ 'a' => 1 ] ] ], - [ 'upsert', [ 'table', [ 'a' => 1 ], [ 'a' ], [ 'a = a + 1' ] ] ], - [ 'lock', [ 'k', 'method' ] ], - [ 'unlock', [ 'k', 'method' ] ], - [ 'getScopedLockAndFlush', [ 'k', 'method', 1 ] ] - ]; - } -} diff --git a/tests/phpunit/includes/libs/rdbms/database/DatabaseDomainTest.php b/tests/phpunit/includes/libs/rdbms/database/DatabaseDomainTest.php deleted file mode 100644 index b1d4fadb7d..0000000000 --- a/tests/phpunit/includes/libs/rdbms/database/DatabaseDomainTest.php +++ /dev/null @@ -1,226 +0,0 @@ - - [ 'foo', 'bar', 'baz_', 'foo-bar-baz_' ], - 'Nothing' => - [ null, null, '', '' ], - 'Invalid $database' => - [ 0, 'bar', '', '', true ], - 'Invalid $schema' => - [ 'foo', 0, '', '', true ], - 'Invalid $prefix' => - [ 'foo', 'bar', 0, '', true ], - 'Dash' => - [ 'foo-bar', 'baz', 'baa_', 'foo?hbar-baz-baa_' ], - 'Question mark' => - [ 'foo?bar', 'baz', 'baa_', 'foo??bar-baz-baa_' ], - ]; - } - - /** - * @dataProvider provideConstruct - */ - public function testConstruct( $db, $schema, $prefix, $id, $exception = false ) { - if ( $exception ) { - $this->setExpectedException( InvalidArgumentException::class ); - new DatabaseDomain( $db, $schema, $prefix ); - return; - } - - $domain = new DatabaseDomain( $db, $schema, $prefix ); - $this->assertInstanceOf( DatabaseDomain::class, $domain ); - $this->assertEquals( $db, $domain->getDatabase() ); - $this->assertEquals( $schema, $domain->getSchema() ); - $this->assertEquals( $prefix, $domain->getTablePrefix() ); - $this->assertEquals( $id, $domain->getId() ); - $this->assertEquals( $id, strval( $domain ), 'toString' ); - } - - public static function provideNewFromId() { - return [ - 'Basic' => - [ 'foo', 'foo', null, '' ], - 'db+prefix' => - [ 'foo-bar_', 'foo', null, 'bar_' ], - 'db+schema+prefix' => - [ 'foo-bar-baz_', 'foo', 'bar', 'baz_' ], - '?h -> -' => - [ 'foo?hbar-baz-baa_', 'foo-bar', 'baz', 'baa_' ], - '?? -> ?' => - [ 'foo??bar-baz-baa_', 'foo?bar', 'baz', 'baa_' ], - '? is left alone' => - [ 'foo?bar-baz-baa_', 'foo?bar', 'baz', 'baa_' ], - 'too many parts' => - [ 'foo-bar-baz-baa_', '', '', '', true ], - 'from instance' => - [ DatabaseDomain::newUnspecified(), null, null, '' ], - ]; - } - - /** - * @dataProvider provideNewFromId - */ - public function testNewFromId( $id, $db, $schema, $prefix, $exception = false ) { - if ( $exception ) { - $this->setExpectedException( InvalidArgumentException::class ); - DatabaseDomain::newFromId( $id ); - return; - } - $domain = DatabaseDomain::newFromId( $id ); - $this->assertInstanceOf( DatabaseDomain::class, $domain ); - $this->assertEquals( $db, $domain->getDatabase() ); - $this->assertEquals( $schema, $domain->getSchema() ); - $this->assertEquals( $prefix, $domain->getTablePrefix() ); - } - - public static function provideEquals() { - return [ - 'Basic' => - [ 'foo', 'foo', null, '' ], - 'db+prefix' => - [ 'foo-bar_', 'foo', null, 'bar_' ], - 'db+schema+prefix' => - [ 'foo-bar-baz_', 'foo', 'bar', 'baz_' ], - '?h -> -' => - [ 'foo?hbar-baz-baa_', 'foo-bar', 'baz', 'baa_' ], - '?? -> ?' => - [ 'foo??bar-baz-baa_', 'foo?bar', 'baz', 'baa_' ], - 'Nothing' => - [ '', null, null, '' ], - ]; - } - - /** - * @dataProvider provideEquals - * @covers Wikimedia\Rdbms\DatabaseDomain::equals - */ - public function testEquals( $id, $db, $schema, $prefix ) { - $fromId = DatabaseDomain::newFromId( $id ); - $this->assertInstanceOf( DatabaseDomain::class, $fromId ); - - $constructed = new DatabaseDomain( $db, $schema, $prefix ); - - $this->assertTrue( $constructed->equals( $id ), 'constructed equals string' ); - $this->assertTrue( $fromId->equals( $id ), 'fromId equals string' ); - - $this->assertTrue( $constructed->equals( $fromId ), 'compare constructed to newId' ); - $this->assertTrue( $fromId->equals( $constructed ), 'compare newId to constructed' ); - } - - /** - * @covers Wikimedia\Rdbms\DatabaseDomain::newUnspecified - */ - public function testNewUnspecified() { - $domain = DatabaseDomain::newUnspecified(); - $this->assertInstanceOf( DatabaseDomain::class, $domain ); - $this->assertTrue( $domain->equals( '' ) ); - $this->assertSame( null, $domain->getDatabase() ); - $this->assertSame( null, $domain->getSchema() ); - $this->assertSame( '', $domain->getTablePrefix() ); - } - - public static function provideIsCompatible() { - return [ - 'Basic' => - [ 'foo', 'foo', null, '', true ], - 'db+prefix' => - [ 'foo-bar_', 'foo', null, 'bar_', true ], - 'db+schema+prefix' => - [ 'foo-bar-baz_', 'foo', 'bar', 'baz_', true ], - 'db+dontcare_schema+prefix' => - [ 'foo-bar-baz_', 'foo', null, 'baz_', false ], - '?h -> -' => - [ 'foo?hbar-baz-baa_', 'foo-bar', 'baz', 'baa_', true ], - '?? -> ?' => - [ 'foo??bar-baz-baa_', 'foo?bar', 'baz', 'baa_', true ], - 'Nothing' => - [ '', null, null, '', true ], - 'dontcaredb+dontcaredbschema+prefix' => - [ 'mywiki-mediawiki-prefix_', null, null, 'prefix_', false ], - 'db+dontcareschema+prefix' => - [ 'mywiki-schema-prefix_', 'mywiki', null, 'prefix_', false ], - 'postgres-db-jobqueue' => - [ 'postgres-mediawiki-', 'postgres', null, '', false ] - ]; - } - - /** - * @dataProvider provideIsCompatible - * @covers Wikimedia\Rdbms\DatabaseDomain::isCompatible - */ - public function testIsCompatible( $id, $db, $schema, $prefix, $transitive ) { - $compareIdObj = DatabaseDomain::newFromId( $id ); - $this->assertInstanceOf( DatabaseDomain::class, $compareIdObj ); - - $fromId = new DatabaseDomain( $db, $schema, $prefix ); - - $this->assertTrue( $fromId->isCompatible( $id ), 'constructed equals string' ); - $this->assertTrue( $fromId->isCompatible( $compareIdObj ), 'fromId equals string' ); - - $this->assertEquals( $transitive, $compareIdObj->isCompatible( $fromId ), - 'test transitivity of nulls components' ); - } - - public static function provideIsCompatible2() { - return [ - 'db+schema+prefix' => - [ 'mywiki-schema-prefix_', 'thatwiki', 'schema', 'prefix_' ], - 'dontcaredb+dontcaredbschema+prefix' => - [ 'thatwiki-mediawiki-otherprefix_', null, null, 'prefix_' ], - 'db+dontcareschema+prefix' => - [ 'notmywiki-schema-prefix_', 'mywiki', null, 'prefix_' ], - ]; - } - - /** - * @dataProvider provideIsCompatible2 - * @covers Wikimedia\Rdbms\DatabaseDomain::isCompatible - */ - public function testIsCompatible2( $id, $db, $schema, $prefix ) { - $compareIdObj = DatabaseDomain::newFromId( $id ); - $this->assertInstanceOf( DatabaseDomain::class, $compareIdObj ); - - $fromId = new DatabaseDomain( $db, $schema, $prefix ); - - $this->assertFalse( $fromId->isCompatible( $id ), 'constructed equals string' ); - $this->assertFalse( $fromId->isCompatible( $compareIdObj ), 'fromId equals string' ); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testSchemaWithNoDB1() { - new DatabaseDomain( null, 'schema', '' ); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testSchemaWithNoDB2() { - DatabaseDomain::newFromId( '-schema-prefix' ); - } - - /** - * @covers Wikimedia\Rdbms\DatabaseDomain::isUnspecified - */ - public function testIsUnspecified() { - $domain = new DatabaseDomain( null, null, '' ); - $this->assertTrue( $domain->isUnspecified() ); - $domain = new DatabaseDomain( 'mywiki', null, '' ); - $this->assertFalse( $domain->isUnspecified() ); - $domain = new DatabaseDomain( 'mywiki', null, '' ); - $this->assertFalse( $domain->isUnspecified() ); - } -} diff --git a/tests/phpunit/includes/libs/rdbms/database/DatabaseMssqlTest.php b/tests/phpunit/includes/libs/rdbms/database/DatabaseMssqlTest.php deleted file mode 100644 index 414042ddcf..0000000000 --- a/tests/phpunit/includes/libs/rdbms/database/DatabaseMssqlTest.php +++ /dev/null @@ -1,62 +0,0 @@ -getMockBuilder( DatabaseMssql::class ) - ->disableOriginalConstructor() - ->setMethods( null ) - ->getMock(); - } - - public function provideBuildSubstring() { - yield [ 'someField', 1, 2, 'SUBSTRING(someField,1,2)' ]; - yield [ 'someField', 1, null, 'SUBSTRING(someField,1,2147483647)' ]; - yield [ 'someField', 1, 3333333333, 'SUBSTRING(someField,1,3333333333)' ]; - } - - /** - * @covers Wikimedia\Rdbms\DatabaseMssql::buildSubstring - * @dataProvider provideBuildSubstring - */ - public function testBuildSubstring( $input, $start, $length, $expected ) { - $mockDb = $this->getMockDb(); - $output = $mockDb->buildSubstring( $input, $start, $length ); - $this->assertSame( $expected, $output ); - } - - public function provideBuildSubstring_invalidParams() { - yield [ -1, 1 ]; - yield [ 1, -1 ]; - yield [ 1, 'foo' ]; - yield [ 'foo', 1 ]; - yield [ null, 1 ]; - yield [ 0, 1 ]; - } - - /** - * @covers Wikimedia\Rdbms\DatabaseMssql::buildSubstring - * @dataProvider provideBuildSubstring_invalidParams - */ - public function testBuildSubstring_invalidParams( $start, $length ) { - $mockDb = $this->getMockDb(); - $this->setExpectedException( InvalidArgumentException::class ); - $mockDb->buildSubstring( 'foo', $start, $length ); - } - - /** - * @covers \Wikimedia\Rdbms\DatabaseMssql::getAttributes - */ - public function testAttributes() { - $this->assertTrue( DatabaseMssql::getAttributes()[Database::ATTR_SCHEMAS_AS_TABLE_GROUPS] ); - } -} diff --git a/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php b/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php deleted file mode 100644 index 4c92545128..0000000000 --- a/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php +++ /dev/null @@ -1,740 +0,0 @@ -getMockBuilder( DatabaseMysqli::class ) - ->disableOriginalConstructor() - ->setMethods( null ) - ->getMock(); - - $quoted = $db->addIdentifierQuotes( $in ); - $this->assertEquals( $expected, $quoted ); - } - - /** - * Feeds testAddIdentifierQuotes - * - * Named per T22281 convention. - */ - public static function provideDiapers() { - return [ - // Format: expected, input - [ '``', '' ], - - // Yeah I really hate loosely typed PHP idiocies nowadays - [ '``', null ], - - // Dear codereviewer, guess what addIdentifierQuotes() - // will return with thoses: - [ '``', false ], - [ '`1`', true ], - - // We never know what could happen - [ '`0`', 0 ], - [ '`1`', 1 ], - - // Whatchout! Should probably use something more meaningful - [ "`'`", "'" ], # single quote - [ '`"`', '"' ], # double quote - [ '````', '`' ], # backtick - [ '`’`', '’' ], # apostrophe (look at your encyclopedia) - - // sneaky NUL bytes are lurking everywhere - [ '``', "\0" ], - [ '`xyzzy`', "\0x\0y\0z\0z\0y\0" ], - - // unicode chars - [ - "`\u{0001}a\u{FFFF}b`", - "\u{0001}a\u{FFFF}b" - ], - [ - "`\u{0001}\u{FFFF}`", - "\u{0001}\u{0000}\u{FFFF}\u{0000}" - ], - [ '`☃`', '☃' ], - [ '`メインページ`', 'メインページ' ], - [ '`Басты_бет`', 'Басты_бет' ], - - // Real world: - [ '`Alix`', 'Alix' ], # while( ! $recovered ) { sleep(); } - [ '`Backtick: ```', 'Backtick: `' ], - [ '`This is a test`', 'This is a test' ], - ]; - } - - private function getMockForViews() { - $db = $this->getMockBuilder( DatabaseMysqli::class ) - ->disableOriginalConstructor() - ->setMethods( [ 'fetchRow', 'query', 'getDBname' ] ) - ->getMock(); - - $db->method( 'query' ) - ->with( $this->anything() ) - ->willReturn( new FakeResultWrapper( [ - (object)[ 'Tables_in_' => 'view1' ], - (object)[ 'Tables_in_' => 'view2' ], - (object)[ 'Tables_in_' => 'myview' ] - ] ) ); - $db->method( 'getDBname' )->willReturn( '' ); - - return $db; - } - - /** - * @covers Wikimedia\Rdbms\DatabaseMysqlBase::listViews - */ - public function testListviews() { - $db = $this->getMockForViews(); - - $this->assertEquals( [ 'view1', 'view2', 'myview' ], - $db->listViews() ); - - // Prefix filtering - $this->assertEquals( [ 'view1', 'view2' ], - $db->listViews( 'view' ) ); - $this->assertEquals( [ 'myview' ], - $db->listViews( 'my' ) ); - $this->assertEquals( [], - $db->listViews( 'UNUSED_PREFIX' ) ); - $this->assertEquals( [ 'view1', 'view2', 'myview' ], - $db->listViews( '' ) ); - } - - /** - * @covers Wikimedia\Rdbms\MySQLMasterPos - */ - public function testBinLogName() { - $pos = new MySQLMasterPos( "db1052.2424/4643", 1 ); - - $this->assertEquals( "db1052", $pos->getLogName() ); - $this->assertEquals( "db1052.2424", $pos->getLogFile() ); - $this->assertEquals( [ 2424, 4643 ], $pos->getLogPosition() ); - } - - /** - * @dataProvider provideComparePositions - * @covers Wikimedia\Rdbms\MySQLMasterPos - */ - public function testHasReached( - MySQLMasterPos $lowerPos, MySQLMasterPos $higherPos, $match, $hetero - ) { - if ( $match ) { - $this->assertTrue( $lowerPos->channelsMatch( $higherPos ) ); - - if ( $hetero ) { - // Each position is has one channel higher than the other - $this->assertFalse( $higherPos->hasReached( $lowerPos ) ); - } else { - $this->assertTrue( $higherPos->hasReached( $lowerPos ) ); - } - $this->assertTrue( $lowerPos->hasReached( $lowerPos ) ); - $this->assertTrue( $higherPos->hasReached( $higherPos ) ); - $this->assertFalse( $lowerPos->hasReached( $higherPos ) ); - } else { // channels don't match - $this->assertFalse( $lowerPos->channelsMatch( $higherPos ) ); - - $this->assertFalse( $higherPos->hasReached( $lowerPos ) ); - $this->assertFalse( $lowerPos->hasReached( $higherPos ) ); - } - } - - public static function provideComparePositions() { - $now = microtime( true ); - - return [ - // Binlog style - [ - new MySQLMasterPos( 'db1034-bin.000976/843431247', $now ), - new MySQLMasterPos( 'db1034-bin.000976/843431248', $now ), - true, - false - ], - [ - new MySQLMasterPos( 'db1034-bin.000976/999', $now ), - new MySQLMasterPos( 'db1034-bin.000976/1000', $now ), - true, - false - ], - [ - new MySQLMasterPos( 'db1034-bin.000976/999', $now ), - new MySQLMasterPos( 'db1035-bin.000976/1000', $now ), - false, - false - ], - // MySQL GTID style - [ - new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:1-23', $now ), - new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:5-24', $now ), - true, - false - ], - [ - new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:5-99', $now ), - new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:1-100', $now ), - true, - false - ], - [ - new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:1-99', $now ), - new MySQLMasterPos( '1E11FA47-71CA-11E1-9E33-C80AA9429562:1-100', $now ), - false, - false - ], - // MariaDB GTID style - [ - new MySQLMasterPos( '255-11-23', $now ), - new MySQLMasterPos( '255-11-24', $now ), - true, - false - ], - [ - new MySQLMasterPos( '255-11-99', $now ), - new MySQLMasterPos( '255-11-100', $now ), - true, - false - ], - [ - new MySQLMasterPos( '255-11-999', $now ), - new MySQLMasterPos( '254-11-1000', $now ), - false, - false - ], - [ - new MySQLMasterPos( '255-11-23,256-12-50', $now ), - new MySQLMasterPos( '255-11-24', $now ), - true, - false - ], - [ - new MySQLMasterPos( '255-11-99,256-12-50,257-12-50', $now ), - new MySQLMasterPos( '255-11-1000', $now ), - true, - false - ], - [ - new MySQLMasterPos( '255-11-23,256-12-50', $now ), - new MySQLMasterPos( '255-11-24,155-52-63', $now ), - true, - false - ], - [ - new MySQLMasterPos( '255-11-99,256-12-50,257-12-50', $now ), - new MySQLMasterPos( '255-11-1000,256-12-51', $now ), - true, - false - ], - [ - new MySQLMasterPos( '255-11-99,256-12-50', $now ), - new MySQLMasterPos( '255-13-1000,256-14-49', $now ), - true, - true - ], - [ - new MySQLMasterPos( '253-11-999,255-11-999', $now ), - new MySQLMasterPos( '254-11-1000', $now ), - false, - false - ], - ]; - } - - /** - * @dataProvider provideChannelPositions - * @covers Wikimedia\Rdbms\MySQLMasterPos - */ - public function testChannelsMatch( MySQLMasterPos $pos1, MySQLMasterPos $pos2, $matches ) { - $this->assertEquals( $matches, $pos1->channelsMatch( $pos2 ) ); - $this->assertEquals( $matches, $pos2->channelsMatch( $pos1 ) ); - - $roundtripPos = new MySQLMasterPos( (string)$pos1, 1 ); - $this->assertEquals( (string)$pos1, (string)$roundtripPos ); - } - - public static function provideChannelPositions() { - $now = microtime( true ); - - return [ - [ - new MySQLMasterPos( 'db1034-bin.000876/44', $now ), - new MySQLMasterPos( 'db1034-bin.000976/74', $now ), - true - ], - [ - new MySQLMasterPos( 'db1052-bin.000976/999', $now ), - new MySQLMasterPos( 'db1052-bin.000976/1000', $now ), - true - ], - [ - new MySQLMasterPos( 'db1066-bin.000976/9999', $now ), - new MySQLMasterPos( 'db1035-bin.000976/10000', $now ), - false - ], - [ - new MySQLMasterPos( 'db1066-bin.000976/9999', $now ), - new MySQLMasterPos( 'trump2016.000976/10000', $now ), - false - ], - ]; - } - - /** - * @dataProvider provideCommonDomainGTIDs - * @covers Wikimedia\Rdbms\MySQLMasterPos - */ - public function testCommonGtidDomains( MySQLMasterPos $pos, MySQLMasterPos $ref, $gtids ) { - $this->assertEquals( $gtids, MySQLMasterPos::getCommonDomainGTIDs( $pos, $ref ) ); - } - - public static function provideCommonDomainGTIDs() { - return [ - [ - new MySQLMasterPos( '255-13-99,256-12-50,257-14-50', 1 ), - new MySQLMasterPos( '255-11-1000', 1 ), - [ '255-13-99' ] - ], - [ - new MySQLMasterPos( - '2E11FA47-71CA-11E1-9E33-C80AA9429562:1-5,' . - '3E11FA47-71CA-11E1-9E33-C80AA9429562:20-99,' . - '7E11FA47-71CA-11E1-9E33-C80AA9429562:1-30', - 1 - ), - new MySQLMasterPos( - '1E11FA47-71CA-11E1-9E33-C80AA9429562:30-100,' . - '3E11FA47-71CA-11E1-9E33-C80AA9429562:30-66', - 1 - ), - [ '3E11FA47-71CA-11E1-9E33-C80AA9429562:20-99' ] - ] - ]; - } - - /** - * @dataProvider provideLagAmounts - * @covers Wikimedia\Rdbms\DatabaseMysqlBase::getLag - * @covers Wikimedia\Rdbms\DatabaseMysqlBase::getLagFromPtHeartbeat - */ - public function testPtHeartbeat( $lag ) { - $db = $this->getMockBuilder( DatabaseMysqli::class ) - ->disableOriginalConstructor() - ->setMethods( [ - 'getLagDetectionMethod', 'getHeartbeatData', 'getMasterServerInfo' ] ) - ->getMock(); - - $db->method( 'getLagDetectionMethod' ) - ->willReturn( 'pt-heartbeat' ); - - $db->method( 'getMasterServerInfo' ) - ->willReturn( [ 'serverId' => 172, 'asOf' => time() ] ); - - // Fake the current time. - list( $nowSecFrac, $nowSec ) = explode( ' ', microtime() ); - $now = (float)$nowSec + (float)$nowSecFrac; - // Fake the heartbeat time. - // Work arounds for weak DataTime microseconds support. - $ptTime = $now - $lag; - $ptSec = (int)$ptTime; - $ptSecFrac = ( $ptTime - $ptSec ); - $ptDateTime = new DateTime( "@$ptSec" ); - $ptTimeISO = $ptDateTime->format( 'Y-m-d\TH:i:s' ); - $ptTimeISO .= ltrim( number_format( $ptSecFrac, 6 ), '0' ); - - $db->method( 'getHeartbeatData' ) - ->with( [ 'server_id' => 172 ] ) - ->willReturn( [ $ptTimeISO, $now ] ); - - $db->setLBInfo( 'clusterMasterHost', 'db1052' ); - $lagEst = $db->getLag(); - - $this->assertGreaterThan( $lag - 0.010, $lagEst, "Correct heatbeat lag" ); - $this->assertLessThan( $lag + 0.010, $lagEst, "Correct heatbeat lag" ); - } - - public static function provideLagAmounts() { - return [ - [ 0 ], - [ 0.3 ], - [ 6.5 ], - [ 10.1 ], - [ 200.2 ], - [ 400.7 ], - [ 600.22 ], - [ 1000.77 ], - ]; - } - - /** - * @dataProvider provideGtidData - * @covers Wikimedia\Rdbms\MySQLMasterPos - * @covers Wikimedia\Rdbms\DatabaseMysqlBase::getReplicaPos - * @covers Wikimedia\Rdbms\DatabaseMysqlBase::getMasterPos - */ - public function testServerGtidTable( $gtable, $rBLtable, $mBLtable, $rGTIDs, $mGTIDs ) { - $db = $this->getMockBuilder( DatabaseMysqli::class ) - ->disableOriginalConstructor() - ->setMethods( [ - 'useGTIDs', - 'getServerGTIDs', - 'getServerRoleStatus', - 'getServerId', - 'getServerUUID' - ] ) - ->getMock(); - - $db->method( 'useGTIDs' )->willReturn( true ); - $db->method( 'getServerGTIDs' )->willReturn( $gtable ); - $db->method( 'getServerRoleStatus' )->willReturnCallback( - function ( $role ) use ( $rBLtable, $mBLtable ) { - if ( $role === 'SLAVE' ) { - return $rBLtable; - } elseif ( $role === 'MASTER' ) { - return $mBLtable; - } - - return null; - } - ); - $db->method( 'getServerId' )->willReturn( 1 ); - $db->method( 'getServerUUID' )->willReturn( '2E11FA47-71CA-11E1-9E33-C80AA9429562' ); - - if ( is_array( $rGTIDs ) ) { - $this->assertEquals( $rGTIDs, $db->getReplicaPos()->getGTIDs() ); - } else { - $this->assertEquals( false, $db->getReplicaPos() ); - } - if ( is_array( $mGTIDs ) ) { - $this->assertEquals( $mGTIDs, $db->getMasterPos()->getGTIDs() ); - } else { - $this->assertEquals( false, $db->getMasterPos() ); - } - } - - public static function provideGtidData() { - return [ - // MariaDB - [ - [ - 'gtid_domain_id' => 100, - 'gtid_current_pos' => '100-13-77', - 'gtid_binlog_pos' => '100-13-77', - 'gtid_slave_pos' => null // master - ], - [ - 'Relay_Master_Log_File' => 'host.1600', - 'Exec_Master_Log_Pos' => '77' - ], - [ - 'File' => 'host.1600', - 'Position' => '77' - ], - [], - [ '100' => '100-13-77' ] - ], - [ - [ - 'gtid_domain_id' => 100, - 'gtid_current_pos' => '100-13-77', - 'gtid_binlog_pos' => '100-13-77', - 'gtid_slave_pos' => '100-13-77' // replica - ], - [ - 'Relay_Master_Log_File' => 'host.1600', - 'Exec_Master_Log_Pos' => '77' - ], - [], - [ '100' => '100-13-77' ], - [ '100' => '100-13-77' ] - ], - [ - [ - 'gtid_current_pos' => '100-13-77', - 'gtid_binlog_pos' => '100-13-77', - 'gtid_slave_pos' => '100-13-77' // replica - ], - [ - 'Relay_Master_Log_File' => 'host.1600', - 'Exec_Master_Log_Pos' => '77' - ], - [], - [ '100' => '100-13-77' ], - [ '100' => '100-13-77' ] - ], - // MySQL - [ - [ - 'gtid_executed' => '2E11FA47-71CA-11E1-9E33-C80AA9429562:1-77' - ], - [ - 'Relay_Master_Log_File' => 'host.1600', - 'Exec_Master_Log_Pos' => '77' - ], - [], // only a replica - [ '2E11FA47-71CA-11E1-9E33-C80AA9429562' - => '2E11FA47-71CA-11E1-9E33-C80AA9429562:1-77' ], - // replica/master use same var - [ '2E11FA47-71CA-11E1-9E33-C80AA9429562' - => '2E11FA47-71CA-11E1-9E33-C80AA9429562:1-77' ], - ], - [ - [ - 'gtid_executed' => '2E11FA47-71CA-11E1-9E33-C80AA9429562:1-49,' . - '2E11FA47-71CA-11E1-9E33-C80AA9429562:51-77' - ], - [ - 'Relay_Master_Log_File' => 'host.1600', - 'Exec_Master_Log_Pos' => '77' - ], - [], // only a replica - [ '2E11FA47-71CA-11E1-9E33-C80AA9429562' - => '2E11FA47-71CA-11E1-9E33-C80AA9429562:51-77' ], - // replica/master use same var - [ '2E11FA47-71CA-11E1-9E33-C80AA9429562' - => '2E11FA47-71CA-11E1-9E33-C80AA9429562:51-77' ], - ], - [ - [ - 'gtid_executed' => null, // not enabled? - 'gtid_binlog_pos' => null - ], - [ - 'Relay_Master_Log_File' => 'host.1600', - 'Exec_Master_Log_Pos' => '77' - ], - [], // only a replica - [], // binlog fallback - false - ], - [ - [ - 'gtid_executed' => null, // not enabled? - 'gtid_binlog_pos' => null - ], - [], // no replication - [], // no replication - false, - false - ] - ]; - } - - /** - * @covers Wikimedia\Rdbms\MySQLMasterPos - */ - public function testSerialize() { - $pos = new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:99', 53636363 ); - $roundtripPos = unserialize( serialize( $pos ) ); - - $this->assertEquals( $pos, $roundtripPos ); - - $pos = new MySQLMasterPos( '255-11-23', 53636363 ); - $roundtripPos = unserialize( serialize( $pos ) ); - - $this->assertEquals( $pos, $roundtripPos ); - } - - /** - * @covers Wikimedia\Rdbms\DatabaseMysqlBase::isInsertSelectSafe - * @dataProvider provideInsertSelectCases - */ - public function testInsertSelectIsSafe( $insertOpts, $selectOpts, $row, $safe ) { - $db = $this->getMockBuilder( DatabaseMysqli::class ) - ->disableOriginalConstructor() - ->setMethods( [ 'getReplicationSafetyInfo' ] ) - ->getMock(); - $db->method( 'getReplicationSafetyInfo' )->willReturn( (object)$row ); - $dbw = TestingAccessWrapper::newFromObject( $db ); - - $this->assertEquals( $safe, $dbw->isInsertSelectSafe( $insertOpts, $selectOpts ) ); - } - - public function provideInsertSelectCases() { - return [ - [ - [], - [], - [ - 'innodb_autoinc_lock_mode' => '2', - 'binlog_format' => 'ROW', - ], - true - ], - [ - [], - [ 'LIMIT' => 100 ], - [ - 'innodb_autoinc_lock_mode' => '2', - 'binlog_format' => 'ROW', - ], - true - ], - [ - [], - [ 'LIMIT' => 100 ], - [ - 'innodb_autoinc_lock_mode' => '0', - 'binlog_format' => 'STATEMENT', - ], - false - ], - [ - [], - [], - [ - 'innodb_autoinc_lock_mode' => '2', - 'binlog_format' => 'STATEMENT', - ], - false - ], - [ - [ 'NO_AUTO_COLUMNS' ], - [ 'LIMIT' => 100 ], - [ - 'innodb_autoinc_lock_mode' => '0', - 'binlog_format' => 'STATEMENT', - ], - false - ], - [ - [], - [], - [ - 'innodb_autoinc_lock_mode' => 0, - 'binlog_format' => 'STATEMENT', - ], - true - ], - [ - [ 'NO_AUTO_COLUMNS' ], - [], - [ - 'innodb_autoinc_lock_mode' => 2, - 'binlog_format' => 'STATEMENT', - ], - true - ], - [ - [ 'NO_AUTO_COLUMNS' ], - [], - [ - 'innodb_autoinc_lock_mode' => 0, - 'binlog_format' => 'STATEMENT', - ], - true - ], - - ]; - } - - /** - * @covers \Wikimedia\Rdbms\DatabaseMysqlBase::buildIntegerCast - */ - public function testBuildIntegerCast() { - $db = $this->getMockBuilder( DatabaseMysqli::class ) - ->disableOriginalConstructor() - ->setMethods( null ) - ->getMock(); - $output = $db->buildIntegerCast( 'fieldName' ); - $this->assertSame( 'CAST( fieldName AS SIGNED )', $output ); - } - - /** - * @covers Wikimedia\Rdbms\Database::setIndexAliases - */ - public function testIndexAliases() { - $db = $this->getMockBuilder( DatabaseMysqli::class ) - ->disableOriginalConstructor() - ->setMethods( [ 'mysqlRealEscapeString', 'dbSchema', 'tablePrefix' ] ) - ->getMock(); - $db->method( 'mysqlRealEscapeString' )->willReturnCallback( - function ( $s ) { - return str_replace( "'", "\\'", $s ); - } - ); - - $db->setIndexAliases( [ 'a_b_idx' => 'a_c_idx' ] ); - $sql = $db->selectSQLText( - 'zend', 'field', [ 'a' => 'x' ], __METHOD__, [ 'USE INDEX' => 'a_b_idx' ] ); - - $this->assertEquals( - "SELECT field FROM `zend` FORCE INDEX (a_c_idx) WHERE a = 'x' ", - $sql - ); - - $db->setIndexAliases( [] ); - $sql = $db->selectSQLText( - 'zend', 'field', [ 'a' => 'x' ], __METHOD__, [ 'USE INDEX' => 'a_b_idx' ] ); - - $this->assertEquals( - "SELECT field FROM `zend` FORCE INDEX (a_b_idx) WHERE a = 'x' ", - $sql - ); - } - - /** - * @covers Wikimedia\Rdbms\Database::setTableAliases - */ - public function testTableAliases() { - $db = $this->getMockBuilder( DatabaseMysqli::class ) - ->disableOriginalConstructor() - ->setMethods( [ 'mysqlRealEscapeString', 'dbSchema', 'tablePrefix' ] ) - ->getMock(); - $db->method( 'mysqlRealEscapeString' )->willReturnCallback( - function ( $s ) { - return str_replace( "'", "\\'", $s ); - } - ); - - $db->setTableAliases( [ - 'meow' => [ 'dbname' => 'feline', 'schema' => null, 'prefix' => 'cat_' ] - ] ); - $sql = $db->selectSQLText( 'meow', 'field', [ 'a' => 'x' ], __METHOD__ ); - - $this->assertEquals( - "SELECT field FROM `feline`.`cat_meow` WHERE a = 'x' ", - $sql - ); - - $db->setTableAliases( [] ); - $sql = $db->selectSQLText( 'meow', 'field', [ 'a' => 'x' ], __METHOD__ ); - - $this->assertEquals( - "SELECT field FROM `meow` WHERE a = 'x' ", - $sql - ); - } -} diff --git a/tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php b/tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php deleted file mode 100644 index 0e133d8c2c..0000000000 --- a/tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php +++ /dev/null @@ -1,2164 +0,0 @@ -database = new DatabaseTestHelper( __CLASS__, [ 'cliMode' => true ] ); - } - - protected function assertLastSql( $sqlText ) { - $this->assertEquals( - $sqlText, - $this->database->getLastSqls() - ); - } - - protected function assertLastSqlDb( $sqlText, DatabaseTestHelper $db ) { - $this->assertEquals( $sqlText, $db->getLastSqls() ); - } - - /** - * @dataProvider provideSelect - * @covers Wikimedia\Rdbms\Database::select - * @covers Wikimedia\Rdbms\Database::selectSQLText - * @covers Wikimedia\Rdbms\Database::tableNamesWithIndexClauseOrJOIN - * @covers Wikimedia\Rdbms\Database::useIndexClause - * @covers Wikimedia\Rdbms\Database::ignoreIndexClause - * @covers Wikimedia\Rdbms\Database::makeSelectOptions - * @covers Wikimedia\Rdbms\Database::makeOrderBy - * @covers Wikimedia\Rdbms\Database::makeGroupByWithHaving - * @covers Wikimedia\Rdbms\Database::selectFieldsOrOptionsAggregate - * @covers Wikimedia\Rdbms\Database::selectOptionsIncludeLocking - */ - public function testSelect( $sql, $sqlText ) { - $this->database->select( - $sql['tables'], - $sql['fields'], - $sql['conds'] ?? [], - __METHOD__, - $sql['options'] ?? [], - $sql['join_conds'] ?? [] - ); - $this->assertLastSql( $sqlText ); - } - - public static function provideSelect() { - return [ - [ - [ - 'tables' => 'table', - 'fields' => [ 'field', 'alias' => 'field2' ], - 'conds' => [ 'alias' => 'text' ], - ], - "SELECT field,field2 AS alias " . - "FROM table " . - "WHERE alias = 'text'" - ], - [ - [ - 'tables' => 'table', - 'fields' => [ 'field', 'alias' => 'field2' ], - 'conds' => 'alias = \'text\'', - ], - "SELECT field,field2 AS alias " . - "FROM table " . - "WHERE alias = 'text'" - ], - [ - [ - 'tables' => 'table', - 'fields' => [ 'field', 'alias' => 'field2' ], - 'conds' => [], - ], - "SELECT field,field2 AS alias " . - "FROM table" - ], - [ - [ - 'tables' => 'table', - 'fields' => [ 'field', 'alias' => 'field2' ], - 'conds' => '', - ], - "SELECT field,field2 AS alias " . - "FROM table" - ], - [ - [ - 'tables' => 'table', - 'fields' => [ 'field', 'alias' => 'field2' ], - 'conds' => '0', // T188314 - ], - "SELECT field,field2 AS alias " . - "FROM table " . - "WHERE 0" - ], - [ - [ - // 'tables' with space prepended indicates pre-escaped table name - 'tables' => ' table LEFT JOIN table2', - 'fields' => [ 'field' ], - 'conds' => [ 'field' => 'text' ], - ], - "SELECT field FROM table LEFT JOIN table2 WHERE field = 'text'" - ], - [ - [ - // Empty 'tables' is allowed - 'tables' => '', - 'fields' => [ 'SPECIAL_QUERY()' ], - ], - "SELECT SPECIAL_QUERY()" - ], - [ - [ - 'tables' => 'table', - 'fields' => [ 'field', 'alias' => 'field2' ], - 'conds' => [ 'alias' => 'text' ], - 'options' => [ 'LIMIT' => 1, 'ORDER BY' => 'field' ], - ], - "SELECT field,field2 AS alias " . - "FROM table " . - "WHERE alias = 'text' " . - "ORDER BY field " . - "LIMIT 1" - ], - [ - [ - 'tables' => [ 'table', 't2' => 'table2' ], - 'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ], - 'conds' => [ 'alias' => 'text' ], - 'options' => [ 'LIMIT' => 1, 'ORDER BY' => 'field' ], - 'join_conds' => [ 't2' => [ - 'LEFT JOIN', 'tid = t2.id' - ] ], - ], - "SELECT tid,field,field2 AS alias,t2.id " . - "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " . - "WHERE alias = 'text' " . - "ORDER BY field " . - "LIMIT 1" - ], - [ - [ - 'tables' => [ 'table', 't2' => 'table2' ], - 'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ], - 'conds' => [ 'alias' => 'text' ], - 'options' => [ 'LIMIT' => 1, 'GROUP BY' => 'field', 'HAVING' => 'COUNT(*) > 1' ], - 'join_conds' => [ 't2' => [ - 'LEFT JOIN', 'tid = t2.id' - ] ], - ], - "SELECT tid,field,field2 AS alias,t2.id " . - "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " . - "WHERE alias = 'text' " . - "GROUP BY field HAVING COUNT(*) > 1 " . - "LIMIT 1" - ], - [ - [ - 'tables' => [ 'table', 't2' => 'table2' ], - 'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ], - 'conds' => [ 'alias' => 'text' ], - 'options' => [ - 'LIMIT' => 1, - 'GROUP BY' => [ 'field', 'field2' ], - 'HAVING' => [ 'COUNT(*) > 1', 'field' => 1 ] - ], - 'join_conds' => [ 't2' => [ - 'LEFT JOIN', 'tid = t2.id' - ] ], - ], - "SELECT tid,field,field2 AS alias,t2.id " . - "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " . - "WHERE alias = 'text' " . - "GROUP BY field,field2 HAVING (COUNT(*) > 1) AND field = '1' " . - "LIMIT 1" - ], - [ - [ - 'tables' => [ 'table' ], - 'fields' => [ 'alias' => 'field' ], - 'conds' => [ 'alias' => [ 1, 2, 3, 4 ] ], - ], - "SELECT field AS alias " . - "FROM table " . - "WHERE alias IN ('1','2','3','4')" - ], - [ - [ - 'tables' => 'table', - 'fields' => [ 'field' ], - 'options' => [ 'USE INDEX' => [ 'table' => 'X' ] ], - ], - // No-op by default - "SELECT field FROM table" - ], - [ - [ - 'tables' => 'table', - 'fields' => [ 'field' ], - 'options' => [ 'IGNORE INDEX' => [ 'table' => 'X' ] ], - ], - // No-op by default - "SELECT field FROM table" - ], - [ - [ - 'tables' => 'table', - 'fields' => [ 'field' ], - 'options' => [ 'DISTINCT' ], - ], - "SELECT DISTINCT field FROM table" - ], - [ - [ - 'tables' => 'table', - 'fields' => [ 'field' ], - 'options' => [ 'LOCK IN SHARE MODE' ], - ], - "SELECT field FROM table LOCK IN SHARE MODE" - ], - [ - [ - 'tables' => 'table', - 'fields' => [ 'field' ], - 'options' => [ 'EXPLAIN' => true ], - ], - 'EXPLAIN SELECT field FROM table' - ], - [ - [ - 'tables' => 'table', - 'fields' => [ 'field' ], - 'options' => [ 'FOR UPDATE' ], - ], - "SELECT field FROM table FOR UPDATE" - ], - ]; - } - - /** - * @dataProvider provideLockForUpdate - * @covers Wikimedia\Rdbms\Database::lockForUpdate - */ - public function testLockForUpdate( $sql, $sqlText ) { - $this->database->startAtomic( __METHOD__ ); - $this->database->lockForUpdate( - $sql['tables'], - $sql['conds'] ?? [], - __METHOD__, - $sql['options'] ?? [], - $sql['join_conds'] ?? [] - ); - $this->database->endAtomic( __METHOD__ ); - - $this->assertLastSql( "BEGIN; $sqlText; COMMIT" ); - } - - public static function provideLockForUpdate() { - return [ - [ - [ - 'tables' => [ 'table' ], - 'conds' => [ 'field' => [ 1, 2, 3, 4 ] ], - ], - "SELECT COUNT(*) AS rowcount FROM " . - "(SELECT 1 FROM table WHERE field IN ('1','2','3','4') " . - "FOR UPDATE) tmp_count" - ], - [ - [ - 'tables' => [ 'table', 't2' => 'table2' ], - 'conds' => [ 'field' => 'text' ], - 'options' => [ 'LIMIT' => 1, 'ORDER BY' => 'field' ], - 'join_conds' => [ 't2' => [ - 'LEFT JOIN', 'tid = t2.id' - ] ], - ], - "SELECT COUNT(*) AS rowcount FROM " . - "(SELECT 1 FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " . - "WHERE field = 'text' ORDER BY field LIMIT 1 FOR UPDATE) tmp_count" - ], - [ - [ - 'tables' => 'table', - ], - "SELECT COUNT(*) AS rowcount FROM " . - "(SELECT 1 FROM table FOR UPDATE) tmp_count" - ], - ]; - } - - /** - * @covers Wikimedia\Rdbms\Subquery - * @dataProvider provideSelectRowCount - * @param array $sql - * @param string $sqlText - */ - public function testSelectRowCount( $sql, $sqlText ) { - $this->database->selectRowCount( - $sql['tables'], - $sql['field'], - $sql['conds'] ?? [], - __METHOD__, - $sql['options'] ?? [], - $sql['join_conds'] ?? [] - ); - $this->assertLastSql( $sqlText ); - } - - public static function provideSelectRowCount() { - return [ - [ - [ - 'tables' => 'table', - 'field' => [ '*' ], - 'conds' => [ 'field' => 'text' ], - ], - "SELECT COUNT(*) AS rowcount FROM " . - "(SELECT 1 FROM table WHERE field = 'text' ) tmp_count" - ], - [ - [ - 'tables' => 'table', - 'field' => [ 'column' ], - 'conds' => [ 'field' => 'text' ], - ], - "SELECT COUNT(*) AS rowcount FROM " . - "(SELECT 1 FROM table WHERE field = 'text' AND (column IS NOT NULL) ) tmp_count" - ], - [ - [ - 'tables' => 'table', - 'field' => [ 'alias' => 'column' ], - 'conds' => [ 'field' => 'text' ], - ], - "SELECT COUNT(*) AS rowcount FROM " . - "(SELECT 1 FROM table WHERE field = 'text' AND (column IS NOT NULL) ) tmp_count" - ], - [ - [ - 'tables' => 'table', - 'field' => [ 'alias' => 'column' ], - 'conds' => '', - ], - "SELECT COUNT(*) AS rowcount FROM " . - "(SELECT 1 FROM table WHERE (column IS NOT NULL) ) tmp_count" - ], - [ - [ - 'tables' => 'table', - 'field' => [ 'alias' => 'column' ], - 'conds' => false, - ], - "SELECT COUNT(*) AS rowcount FROM " . - "(SELECT 1 FROM table WHERE (column IS NOT NULL) ) tmp_count" - ], - [ - [ - 'tables' => 'table', - 'field' => [ 'alias' => 'column' ], - 'conds' => null, - ], - "SELECT COUNT(*) AS rowcount FROM " . - "(SELECT 1 FROM table WHERE (column IS NOT NULL) ) tmp_count" - ], - [ - [ - 'tables' => 'table', - 'field' => [ 'alias' => 'column' ], - 'conds' => '1', - ], - "SELECT COUNT(*) AS rowcount FROM " . - "(SELECT 1 FROM table WHERE (1) AND (column IS NOT NULL) ) tmp_count" - ], - [ - [ - 'tables' => 'table', - 'field' => [ 'alias' => 'column' ], - 'conds' => '0', - ], - "SELECT COUNT(*) AS rowcount FROM " . - "(SELECT 1 FROM table WHERE (0) AND (column IS NOT NULL) ) tmp_count" - ], - ]; - } - - /** - * @dataProvider provideUpdate - * @covers Wikimedia\Rdbms\Database::update - * @covers Wikimedia\Rdbms\Database::makeUpdateOptions - * @covers Wikimedia\Rdbms\Database::makeUpdateOptionsArray - */ - public function testUpdate( $sql, $sqlText ) { - $this->database->update( - $sql['table'], - $sql['values'], - $sql['conds'], - __METHOD__, - $sql['options'] ?? [] - ); - $this->assertLastSql( $sqlText ); - } - - public static function provideUpdate() { - return [ - [ - [ - 'table' => 'table', - 'values' => [ 'field' => 'text', 'field2' => 'text2' ], - 'conds' => [ 'alias' => 'text' ], - ], - "UPDATE table " . - "SET field = 'text'" . - ",field2 = 'text2' " . - "WHERE alias = 'text'" - ], - [ - [ - 'table' => 'table', - 'values' => [ 'field = other', 'field2' => 'text2' ], - 'conds' => [ 'id' => '1' ], - ], - "UPDATE table " . - "SET field = other" . - ",field2 = 'text2' " . - "WHERE id = '1'" - ], - [ - [ - 'table' => 'table', - 'values' => [ 'field = other', 'field2' => 'text2' ], - 'conds' => '*', - ], - "UPDATE table " . - "SET field = other" . - ",field2 = 'text2'" - ], - ]; - } - - /** - * @dataProvider provideDelete - * @covers Wikimedia\Rdbms\Database::delete - */ - public function testDelete( $sql, $sqlText ) { - $this->database->delete( - $sql['table'], - $sql['conds'], - __METHOD__ - ); - $this->assertLastSql( $sqlText ); - } - - public static function provideDelete() { - return [ - [ - [ - 'table' => 'table', - 'conds' => [ 'alias' => 'text' ], - ], - "DELETE FROM table " . - "WHERE alias = 'text'" - ], - [ - [ - 'table' => 'table', - 'conds' => '*', - ], - "DELETE FROM table" - ], - ]; - } - - /** - * @dataProvider provideUpsert - * @covers Wikimedia\Rdbms\Database::upsert - */ - public function testUpsert( $sql, $sqlText ) { - $this->database->upsert( - $sql['table'], - $sql['rows'], - $sql['uniqueIndexes'], - $sql['set'], - __METHOD__ - ); - $this->assertLastSql( $sqlText ); - } - - public static function provideUpsert() { - return [ - [ - [ - 'table' => 'upsert_table', - 'rows' => [ 'field' => 'text', 'field2' => 'text2' ], - 'uniqueIndexes' => [ 'field' ], - 'set' => [ 'field' => 'set' ], - ], - "BEGIN; " . - "UPDATE upsert_table " . - "SET field = 'set' " . - "WHERE ((field = 'text')); " . - "INSERT IGNORE INTO upsert_table " . - "(field,field2) " . - "VALUES ('text','text2'); " . - "COMMIT" - ], - ]; - } - - /** - * @dataProvider provideDeleteJoin - * @covers Wikimedia\Rdbms\Database::deleteJoin - */ - public function testDeleteJoin( $sql, $sqlText ) { - $this->database->deleteJoin( - $sql['delTable'], - $sql['joinTable'], - $sql['delVar'], - $sql['joinVar'], - $sql['conds'], - __METHOD__ - ); - $this->assertLastSql( $sqlText ); - } - - public static function provideDeleteJoin() { - return [ - [ - [ - 'delTable' => 'table', - 'joinTable' => 'table_join', - 'delVar' => 'field', - 'joinVar' => 'field_join', - 'conds' => [ 'alias' => 'text' ], - ], - "DELETE FROM table " . - "WHERE field IN (" . - "SELECT field_join FROM table_join WHERE alias = 'text'" . - ")" - ], - [ - [ - 'delTable' => 'table', - 'joinTable' => 'table_join', - 'delVar' => 'field', - 'joinVar' => 'field_join', - 'conds' => '*', - ], - "DELETE FROM table " . - "WHERE field IN (" . - "SELECT field_join FROM table_join " . - ")" - ], - ]; - } - - /** - * @dataProvider provideInsert - * @covers Wikimedia\Rdbms\Database::insert - * @covers Wikimedia\Rdbms\Database::makeInsertOptions - */ - public function testInsert( $sql, $sqlText ) { - $this->database->insert( - $sql['table'], - $sql['rows'], - __METHOD__, - $sql['options'] ?? [] - ); - $this->assertLastSql( $sqlText ); - } - - public static function provideInsert() { - return [ - [ - [ - 'table' => 'table', - 'rows' => [ 'field' => 'text', 'field2' => 2 ], - ], - "INSERT INTO table " . - "(field,field2) " . - "VALUES ('text','2')" - ], - [ - [ - 'table' => 'table', - 'rows' => [ 'field' => 'text', 'field2' => 2 ], - 'options' => 'IGNORE', - ], - "INSERT IGNORE INTO table " . - "(field,field2) " . - "VALUES ('text','2')" - ], - [ - [ - 'table' => 'table', - 'rows' => [ - [ 'field' => 'text', 'field2' => 2 ], - [ 'field' => 'multi', 'field2' => 3 ], - ], - 'options' => 'IGNORE', - ], - "INSERT IGNORE INTO table " . - "(field,field2) " . - "VALUES " . - "('text','2')," . - "('multi','3')" - ], - ]; - } - - /** - * @dataProvider provideInsertSelect - * @covers Wikimedia\Rdbms\Database::insertSelect - * @covers Wikimedia\Rdbms\Database::nativeInsertSelect - */ - public function testInsertSelect( $sql, $sqlTextNative, $sqlSelect, $sqlInsert ) { - $this->database->insertSelect( - $sql['destTable'], - $sql['srcTable'], - $sql['varMap'], - $sql['conds'], - __METHOD__, - $sql['insertOptions'] ?? [], - $sql['selectOptions'] ?? [], - $sql['selectJoinConds'] ?? [] - ); - $this->assertLastSql( $sqlTextNative ); - - $dbWeb = new DatabaseTestHelper( __CLASS__, [ 'cliMode' => false ] ); - $dbWeb->forceNextResult( [ - array_flip( array_keys( $sql['varMap'] ) ) - ] ); - $dbWeb->insertSelect( - $sql['destTable'], - $sql['srcTable'], - $sql['varMap'], - $sql['conds'], - __METHOD__, - $sql['insertOptions'] ?? [], - $sql['selectOptions'] ?? [], - $sql['selectJoinConds'] ?? [] - ); - $this->assertLastSqlDb( implode( '; ', [ $sqlSelect, 'BEGIN', $sqlInsert, 'COMMIT' ] ), $dbWeb ); - } - - public static function provideInsertSelect() { - return [ - [ - [ - 'destTable' => 'insert_table', - 'srcTable' => 'select_table', - 'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ], - 'conds' => '*', - ], - "INSERT INTO insert_table " . - "(field_insert,field) " . - "SELECT field_select,field2 " . - "FROM select_table", - "SELECT field_select AS field_insert,field2 AS field " . - "FROM select_table FOR UPDATE", - "INSERT INTO insert_table (field_insert,field) VALUES ('0','1')" - ], - [ - [ - 'destTable' => 'insert_table', - 'srcTable' => 'select_table', - 'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ], - 'conds' => [ 'field' => 2 ], - ], - "INSERT INTO insert_table " . - "(field_insert,field) " . - "SELECT field_select,field2 " . - "FROM select_table " . - "WHERE field = '2'", - "SELECT field_select AS field_insert,field2 AS field FROM " . - "select_table WHERE field = '2' FOR UPDATE", - "INSERT INTO insert_table (field_insert,field) VALUES ('0','1')" - ], - [ - [ - 'destTable' => 'insert_table', - 'srcTable' => 'select_table', - 'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ], - 'conds' => [ 'field' => 2 ], - 'insertOptions' => 'IGNORE', - 'selectOptions' => [ 'ORDER BY' => 'field' ], - ], - "INSERT IGNORE INTO insert_table " . - "(field_insert,field) " . - "SELECT field_select,field2 " . - "FROM select_table " . - "WHERE field = '2' " . - "ORDER BY field", - "SELECT field_select AS field_insert,field2 AS field " . - "FROM select_table WHERE field = '2' ORDER BY field FOR UPDATE", - "INSERT IGNORE INTO insert_table (field_insert,field) VALUES ('0','1')" - ], - [ - [ - 'destTable' => 'insert_table', - 'srcTable' => [ 'select_table1', 'select_table2' ], - 'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ], - 'conds' => [ 'field' => 2 ], - 'insertOptions' => [ 'NO_AUTO_COLUMNS' ], - 'selectOptions' => [ 'ORDER BY' => 'field', 'FORCE INDEX' => [ 'select_table1' => 'index1' ] ], - 'selectJoinConds' => [ - 'select_table2' => [ 'LEFT JOIN', [ 'select_table1.foo = select_table2.bar' ] ], - ], - ], - "INSERT INTO insert_table " . - "(field_insert,field) " . - "SELECT field_select,field2 " . - "FROM select_table1 LEFT JOIN select_table2 ON ((select_table1.foo = select_table2.bar)) " . - "WHERE field = '2' " . - "ORDER BY field", - "SELECT field_select AS field_insert,field2 AS field " . - "FROM select_table1 LEFT JOIN select_table2 ON ((select_table1.foo = select_table2.bar)) " . - "WHERE field = '2' ORDER BY field FOR UPDATE", - "INSERT INTO insert_table (field_insert,field) VALUES ('0','1')" - ], - ]; - } - - /** - * @covers Wikimedia\Rdbms\Database::insertSelect - * @covers Wikimedia\Rdbms\Database::nativeInsertSelect - */ - public function testInsertSelectBatching() { - $dbWeb = new DatabaseTestHelper( __CLASS__, [ 'cliMode' => false ] ); - $rows = []; - for ( $i = 0; $i <= 25000; $i++ ) { - $rows[] = [ 'field' => $i ]; - } - $dbWeb->forceNextResult( $rows ); - $dbWeb->insertSelect( - 'insert_table', - 'select_table', - [ 'field' => 'field2' ], - '*', - __METHOD__ - ); - $this->assertLastSqlDb( implode( '; ', [ - 'SELECT field2 AS field FROM select_table FOR UPDATE', - 'BEGIN', - "INSERT INTO insert_table (field) VALUES ('" . implode( "'),('", range( 0, 9999 ) ) . "')", - "INSERT INTO insert_table (field) VALUES ('" . implode( "'),('", range( 10000, 19999 ) ) . "')", - "INSERT INTO insert_table (field) VALUES ('" . implode( "'),('", range( 20000, 25000 ) ) . "')", - 'COMMIT' - ] ), $dbWeb ); - } - - /** - * @dataProvider provideReplace - * @covers Wikimedia\Rdbms\Database::replace - */ - public function testReplace( $sql, $sqlText ) { - $this->database->replace( - $sql['table'], - $sql['uniqueIndexes'], - $sql['rows'], - __METHOD__ - ); - $this->assertLastSql( $sqlText ); - } - - public static function provideReplace() { - return [ - [ - [ - 'table' => 'replace_table', - 'uniqueIndexes' => [ 'field' ], - 'rows' => [ 'field' => 'text', 'field2' => 'text2' ], - ], - "BEGIN; DELETE FROM replace_table " . - "WHERE (field = 'text'); " . - "INSERT INTO replace_table " . - "(field,field2) " . - "VALUES ('text','text2'); COMMIT" - ], - [ - [ - 'table' => 'module_deps', - 'uniqueIndexes' => [ [ 'md_module', 'md_skin' ] ], - 'rows' => [ - 'md_module' => 'module', - 'md_skin' => 'skin', - 'md_deps' => 'deps', - ], - ], - "BEGIN; DELETE FROM module_deps " . - "WHERE (md_module = 'module' AND md_skin = 'skin'); " . - "INSERT INTO module_deps " . - "(md_module,md_skin,md_deps) " . - "VALUES ('module','skin','deps'); COMMIT" - ], - [ - [ - 'table' => 'module_deps', - 'uniqueIndexes' => [ [ 'md_module', 'md_skin' ] ], - 'rows' => [ - [ - 'md_module' => 'module', - 'md_skin' => 'skin', - 'md_deps' => 'deps', - ], [ - 'md_module' => 'module2', - 'md_skin' => 'skin2', - 'md_deps' => 'deps2', - ], - ], - ], - "BEGIN; DELETE FROM module_deps " . - "WHERE (md_module = 'module' AND md_skin = 'skin'); " . - "INSERT INTO module_deps " . - "(md_module,md_skin,md_deps) " . - "VALUES ('module','skin','deps'); " . - "DELETE FROM module_deps " . - "WHERE (md_module = 'module2' AND md_skin = 'skin2'); " . - "INSERT INTO module_deps " . - "(md_module,md_skin,md_deps) " . - "VALUES ('module2','skin2','deps2'); COMMIT" - ], - [ - [ - 'table' => 'module_deps', - 'uniqueIndexes' => [ 'md_module', 'md_skin' ], - 'rows' => [ - [ - 'md_module' => 'module', - 'md_skin' => 'skin', - 'md_deps' => 'deps', - ], [ - 'md_module' => 'module2', - 'md_skin' => 'skin2', - 'md_deps' => 'deps2', - ], - ], - ], - "BEGIN; DELETE FROM module_deps " . - "WHERE (md_module = 'module') OR (md_skin = 'skin'); " . - "INSERT INTO module_deps " . - "(md_module,md_skin,md_deps) " . - "VALUES ('module','skin','deps'); " . - "DELETE FROM module_deps " . - "WHERE (md_module = 'module2') OR (md_skin = 'skin2'); " . - "INSERT INTO module_deps " . - "(md_module,md_skin,md_deps) " . - "VALUES ('module2','skin2','deps2'); COMMIT" - ], - [ - [ - 'table' => 'module_deps', - 'uniqueIndexes' => [], - 'rows' => [ - 'md_module' => 'module', - 'md_skin' => 'skin', - 'md_deps' => 'deps', - ], - ], - "BEGIN; INSERT INTO module_deps " . - "(md_module,md_skin,md_deps) " . - "VALUES ('module','skin','deps'); COMMIT" - ], - ]; - } - - /** - * @dataProvider provideNativeReplace - * @covers Wikimedia\Rdbms\Database::nativeReplace - */ - public function testNativeReplace( $sql, $sqlText ) { - $this->database->nativeReplace( - $sql['table'], - $sql['rows'], - __METHOD__ - ); - $this->assertLastSql( $sqlText ); - } - - public static function provideNativeReplace() { - return [ - [ - [ - 'table' => 'replace_table', - 'rows' => [ 'field' => 'text', 'field2' => 'text2' ], - ], - "REPLACE INTO replace_table " . - "(field,field2) " . - "VALUES ('text','text2')" - ], - ]; - } - - /** - * @dataProvider provideConditional - * @covers Wikimedia\Rdbms\Database::conditional - */ - public function testConditional( $sql, $sqlText ) { - $this->assertEquals( trim( $this->database->conditional( - $sql['conds'], - $sql['true'], - $sql['false'] - ) ), $sqlText ); - } - - public static function provideConditional() { - return [ - [ - [ - 'conds' => [ 'field' => 'text' ], - 'true' => 1, - 'false' => 'NULL', - ], - "(CASE WHEN field = 'text' THEN 1 ELSE NULL END)" - ], - [ - [ - 'conds' => [ 'field' => 'text', 'field2' => 'anothertext' ], - 'true' => 1, - 'false' => 'NULL', - ], - "(CASE WHEN field = 'text' AND field2 = 'anothertext' THEN 1 ELSE NULL END)" - ], - [ - [ - 'conds' => 'field=1', - 'true' => 1, - 'false' => 'NULL', - ], - "(CASE WHEN field=1 THEN 1 ELSE NULL END)" - ], - ]; - } - - /** - * @dataProvider provideBuildConcat - * @covers Wikimedia\Rdbms\Database::buildConcat - */ - public function testBuildConcat( $stringList, $sqlText ) { - $this->assertEquals( trim( $this->database->buildConcat( - $stringList - ) ), $sqlText ); - } - - public static function provideBuildConcat() { - return [ - [ - [ 'field', 'field2' ], - "CONCAT(field,field2)" - ], - [ - [ "'test'", 'field2' ], - "CONCAT('test',field2)" - ], - ]; - } - - /** - * @dataProvider provideBuildLike - * @covers Wikimedia\Rdbms\Database::buildLike - * @covers Wikimedia\Rdbms\Database::escapeLikeInternal - */ - public function testBuildLike( $array, $sqlText ) { - $this->assertEquals( trim( $this->database->buildLike( - $array - ) ), $sqlText ); - } - - public static function provideBuildLike() { - return [ - [ - 'text', - "LIKE 'text' ESCAPE '`'" - ], - [ - [ 'text', new LikeMatch( '%' ) ], - "LIKE 'text%' ESCAPE '`'" - ], - [ - [ 'text', new LikeMatch( '%' ), 'text2' ], - "LIKE 'text%text2' ESCAPE '`'" - ], - [ - [ 'text', new LikeMatch( '_' ) ], - "LIKE 'text_' ESCAPE '`'" - ], - [ - 'more_text', - "LIKE 'more`_text' ESCAPE '`'" - ], - [ - [ 'C:\\Windows\\', new LikeMatch( '%' ) ], - "LIKE 'C:\\Windows\\%' ESCAPE '`'" - ], - [ - [ 'accent`_test`', new LikeMatch( '%' ) ], - "LIKE 'accent```_test``%' ESCAPE '`'" - ], - ]; - } - - /** - * @dataProvider provideUnionQueries - * @covers Wikimedia\Rdbms\Database::unionQueries - */ - public function testUnionQueries( $sql, $sqlText ) { - $this->assertEquals( trim( $this->database->unionQueries( - $sql['sqls'], - $sql['all'] - ) ), $sqlText ); - } - - public static function provideUnionQueries() { - return [ - [ - [ - 'sqls' => [ 'RAW SQL', 'RAW2SQL' ], - 'all' => true, - ], - "(RAW SQL) UNION ALL (RAW2SQL)" - ], - [ - [ - 'sqls' => [ 'RAW SQL', 'RAW2SQL' ], - 'all' => false, - ], - "(RAW SQL) UNION (RAW2SQL)" - ], - [ - [ - 'sqls' => [ 'RAW SQL', 'RAW2SQL', 'RAW3SQL' ], - 'all' => false, - ], - "(RAW SQL) UNION (RAW2SQL) UNION (RAW3SQL)" - ], - ]; - } - - /** - * @dataProvider provideUnionConditionPermutations - * @covers Wikimedia\Rdbms\Database::unionConditionPermutations - */ - public function testUnionConditionPermutations( $params, $expect ) { - if ( isset( $params['unionSupportsOrderAndLimit'] ) ) { - $this->database->setUnionSupportsOrderAndLimit( $params['unionSupportsOrderAndLimit'] ); - } - - $sql = trim( $this->database->unionConditionPermutations( - $params['table'], - $params['vars'], - $params['permute_conds'], - $params['extra_conds'] ?? '', - 'FNAME', - $params['options'] ?? [], - $params['join_conds'] ?? [] - ) ); - $this->assertEquals( $expect, $sql ); - } - - public static function provideUnionConditionPermutations() { - // phpcs:disable Generic.Files.LineLength - return [ - [ - [ - 'table' => [ 'table1', 'table2' ], - 'vars' => [ 'field1', 'alias' => 'field2' ], - 'permute_conds' => [ - 'field3' => [ 1, 2, 3 ], - 'duplicates' => [ 4, 5, 4 ], - 'empty' => [], - 'single' => [ 0 ], - ], - 'extra_conds' => 'table2.bar > 23', - 'options' => [ - 'ORDER BY' => [ 'field1', 'alias' ], - 'INNER ORDER BY' => [ 'field1', 'field2' ], - 'LIMIT' => 100, - ], - 'join_conds' => [ - 'table2' => [ 'JOIN', 'table1.foo_id = table2.foo_id' ], - ], - ], - "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '1' AND duplicates = '4' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " . - "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '1' AND duplicates = '5' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " . - "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '2' AND duplicates = '4' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " . - "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '2' AND duplicates = '5' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " . - "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '3' AND duplicates = '4' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " . - "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '3' AND duplicates = '5' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) " . - "ORDER BY field1,alias LIMIT 100" - ], - [ - [ - 'table' => 'foo', - 'vars' => [ 'foo_id' ], - 'permute_conds' => [ - 'bar' => [ 1, 2, 3 ], - ], - 'extra_conds' => [ 'baz' => null ], - 'options' => [ - 'NOTALL', - 'ORDER BY' => [ 'foo_id' ], - 'LIMIT' => 25, - ], - ], - "(SELECT foo_id FROM foo WHERE bar = '1' AND baz IS NULL ORDER BY foo_id LIMIT 25 ) UNION " . - "(SELECT foo_id FROM foo WHERE bar = '2' AND baz IS NULL ORDER BY foo_id LIMIT 25 ) UNION " . - "(SELECT foo_id FROM foo WHERE bar = '3' AND baz IS NULL ORDER BY foo_id LIMIT 25 ) " . - "ORDER BY foo_id LIMIT 25" - ], - [ - [ - 'table' => 'foo', - 'vars' => [ 'foo_id' ], - 'permute_conds' => [ - 'bar' => [ 1, 2, 3 ], - ], - 'extra_conds' => [ 'baz' => null ], - 'options' => [ - 'NOTALL' => true, - 'ORDER BY' => [ 'foo_id' ], - 'LIMIT' => 25, - ], - 'unionSupportsOrderAndLimit' => false, - ], - "(SELECT foo_id FROM foo WHERE bar = '1' AND baz IS NULL ) UNION " . - "(SELECT foo_id FROM foo WHERE bar = '2' AND baz IS NULL ) UNION " . - "(SELECT foo_id FROM foo WHERE bar = '3' AND baz IS NULL ) " . - "ORDER BY foo_id LIMIT 25" - ], - [ - [ - 'table' => 'foo', - 'vars' => [ 'foo_id' ], - 'permute_conds' => [], - 'extra_conds' => [ 'baz' => null ], - 'options' => [ - 'ORDER BY' => [ 'foo_id' ], - 'LIMIT' => 25, - ], - ], - "SELECT foo_id FROM foo WHERE baz IS NULL ORDER BY foo_id LIMIT 25" - ], - [ - [ - 'table' => 'foo', - 'vars' => [ 'foo_id' ], - 'permute_conds' => [ - 'bar' => [], - ], - 'extra_conds' => [ 'baz' => null ], - 'options' => [ - 'ORDER BY' => [ 'foo_id' ], - 'LIMIT' => 25, - ], - ], - "SELECT foo_id FROM foo WHERE baz IS NULL ORDER BY foo_id LIMIT 25" - ], - [ - [ - 'table' => 'foo', - 'vars' => [ 'foo_id' ], - 'permute_conds' => [ - 'bar' => [ 1 ], - ], - 'options' => [ - 'ORDER BY' => [ 'foo_id' ], - 'LIMIT' => 25, - 'OFFSET' => 150, - ], - ], - "SELECT foo_id FROM foo WHERE bar = '1' ORDER BY foo_id LIMIT 150,25" - ], - [ - [ - 'table' => 'foo', - 'vars' => [ 'foo_id' ], - 'permute_conds' => [], - 'extra_conds' => [ 'baz' => null ], - 'options' => [ - 'ORDER BY' => [ 'foo_id' ], - 'LIMIT' => 25, - 'OFFSET' => 150, - 'INNER ORDER BY' => [ 'bar_id' ], - ], - ], - "(SELECT foo_id FROM foo WHERE baz IS NULL ORDER BY bar_id LIMIT 175 ) ORDER BY foo_id LIMIT 150,25" - ], - [ - [ - 'table' => 'foo', - 'vars' => [ 'foo_id' ], - 'permute_conds' => [], - 'extra_conds' => [ 'baz' => null ], - 'options' => [ - 'ORDER BY' => [ 'foo_id' ], - 'LIMIT' => 25, - 'OFFSET' => 150, - 'INNER ORDER BY' => [ 'bar_id' ], - ], - 'unionSupportsOrderAndLimit' => false, - ], - "SELECT foo_id FROM foo WHERE baz IS NULL ORDER BY foo_id LIMIT 150,25" - ], - ]; - // phpcs:enable - } - - /** - * @covers Wikimedia\Rdbms\Database::commit - * @covers Wikimedia\Rdbms\Database::doCommit - */ - public function testTransactionCommit() { - $this->database->begin( __METHOD__ ); - $this->database->commit( __METHOD__ ); - $this->assertLastSql( 'BEGIN; COMMIT' ); - } - - /** - * @covers Wikimedia\Rdbms\Database::rollback - * @covers Wikimedia\Rdbms\Database::doRollback - */ - public function testTransactionRollback() { - $this->database->begin( __METHOD__ ); - $this->database->rollback( __METHOD__ ); - $this->assertLastSql( 'BEGIN; ROLLBACK' ); - } - - /** - * @covers Wikimedia\Rdbms\Database::dropTable - */ - public function testDropTable() { - $this->database->setExistingTables( [ 'table' ] ); - $this->database->dropTable( 'table', __METHOD__ ); - $this->assertLastSql( 'DROP TABLE table CASCADE' ); - } - - /** - * @covers Wikimedia\Rdbms\Database::dropTable - */ - public function testDropNonExistingTable() { - $this->assertFalse( - $this->database->dropTable( 'non_existing', __METHOD__ ) - ); - } - - /** - * @dataProvider provideMakeList - * @covers Wikimedia\Rdbms\Database::makeList - */ - public function testMakeList( $list, $mode, $sqlText ) { - $this->assertEquals( trim( $this->database->makeList( - $list, $mode - ) ), $sqlText ); - } - - public static function provideMakeList() { - return [ - [ - [ 'value', 'value2' ], - LIST_COMMA, - "'value','value2'" - ], - [ - [ 'field', 'field2' ], - LIST_NAMES, - "field,field2" - ], - [ - [ 'field' => 'value', 'field2' => 'value2' ], - LIST_AND, - "field = 'value' AND field2 = 'value2'" - ], - [ - [ 'field' => null, "field2 != 'value2'" ], - LIST_AND, - "field IS NULL AND (field2 != 'value2')" - ], - [ - [ 'field' => [ 'value', null, 'value2' ], 'field2' => 'value2' ], - LIST_AND, - "(field IN ('value','value2') OR field IS NULL) AND field2 = 'value2'" - ], - [ - [ 'field' => [ null ], 'field2' => null ], - LIST_AND, - "field IS NULL AND field2 IS NULL" - ], - [ - [ 'field' => 'value', 'field2' => 'value2' ], - LIST_OR, - "field = 'value' OR field2 = 'value2'" - ], - [ - [ 'field' => 'value', 'field2' => null ], - LIST_OR, - "field = 'value' OR field2 IS NULL" - ], - [ - [ 'field' => [ 'value', 'value2' ], 'field2' => [ 'value' ] ], - LIST_OR, - "field IN ('value','value2') OR field2 = 'value'" - ], - [ - [ 'field' => [ null, 'value', null, 'value2' ], "field2 != 'value2'" ], - LIST_OR, - "(field IN ('value','value2') OR field IS NULL) OR (field2 != 'value2')" - ], - [ - [ 'field' => 'value', 'field2' => 'value2' ], - LIST_SET, - "field = 'value',field2 = 'value2'" - ], - [ - [ 'field' => 'value', 'field2' => null ], - LIST_SET, - "field = 'value',field2 = NULL" - ], - [ - [ 'field' => 'value', "field2 != 'value2'" ], - LIST_SET, - "field = 'value',field2 != 'value2'" - ], - ]; - } - - /** - * @covers Wikimedia\Rdbms\Database::registerTempTableWrite - */ - public function testSessionTempTables() { - $temp1 = $this->database->tableName( 'tmp_table_1' ); - $temp2 = $this->database->tableName( 'tmp_table_2' ); - $temp3 = $this->database->tableName( 'tmp_table_3' ); - - $this->database->query( "CREATE TEMPORARY TABLE $temp1 LIKE orig_tbl", __METHOD__ ); - $this->database->query( "CREATE TEMPORARY TABLE $temp2 LIKE orig_tbl", __METHOD__ ); - $this->database->query( "CREATE TEMPORARY TABLE $temp3 LIKE orig_tbl", __METHOD__ ); - - $this->assertTrue( $this->database->tableExists( "tmp_table_1", __METHOD__ ) ); - $this->assertTrue( $this->database->tableExists( "tmp_table_2", __METHOD__ ) ); - $this->assertTrue( $this->database->tableExists( "tmp_table_3", __METHOD__ ) ); - - $this->database->dropTable( 'tmp_table_1', __METHOD__ ); - $this->database->dropTable( 'tmp_table_2', __METHOD__ ); - $this->database->dropTable( 'tmp_table_3', __METHOD__ ); - - $this->assertFalse( $this->database->tableExists( "tmp_table_1", __METHOD__ ) ); - $this->assertFalse( $this->database->tableExists( "tmp_table_2", __METHOD__ ) ); - $this->assertFalse( $this->database->tableExists( "tmp_table_3", __METHOD__ ) ); - - $this->database->query( "CREATE TEMPORARY TABLE tmp_table_1 LIKE orig_tbl", __METHOD__ ); - $this->database->query( "CREATE TEMPORARY TABLE 'tmp_table_2' LIKE orig_tbl", __METHOD__ ); - $this->database->query( "CREATE TEMPORARY TABLE `tmp_table_3` LIKE orig_tbl", __METHOD__ ); - - $this->assertTrue( $this->database->tableExists( "tmp_table_1", __METHOD__ ) ); - $this->assertTrue( $this->database->tableExists( "tmp_table_2", __METHOD__ ) ); - $this->assertTrue( $this->database->tableExists( "tmp_table_3", __METHOD__ ) ); - - $this->database->query( "DROP TEMPORARY TABLE tmp_table_1 LIKE orig_tbl", __METHOD__ ); - $this->database->query( "DROP TEMPORARY TABLE 'tmp_table_2' LIKE orig_tbl", __METHOD__ ); - $this->database->query( "DROP TABLE `tmp_table_3` LIKE orig_tbl", __METHOD__ ); - - $this->assertFalse( $this->database->tableExists( "tmp_table_1", __METHOD__ ) ); - $this->assertFalse( $this->database->tableExists( "tmp_table_2", __METHOD__ ) ); - $this->assertFalse( $this->database->tableExists( "tmp_table_3", __METHOD__ ) ); - } - - public function provideBuildSubstring() { - yield [ 'someField', 1, 2, 'SUBSTRING(someField FROM 1 FOR 2)' ]; - yield [ 'someField', 1, null, 'SUBSTRING(someField FROM 1)' ]; - } - - /** - * @covers Wikimedia\Rdbms\Database::buildSubstring - * @dataProvider provideBuildSubstring - */ - public function testBuildSubstring( $input, $start, $length, $expected ) { - $output = $this->database->buildSubstring( $input, $start, $length ); - $this->assertSame( $expected, $output ); - } - - public function provideBuildSubstring_invalidParams() { - yield [ -1, 1 ]; - yield [ 1, -1 ]; - yield [ 1, 'foo' ]; - yield [ 'foo', 1 ]; - yield [ null, 1 ]; - yield [ 0, 1 ]; - } - - /** - * @covers Wikimedia\Rdbms\Database::buildSubstring - * @covers Wikimedia\Rdbms\Database::assertBuildSubstringParams - * @dataProvider provideBuildSubstring_invalidParams - */ - public function testBuildSubstring_invalidParams( $start, $length ) { - $this->setExpectedException( InvalidArgumentException::class ); - $this->database->buildSubstring( 'foo', $start, $length ); - } - - /** - * @covers \Wikimedia\Rdbms\Database::buildIntegerCast - */ - public function testBuildIntegerCast() { - $output = $this->database->buildIntegerCast( 'fieldName' ); - $this->assertSame( 'CAST( fieldName AS INTEGER )', $output ); - } - - /** - * @covers \Wikimedia\Rdbms\Database::doSavepoint - * @covers \Wikimedia\Rdbms\Database::doReleaseSavepoint - * @covers \Wikimedia\Rdbms\Database::doRollbackToSavepoint - * @covers \Wikimedia\Rdbms\Database::startAtomic - * @covers \Wikimedia\Rdbms\Database::endAtomic - * @covers \Wikimedia\Rdbms\Database::cancelAtomic - * @covers \Wikimedia\Rdbms\Database::doAtomicSection - */ - public function testAtomicSections() { - $this->database->startAtomic( __METHOD__ ); - $this->database->endAtomic( __METHOD__ ); - $this->assertLastSql( 'BEGIN; COMMIT' ); - - $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->cancelAtomic( __METHOD__ ); - $this->assertLastSql( 'BEGIN; ROLLBACK' ); - - $this->database->begin( __METHOD__ ); - $this->database->startAtomic( __METHOD__ ); - $this->database->endAtomic( __METHOD__ ); - $this->database->commit( __METHOD__ ); - $this->assertLastSql( 'BEGIN; COMMIT' ); - - $this->database->begin( __METHOD__ ); - $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->endAtomic( __METHOD__ ); - $this->database->commit( __METHOD__ ); - // phpcs:ignore Generic.Files.LineLength - $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; RELEASE SAVEPOINT wikimedia_rdbms_atomic1; COMMIT' ); - - $this->database->begin( __METHOD__ ); - $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->cancelAtomic( __METHOD__ ); - $this->database->commit( __METHOD__ ); - // phpcs:ignore Generic.Files.LineLength - $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; COMMIT' ); - - $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->cancelAtomic( __METHOD__ ); - $this->database->endAtomic( __METHOD__ ); - // phpcs:ignore Generic.Files.LineLength - $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; COMMIT' ); - - $noOpCallack = function () { - }; - - $this->database->doAtomicSection( __METHOD__, $noOpCallack, IDatabase::ATOMIC_CANCELABLE ); - $this->assertLastSql( 'BEGIN; COMMIT' ); - - $this->database->doAtomicSection( __METHOD__, $noOpCallack ); - $this->assertLastSql( 'BEGIN; COMMIT' ); - - $this->database->begin( __METHOD__ ); - $this->database->doAtomicSection( __METHOD__, $noOpCallack, IDatabase::ATOMIC_CANCELABLE ); - $this->database->rollback( __METHOD__ ); - // phpcs:ignore Generic.Files.LineLength - $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; RELEASE SAVEPOINT wikimedia_rdbms_atomic1; ROLLBACK' ); - - $fname = __METHOD__; - $triggerMap = [ - '-' => '-', - IDatabase::TRIGGER_COMMIT => 'tCommit', - IDatabase::TRIGGER_ROLLBACK => 'tRollback' - ]; - $pcCallback = function ( IDatabase $db ) use ( $fname ) { - $this->database->query( "SELECT 0", $fname ); - }; - $callback1 = function ( $trigger = '-' ) use ( $fname, $triggerMap ) { - $this->database->query( "SELECT 1, {$triggerMap[$trigger]} AS t", $fname ); - }; - $callback2 = function ( $trigger = '-' ) use ( $fname, $triggerMap ) { - $this->database->query( "SELECT 2, {$triggerMap[$trigger]} AS t", $fname ); - }; - $callback3 = function ( $trigger = '-' ) use ( $fname, $triggerMap ) { - $this->database->query( "SELECT 3, {$triggerMap[$trigger]} AS t", $fname ); - }; - - $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->onTransactionPreCommitOrIdle( $pcCallback, __METHOD__ ); - $this->database->cancelAtomic( __METHOD__ ); - $this->assertLastSql( 'BEGIN; ROLLBACK' ); - - $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ ); - $this->database->cancelAtomic( __METHOD__ ); - $this->assertLastSql( 'BEGIN; ROLLBACK' ); - - $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->onTransactionResolution( $callback1, __METHOD__ ); - $this->database->cancelAtomic( __METHOD__ ); - $this->assertLastSql( 'BEGIN; ROLLBACK; SELECT 1, tRollback AS t' ); - - $this->database->startAtomic( __METHOD__ . '_outer' ); - $this->database->onTransactionPreCommitOrIdle( $pcCallback, __METHOD__ ); - $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->onTransactionPreCommitOrIdle( $pcCallback, __METHOD__ ); - $this->database->cancelAtomic( __METHOD__ ); - $this->database->onTransactionPreCommitOrIdle( $pcCallback, __METHOD__ ); - $this->database->endAtomic( __METHOD__ . '_outer' ); - $this->assertLastSql( implode( "; ", [ - 'BEGIN', - 'SAVEPOINT wikimedia_rdbms_atomic1', - 'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1', - 'SELECT 0', - 'SELECT 0', - 'COMMIT' - ] ) ); - - $this->database->startAtomic( __METHOD__ . '_outer' ); - $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ ); - $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->onTransactionCommitOrIdle( $callback2, __METHOD__ ); - $this->database->cancelAtomic( __METHOD__ ); - $this->database->onTransactionCommitOrIdle( $callback3, __METHOD__ ); - $this->database->endAtomic( __METHOD__ . '_outer' ); - $this->assertLastSql( implode( "; ", [ - 'BEGIN', - 'SAVEPOINT wikimedia_rdbms_atomic1', - 'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1', - 'COMMIT', - 'SELECT 1, tCommit AS t', - 'SELECT 3, tCommit AS t' - ] ) ); - - $this->database->startAtomic( __METHOD__ . '_outer' ); - $this->database->onTransactionResolution( $callback1, __METHOD__ ); - $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->onTransactionResolution( $callback2, __METHOD__ ); - $this->database->cancelAtomic( __METHOD__ ); - $this->database->onTransactionResolution( $callback3, __METHOD__ ); - $this->database->endAtomic( __METHOD__ . '_outer' ); - $this->assertLastSql( implode( "; ", [ - 'BEGIN', - 'SAVEPOINT wikimedia_rdbms_atomic1', - 'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1', - 'COMMIT', - 'SELECT 1, tCommit AS t', - 'SELECT 2, tRollback AS t', - 'SELECT 3, tCommit AS t' - ] ) ); - - $makeCallback = function ( $id ) use ( $fname, $triggerMap ) { - return function ( $trigger = '-' ) use ( $id, $fname, $triggerMap ) { - $this->database->query( "SELECT $id, {$triggerMap[$trigger]} AS t", $fname ); - }; - }; - - $this->database->startAtomic( __METHOD__ . '_outer' ); - $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->onTransactionResolution( $makeCallback( 1 ), __METHOD__ ); - $this->database->cancelAtomic( __METHOD__ ); - $this->database->endAtomic( __METHOD__ . '_outer' ); - $this->assertLastSql( implode( "; ", [ - 'BEGIN', - 'SAVEPOINT wikimedia_rdbms_atomic1', - 'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1', - 'COMMIT', - 'SELECT 1, tRollback AS t' - ] ) ); - - $this->database->startAtomic( __METHOD__ . '_level1', IDatabase::ATOMIC_CANCELABLE ); - $this->database->onTransactionResolution( $makeCallback( 1 ), __METHOD__ ); - $this->database->startAtomic( __METHOD__ . '_level2' ); - $this->database->startAtomic( __METHOD__ . '_level3', IDatabase::ATOMIC_CANCELABLE ); - $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->onTransactionResolution( $makeCallback( 2 ), __METHOD__ ); - $this->database->endAtomic( __METHOD__ ); - $this->database->onTransactionResolution( $makeCallback( 3 ), __METHOD__ ); - $this->database->cancelAtomic( __METHOD__ . '_level3' ); - $this->database->endAtomic( __METHOD__ . '_level2' ); - $this->database->onTransactionResolution( $makeCallback( 4 ), __METHOD__ ); - $this->database->endAtomic( __METHOD__ . '_level1' ); - $this->assertLastSql( implode( "; ", [ - 'BEGIN', - 'SAVEPOINT wikimedia_rdbms_atomic1', - 'SAVEPOINT wikimedia_rdbms_atomic2', - 'RELEASE SAVEPOINT wikimedia_rdbms_atomic2', - 'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1', - 'COMMIT; SELECT 1, tCommit AS t', - 'SELECT 2, tRollback AS t', - 'SELECT 3, tRollback AS t', - 'SELECT 4, tCommit AS t' - ] ) ); - } - - /** - * @covers \Wikimedia\Rdbms\Database::doSavepoint - * @covers \Wikimedia\Rdbms\Database::doReleaseSavepoint - * @covers \Wikimedia\Rdbms\Database::doRollbackToSavepoint - * @covers \Wikimedia\Rdbms\Database::startAtomic - * @covers \Wikimedia\Rdbms\Database::endAtomic - * @covers \Wikimedia\Rdbms\Database::cancelAtomic - * @covers \Wikimedia\Rdbms\Database::doAtomicSection - */ - public function testAtomicSectionsRecovery() { - $this->database->begin( __METHOD__ ); - try { - $this->database->doAtomicSection( - __METHOD__, - function () { - $this->database->startAtomic( 'inner_func1' ); - $this->database->startAtomic( 'inner_func2' ); - - throw new RuntimeException( 'Test exception' ); - }, - IDatabase::ATOMIC_CANCELABLE - ); - $this->fail( 'Expected exception not thrown' ); - } catch ( RuntimeException $ex ) { - $this->assertSame( 'Test exception', $ex->getMessage() ); - } - $this->database->commit( __METHOD__ ); - // phpcs:ignore Generic.Files.LineLength - $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; COMMIT' ); - - $this->database->begin( __METHOD__ ); - try { - $this->database->doAtomicSection( - __METHOD__, - function () { - throw new RuntimeException( 'Test exception' ); - } - ); - $this->fail( 'Test exception not thrown' ); - } catch ( RuntimeException $ex ) { - $this->assertSame( 'Test exception', $ex->getMessage() ); - } - try { - $this->database->commit( __METHOD__ ); - $this->fail( 'Test exception not thrown' ); - } catch ( DBTransactionError $ex ) { - $this->assertSame( - 'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR.', - $ex->getMessage() - ); - } - $this->database->rollback( __METHOD__ ); - $this->assertLastSql( 'BEGIN; ROLLBACK' ); - } - - /** - * @covers \Wikimedia\Rdbms\Database::doSavepoint - * @covers \Wikimedia\Rdbms\Database::doReleaseSavepoint - * @covers \Wikimedia\Rdbms\Database::doRollbackToSavepoint - * @covers \Wikimedia\Rdbms\Database::startAtomic - * @covers \Wikimedia\Rdbms\Database::endAtomic - * @covers \Wikimedia\Rdbms\Database::cancelAtomic - * @covers \Wikimedia\Rdbms\Database::doAtomicSection - */ - public function testAtomicSectionsCallbackCancellation() { - $fname = __METHOD__; - $callback1Called = null; - $callback1 = function ( $trigger = '-' ) use ( $fname, &$callback1Called ) { - $callback1Called = $trigger; - $this->database->query( "SELECT 1", $fname ); - }; - $callback2Called = null; - $callback2 = function ( $trigger = '-' ) use ( $fname, &$callback2Called ) { - $callback2Called = $trigger; - $this->database->query( "SELECT 2", $fname ); - }; - $callback3Called = null; - $callback3 = function ( $trigger = '-' ) use ( $fname, &$callback3Called ) { - $callback3Called = $trigger; - $this->database->query( "SELECT 3", $fname ); - }; - - $this->database->startAtomic( __METHOD__ . '_outer' ); - $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->startAtomic( __METHOD__ . '_inner' ); - $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ ); - $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ ); - $this->database->onTransactionResolution( $callback3, __METHOD__ ); - $this->database->endAtomic( __METHOD__ . '_inner' ); - $this->database->cancelAtomic( __METHOD__ ); - $this->database->endAtomic( __METHOD__ . '_outer' ); - $this->assertNull( $callback1Called ); - $this->assertNull( $callback2Called ); - $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called ); - // phpcs:ignore Generic.Files.LineLength - $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; COMMIT; SELECT 3' ); - - $callback1Called = null; - $callback2Called = null; - $callback3Called = null; - $this->database->startAtomic( __METHOD__ . '_outer' ); - $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->startAtomic( __METHOD__ . '_inner', IDatabase::ATOMIC_CANCELABLE ); - $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ ); - $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ ); - $this->database->onTransactionResolution( $callback3, __METHOD__ ); - $this->database->endAtomic( __METHOD__ . '_inner' ); - $this->database->cancelAtomic( __METHOD__ ); - $this->database->endAtomic( __METHOD__ . '_outer' ); - $this->assertNull( $callback1Called ); - $this->assertNull( $callback2Called ); - $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called ); - // phpcs:ignore Generic.Files.LineLength - $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; SAVEPOINT wikimedia_rdbms_atomic2; RELEASE SAVEPOINT wikimedia_rdbms_atomic2; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; COMMIT; SELECT 3' ); - - $callback1Called = null; - $callback2Called = null; - $callback3Called = null; - $this->database->startAtomic( __METHOD__ . '_outer' ); - $atomicId = $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->startAtomic( __METHOD__ . '_inner' ); - $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ ); - $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ ); - $this->database->onTransactionResolution( $callback3, __METHOD__ ); - $this->database->cancelAtomic( __METHOD__, $atomicId ); - $this->database->endAtomic( __METHOD__ . '_outer' ); - $this->assertNull( $callback1Called ); - $this->assertNull( $callback2Called ); - $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called ); - - $callback1Called = null; - $callback2Called = null; - $callback3Called = null; - $this->database->startAtomic( __METHOD__ . '_outer' ); - $atomicId = $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->startAtomic( __METHOD__ . '_inner' ); - $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ ); - $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ ); - $this->database->onTransactionResolution( $callback3, __METHOD__ ); - try { - $this->database->cancelAtomic( __METHOD__ . '_X', $atomicId ); - } catch ( DBUnexpectedError $e ) { - $m = __METHOD__; - $this->assertSame( - "Invalid atomic section ended (got {$m}_X but expected {$m}).", - $e->getMessage() - ); - } - $this->database->cancelAtomic( __METHOD__ ); - $this->database->endAtomic( __METHOD__ . '_outer' ); - $this->assertNull( $callback1Called ); - $this->assertNull( $callback2Called ); - $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called ); - - $this->database->startAtomic( __METHOD__ . '_outer' ); - $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->startAtomic( __METHOD__ . '_inner' ); - $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ ); - $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ ); - $this->database->onTransactionResolution( $callback3, __METHOD__ ); - $this->database->cancelAtomic( __METHOD__ . '_inner' ); - $this->database->cancelAtomic( __METHOD__ ); - $this->database->endAtomic( __METHOD__ . '_outer' ); - $this->assertNull( $callback1Called ); - $this->assertNull( $callback2Called ); - $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called ); - - $wrapper = TestingAccessWrapper::newFromObject( $this->database ); - $callback1Called = null; - $callback2Called = null; - $callback3Called = null; - $this->database->startAtomic( __METHOD__ . '_outer' ); - $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->startAtomic( __METHOD__ . '_inner' ); - $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ ); - $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ ); - $this->database->onTransactionResolution( $callback3, __METHOD__ ); - $wrapper->trxStatus = Database::STATUS_TRX_ERROR; - $this->database->cancelAtomic( __METHOD__ . '_inner' ); - $this->database->cancelAtomic( __METHOD__ ); - $this->database->endAtomic( __METHOD__ . '_outer' ); - $this->assertNull( $callback1Called ); - $this->assertNull( $callback2Called ); - $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called ); - } - - /** - * @covers \Wikimedia\Rdbms\Database::doSavepoint - * @covers \Wikimedia\Rdbms\Database::doReleaseSavepoint - * @covers \Wikimedia\Rdbms\Database::doRollbackToSavepoint - * @covers \Wikimedia\Rdbms\Database::startAtomic - * @covers \Wikimedia\Rdbms\Database::endAtomic - * @covers \Wikimedia\Rdbms\Database::cancelAtomic - * @covers \Wikimedia\Rdbms\Database::doAtomicSection - */ - public function testAtomicSectionsTrxRound() { - $this->database->setFlag( IDatabase::DBO_TRX ); - $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE ); - $this->database->query( 'SELECT 1', __METHOD__ ); - $this->database->endAtomic( __METHOD__ ); - $this->database->commit( __METHOD__, IDatabase::FLUSHING_ALL_PEERS ); - // phpcs:ignore Generic.Files.LineLength - $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; SELECT 1; RELEASE SAVEPOINT wikimedia_rdbms_atomic1; COMMIT' ); - } - - public static function provideAtomicSectionMethodsForErrors() { - return [ - [ 'endAtomic' ], - [ 'cancelAtomic' ], - ]; - } - - /** - * @dataProvider provideAtomicSectionMethodsForErrors - * @covers \Wikimedia\Rdbms\Database::endAtomic - * @covers \Wikimedia\Rdbms\Database::cancelAtomic - */ - public function testNoAtomicSection( $method ) { - try { - $this->database->$method( __METHOD__ ); - $this->fail( 'Expected exception not thrown' ); - } catch ( DBUnexpectedError $ex ) { - $this->assertSame( - 'No atomic section is open (got ' . __METHOD__ . ').', - $ex->getMessage() - ); - } - } - - /** - * @dataProvider provideAtomicSectionMethodsForErrors - * @covers \Wikimedia\Rdbms\Database::endAtomic - * @covers \Wikimedia\Rdbms\Database::cancelAtomic - */ - public function testInvalidAtomicSectionEnded( $method ) { - $this->database->startAtomic( __METHOD__ . 'X' ); - try { - $this->database->$method( __METHOD__ ); - $this->fail( 'Expected exception not thrown' ); - } catch ( DBUnexpectedError $ex ) { - $this->assertSame( - 'Invalid atomic section ended (got ' . __METHOD__ . ' but expected ' . - __METHOD__ . 'X).', - $ex->getMessage() - ); - } - } - - /** - * @covers \Wikimedia\Rdbms\Database::cancelAtomic - */ - public function testUncancellableAtomicSection() { - $this->database->startAtomic( __METHOD__ ); - try { - $this->database->cancelAtomic( __METHOD__ ); - $this->database->select( 'test', '1', [], __METHOD__ ); - $this->fail( 'Expected exception not thrown' ); - } catch ( DBTransactionError $ex ) { - $this->assertSame( - 'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR.', - $ex->getMessage() - ); - } - } - - /** - * @expectedException \Wikimedia\Rdbms\DBTransactionStateError - * @covers \Wikimedia\Rdbms\Database::assertQueryIsCurrentlyAllowed - */ - public function testTransactionErrorState1() { - $wrapper = TestingAccessWrapper::newFromObject( $this->database ); - - $this->database->begin( __METHOD__ ); - $wrapper->trxStatus = Database::STATUS_TRX_ERROR; - $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ ); - $this->database->commit( __METHOD__ ); - } - - /** - * @covers \Wikimedia\Rdbms\Database::query - */ - public function testTransactionErrorState2() { - $wrapper = TestingAccessWrapper::newFromObject( $this->database ); - - $this->database->startAtomic( __METHOD__ ); - $wrapper->trxStatus = Database::STATUS_TRX_ERROR; - $this->database->rollback( __METHOD__ ); - $this->assertEquals( 0, $this->database->trxLevel() ); - $this->assertEquals( Database::STATUS_TRX_NONE, $wrapper->trxStatus() ); - $this->assertLastSql( 'BEGIN; ROLLBACK' ); - - $this->database->startAtomic( __METHOD__ ); - $this->assertEquals( Database::STATUS_TRX_OK, $wrapper->trxStatus() ); - $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ ); - $this->database->endAtomic( __METHOD__ ); - $this->assertEquals( Database::STATUS_TRX_NONE, $wrapper->trxStatus() ); - $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'1\'; COMMIT' ); - $this->assertEquals( 0, $this->database->trxLevel(), 'Use after rollback()' ); - - $this->database->begin( __METHOD__ ); - $this->database->startAtomic( __METHOD__, Database::ATOMIC_CANCELABLE ); - $this->database->update( 'y', [ 'a' => 1 ], [ 'field' => 1 ], __METHOD__ ); - $wrapper->trxStatus = Database::STATUS_TRX_ERROR; - $this->database->cancelAtomic( __METHOD__ ); - $this->assertEquals( Database::STATUS_TRX_OK, $wrapper->trxStatus() ); - $this->database->startAtomic( __METHOD__ ); - $this->database->delete( 'y', [ 'field' => 1 ], __METHOD__ ); - $this->database->endAtomic( __METHOD__ ); - $this->database->commit( __METHOD__ ); - // phpcs:ignore Generic.Files.LineLength - $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; UPDATE y SET a = \'1\' WHERE field = \'1\'; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; DELETE FROM y WHERE field = \'1\'; COMMIT' ); - $this->assertEquals( 0, $this->database->trxLevel(), 'Use after rollback()' ); - - // Next transaction - $this->database->startAtomic( __METHOD__ ); - $this->assertEquals( Database::STATUS_TRX_OK, $wrapper->trxStatus() ); - $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ ); - $this->database->endAtomic( __METHOD__ ); - $this->assertEquals( Database::STATUS_TRX_NONE, $wrapper->trxStatus() ); - $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'3\'; COMMIT' ); - $this->assertEquals( 0, $this->database->trxLevel() ); - } - - /** - * @covers \Wikimedia\Rdbms\Database::query - */ - public function testImplicitTransactionRollback() { - $doError = function () { - $this->database->forceNextQueryError( 666, 'Evilness' ); - try { - $this->database->delete( 'error', '1', __CLASS__ . '::SomeCaller' ); - $this->fail( 'Expected exception not thrown' ); - } catch ( DBError $e ) { - $this->assertSame( 666, $e->errno ); - } - }; - - $this->database->setFlag( Database::DBO_TRX ); - - // Implicit transaction does not get silently rolled back - $this->database->begin( __METHOD__, Database::TRANSACTION_INTERNAL ); - call_user_func( $doError ); - try { - $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ ); - $this->fail( 'Expected exception not thrown' ); - } catch ( DBTransactionError $e ) { - $this->assertEquals( - 'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR.', - $e->getMessage() - ); - } - try { - $this->database->commit( __METHOD__, Database::FLUSHING_INTERNAL ); - $this->fail( 'Expected exception not thrown' ); - } catch ( DBTransactionError $e ) { - $this->assertEquals( - 'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR.', - $e->getMessage() - ); - } - $this->database->rollback( __METHOD__, Database::FLUSHING_INTERNAL ); - $this->assertLastSql( 'BEGIN; DELETE FROM error WHERE 1; ROLLBACK' ); - - // Likewise if there were prior writes - $this->database->begin( __METHOD__, Database::TRANSACTION_INTERNAL ); - $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ ); - call_user_func( $doError ); - try { - $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ ); - $this->fail( 'Expected exception not thrown' ); - } catch ( DBTransactionStateError $e ) { - } - $this->database->rollback( __METHOD__, Database::FLUSHING_INTERNAL ); - // phpcs:ignore - $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'1\'; DELETE FROM error WHERE 1; ROLLBACK' ); - } - - /** - * @covers \Wikimedia\Rdbms\Database::query - */ - public function testTransactionStatementRollbackIgnoring() { - $wrapper = TestingAccessWrapper::newFromObject( $this->database ); - $warning = []; - $wrapper->deprecationLogger = function ( $msg ) use ( &$warning ) { - $warning[] = $msg; - }; - - $doError = function () { - $this->database->forceNextQueryError( 666, 'Evilness', [ - 'wasKnownStatementRollbackError' => true, - ] ); - try { - $this->database->delete( 'error', '1', __CLASS__ . '::SomeCaller' ); - $this->fail( 'Expected exception not thrown' ); - } catch ( DBError $e ) { - $this->assertSame( 666, $e->errno ); - } - }; - $expectWarning = 'Caller from ' . __METHOD__ . - ' ignored an error originally raised from ' . __CLASS__ . '::SomeCaller: [666] Evilness'; - - // Rollback doesn't raise a warning - $warning = []; - $this->database->startAtomic( __METHOD__ ); - call_user_func( $doError ); - $this->database->rollback( __METHOD__ ); - $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ ); - $this->assertSame( [], $warning ); - // phpcs:ignore - $this->assertLastSql( 'BEGIN; DELETE FROM error WHERE 1; ROLLBACK; DELETE FROM x WHERE field = \'1\'' ); - - // cancelAtomic() doesn't raise a warning - $warning = []; - $this->database->begin( __METHOD__ ); - $this->database->startAtomic( __METHOD__, Database::ATOMIC_CANCELABLE ); - call_user_func( $doError ); - $this->database->cancelAtomic( __METHOD__ ); - $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ ); - $this->database->commit( __METHOD__ ); - $this->assertSame( [], $warning ); - // phpcs:ignore - $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; DELETE FROM error WHERE 1; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; DELETE FROM x WHERE field = \'1\'; COMMIT' ); - - // Commit does raise a warning - $warning = []; - $this->database->begin( __METHOD__ ); - call_user_func( $doError ); - $this->database->commit( __METHOD__ ); - $this->assertSame( [ $expectWarning ], $warning ); - $this->assertLastSql( 'BEGIN; DELETE FROM error WHERE 1; COMMIT' ); - - // Deprecation only gets raised once - $warning = []; - $this->database->begin( __METHOD__ ); - call_user_func( $doError ); - $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ ); - $this->database->commit( __METHOD__ ); - $this->assertSame( [ $expectWarning ], $warning ); - // phpcs:ignore - $this->assertLastSql( 'BEGIN; DELETE FROM error WHERE 1; DELETE FROM x WHERE field = \'1\'; COMMIT' ); - } - - /** - * @covers \Wikimedia\Rdbms\Database::close - */ - public function testPrematureClose1() { - $fname = __METHOD__; - $this->database->begin( __METHOD__ ); - $this->database->onTransactionCommitOrIdle( function () use ( $fname ) { - $this->database->query( 'SELECT 1', $fname ); - } ); - $this->database->onTransactionResolution( function () use ( $fname ) { - $this->database->query( 'SELECT 2', $fname ); - } ); - $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ ); - try { - $this->database->close(); - $this->fail( 'Expected exception not thrown' ); - } catch ( DBUnexpectedError $ex ) { - $this->assertSame( - "Wikimedia\Rdbms\Database::close: transaction is still open (from $fname).", - $ex->getMessage() - ); - } - - $this->assertFalse( $this->database->isOpen() ); - $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'3\'; ROLLBACK; SELECT 2' ); - $this->assertEquals( 0, $this->database->trxLevel() ); - } - - /** - * @covers \Wikimedia\Rdbms\Database::close - */ - public function testPrematureClose2() { - try { - $fname = __METHOD__; - $this->database->startAtomic( __METHOD__ ); - $this->database->onTransactionCommitOrIdle( function () use ( $fname ) { - $this->database->query( 'SELECT 1', $fname ); - } ); - $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ ); - $this->database->close(); - $this->fail( 'Expected exception not thrown' ); - } catch ( DBUnexpectedError $ex ) { - $this->assertSame( - 'Wikimedia\Rdbms\Database::close: atomic sections ' . - 'DatabaseSQLTest::testPrematureClose2 are still open.', - $ex->getMessage() - ); - } - - $this->assertFalse( $this->database->isOpen() ); - $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'3\'; ROLLBACK' ); - $this->assertEquals( 0, $this->database->trxLevel() ); - } - - /** - * @covers \Wikimedia\Rdbms\Database::close - */ - public function testPrematureClose3() { - try { - $this->database->setFlag( IDatabase::DBO_TRX ); - $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ ); - $this->assertEquals( 1, $this->database->trxLevel() ); - $this->database->close(); - $this->fail( 'Expected exception not thrown' ); - } catch ( DBUnexpectedError $ex ) { - $this->assertSame( - 'Wikimedia\Rdbms\Database::close: ' . - 'mass commit/rollback of peer transaction required (DBO_TRX set).', - $ex->getMessage() - ); - } - - $this->assertFalse( $this->database->isOpen() ); - $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'3\'; ROLLBACK' ); - $this->assertEquals( 0, $this->database->trxLevel() ); - } - - /** - * @covers \Wikimedia\Rdbms\Database::close - */ - public function testPrematureClose4() { - $this->database->setFlag( IDatabase::DBO_TRX ); - $this->database->query( 'SELECT 1', __METHOD__ ); - $this->assertEquals( 1, $this->database->trxLevel() ); - $this->database->close(); - $this->database->clearFlag( IDatabase::DBO_TRX ); - - $this->assertFalse( $this->database->isOpen() ); - $this->assertLastSql( 'BEGIN; SELECT 1; ROLLBACK' ); - $this->assertEquals( 0, $this->database->trxLevel() ); - } - - /** - * @covers Wikimedia\Rdbms\Database::selectFieldValues() - */ - public function testSelectFieldValues() { - $this->database->forceNextResult( [ - (object)[ 'value' => 'row1' ], - (object)[ 'value' => 'row2' ], - (object)[ 'value' => 'row3' ], - ] ); - - $this->assertSame( - [ 'row1', 'row2', 'row3' ], - $this->database->selectFieldValues( 'table', 'table.field', 'conds', __METHOD__ ) - ); - $this->assertLastSql( 'SELECT table.field AS value FROM table WHERE conds' ); - } -} diff --git a/tests/phpunit/includes/libs/rdbms/database/DatabaseSqliteRdbmsTest.php b/tests/phpunit/includes/libs/rdbms/database/DatabaseSqliteRdbmsTest.php deleted file mode 100644 index a886d6bf76..0000000000 --- a/tests/phpunit/includes/libs/rdbms/database/DatabaseSqliteRdbmsTest.php +++ /dev/null @@ -1,60 +0,0 @@ -getMockBuilder( DatabaseSqlite::class ) - ->disableOriginalConstructor() - ->setMethods( null ) - ->getMock(); - } - - public function provideBuildSubstring() { - yield [ 'someField', 1, 2, 'SUBSTR(someField,1,2)' ]; - yield [ 'someField', 1, null, 'SUBSTR(someField,1)' ]; - } - - /** - * @covers Wikimedia\Rdbms\DatabaseSqlite::buildSubstring - * @dataProvider provideBuildSubstring - */ - public function testBuildSubstring( $input, $start, $length, $expected ) { - $dbMock = $this->getMockDb(); - $output = $dbMock->buildSubstring( $input, $start, $length ); - $this->assertSame( $expected, $output ); - } - - public function provideBuildSubstring_invalidParams() { - yield [ -1, 1 ]; - yield [ 1, -1 ]; - yield [ 1, 'foo' ]; - yield [ 'foo', 1 ]; - yield [ null, 1 ]; - yield [ 0, 1 ]; - } - - /** - * @covers Wikimedia\Rdbms\DatabaseSqlite::buildSubstring - * @dataProvider provideBuildSubstring_invalidParams - */ - public function testBuildSubstring_invalidParams( $start, $length ) { - $dbMock = $this->getMockDb(); - $this->setExpectedException( InvalidArgumentException::class ); - $dbMock->buildSubstring( 'foo', $start, $length ); - } - -} diff --git a/tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php b/tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php deleted file mode 100644 index 8b24791ca6..0000000000 --- a/tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php +++ /dev/null @@ -1,707 +0,0 @@ -db = new DatabaseTestHelper( __CLASS__ . '::' . $this->getName() ); - } - - /** - * @dataProvider provideAddQuotes - * @covers Wikimedia\Rdbms\Database::factory - */ - public function testFactory() { - $m = Database::NEW_UNCONNECTED; // no-connect mode - $p = [ 'host' => 'localhost', 'user' => 'me', 'password' => 'myself', 'dbname' => 'i' ]; - - $this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'mysqli', $p, $m ) ); - $this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'MySqli', $p, $m ) ); - $this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'MySQLi', $p, $m ) ); - $this->assertInstanceOf( DatabasePostgres::class, Database::factory( 'postgres', $p, $m ) ); - $this->assertInstanceOf( DatabasePostgres::class, Database::factory( 'Postgres', $p, $m ) ); - - $x = $p + [ 'port' => 10000, 'UseWindowsAuth' => false ]; - $this->assertInstanceOf( DatabaseMssql::class, Database::factory( 'mssql', $x, $m ) ); - - $x = $p + [ 'dbFilePath' => 'some/file.sqlite' ]; - $this->assertInstanceOf( DatabaseSqlite::class, Database::factory( 'sqlite', $x, $m ) ); - $x = $p + [ 'dbDirectory' => 'some/file' ]; - $this->assertInstanceOf( DatabaseSqlite::class, Database::factory( 'sqlite', $x, $m ) ); - } - - public static function provideAddQuotes() { - return [ - [ null, 'NULL' ], - [ 1234, "'1234'" ], - [ 1234.5678, "'1234.5678'" ], - [ 'string', "'string'" ], - [ 'string\'s cause trouble', "'string\'s cause trouble'" ], - ]; - } - - /** - * @dataProvider provideAddQuotes - * @covers Wikimedia\Rdbms\Database::addQuotes - */ - public function testAddQuotes( $input, $expected ) { - $this->assertEquals( $expected, $this->db->addQuotes( $input ) ); - } - - public static function provideTableName() { - // Formatting is mostly ignored since addIdentifierQuotes is abstract. - // For testing of addIdentifierQuotes, see actual Database subclas tests. - return [ - 'local' => [ - 'tablename', - 'tablename', - 'quoted', - ], - 'local-raw' => [ - 'tablename', - 'tablename', - 'raw', - ], - 'shared' => [ - 'sharedb.tablename', - 'tablename', - 'quoted', - [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => '' ], - ], - 'shared-raw' => [ - 'sharedb.tablename', - 'tablename', - 'raw', - [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => '' ], - ], - 'shared-prefix' => [ - 'sharedb.sh_tablename', - 'tablename', - 'quoted', - [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => 'sh_' ], - ], - 'shared-prefix-raw' => [ - 'sharedb.sh_tablename', - 'tablename', - 'raw', - [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => 'sh_' ], - ], - 'foreign' => [ - 'databasename.tablename', - 'databasename.tablename', - 'quoted', - ], - 'foreign-raw' => [ - 'databasename.tablename', - 'databasename.tablename', - 'raw', - ], - ]; - } - - /** - * @dataProvider provideTableName - * @covers Wikimedia\Rdbms\Database::tableName - */ - public function testTableName( $expected, $table, $format, array $alias = null ) { - if ( $alias ) { - $this->db->setTableAliases( [ $table => $alias ] ); - } - $this->assertEquals( - $expected, - $this->db->tableName( $table, $format ?: 'quoted' ) - ); - } - - public function provideTableNamesWithIndexClauseOrJOIN() { - return [ - 'one-element array' => [ - [ 'table' ], [], 'table ' - ], - 'comma join' => [ - [ 'table1', 'table2' ], [], 'table1,table2 ' - ], - 'real join' => [ - [ 'table1', 'table2' ], - [ 'table2' => [ 'LEFT JOIN', 't1_id = t2_id' ] ], - 'table1 LEFT JOIN table2 ON ((t1_id = t2_id))' - ], - 'real join with multiple conditionals' => [ - [ 'table1', 'table2' ], - [ 'table2' => [ 'LEFT JOIN', [ 't1_id = t2_id', 't2_x = \'X\'' ] ] ], - 'table1 LEFT JOIN table2 ON ((t1_id = t2_id) AND (t2_x = \'X\'))' - ], - 'join with parenthesized group' => [ - [ 'table1', 'n' => [ 'table2', 'table3' ] ], - [ - 'table3' => [ 'JOIN', 't2_id = t3_id' ], - 'n' => [ 'LEFT JOIN', 't1_id = t2_id' ], - ], - 'table1 LEFT JOIN (table2 JOIN table3 ON ((t2_id = t3_id))) ON ((t1_id = t2_id))' - ], - 'join with degenerate parenthesized group' => [ - [ 'table1', 'n' => [ 't2' => 'table2' ] ], - [ - 'n' => [ 'LEFT JOIN', 't1_id = t2_id' ], - ], - 'table1 LEFT JOIN table2 t2 ON ((t1_id = t2_id))' - ], - ]; - } - - /** - * @dataProvider provideTableNamesWithIndexClauseOrJOIN - * @covers Wikimedia\Rdbms\Database::tableNamesWithIndexClauseOrJOIN - */ - public function testTableNamesWithIndexClauseOrJOIN( $tables, $join_conds, $expect ) { - $clause = TestingAccessWrapper::newFromObject( $this->db ) - ->tableNamesWithIndexClauseOrJOIN( $tables, [], [], $join_conds ); - $this->assertSame( $expect, $clause ); - } - - /** - * @covers Wikimedia\Rdbms\Database::onTransactionCommitOrIdle - * @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks - */ - public function testTransactionIdle() { - $db = $this->db; - - $db->clearFlag( DBO_TRX ); - $called = false; - $flagSet = null; - $callback = function ( $trigger, IDatabase $db ) use ( &$flagSet, &$called ) { - $called = true; - $flagSet = $db->getFlag( DBO_TRX ); - }; - - $db->onTransactionCommitOrIdle( $callback, __METHOD__ ); - $this->assertTrue( $called, 'Callback reached' ); - $this->assertFalse( $flagSet, 'DBO_TRX off in callback' ); - $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX still default' ); - - $flagSet = null; - $called = false; - $db->startAtomic( __METHOD__ ); - $db->onTransactionCommitOrIdle( $callback, __METHOD__ ); - $this->assertFalse( $called, 'Callback not reached during TRX' ); - $db->endAtomic( __METHOD__ ); - - $this->assertTrue( $called, 'Callback reached after COMMIT' ); - $this->assertFalse( $flagSet, 'DBO_TRX off in callback' ); - $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); - - $db->clearFlag( DBO_TRX ); - $db->onTransactionCommitOrIdle( - function ( $trigger, IDatabase $db ) { - $db->setFlag( DBO_TRX ); - }, - __METHOD__ - ); - $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); - } - - /** - * @covers Wikimedia\Rdbms\Database::onTransactionCommitOrIdle - * @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks - */ - public function testTransactionIdle_TRX() { - $db = $this->getMockDB( [ 'isOpen', 'ping', 'getDBname' ] ); - $db->method( 'isOpen' )->willReturn( true ); - $db->method( 'ping' )->willReturn( true ); - $db->method( 'getDBname' )->willReturn( '' ); - $db->setFlag( DBO_TRX ); - - $lbFactory = LBFactorySingle::newFromConnection( $db ); - // Ask for the connection so that LB sets internal state - // about this connection being the master connection - $lb = $lbFactory->getMainLB(); - $conn = $lb->openConnection( $lb->getWriterIndex() ); - $this->assertSame( $db, $conn, 'Same DB instance' ); - $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX is set' ); - - $called = false; - $flagSet = null; - $callback = function () use ( $db, &$flagSet, &$called ) { - $called = true; - $flagSet = $db->getFlag( DBO_TRX ); - }; - - $db->onTransactionCommitOrIdle( $callback, __METHOD__ ); - $this->assertTrue( $called, 'Called when idle if DBO_TRX is set' ); - $this->assertFalse( $flagSet, 'DBO_TRX off in callback' ); - $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX still default' ); - - $called = false; - $lbFactory->beginMasterChanges( __METHOD__ ); - $db->onTransactionCommitOrIdle( $callback, __METHOD__ ); - $this->assertFalse( $called, 'Not called when lb-transaction is active' ); - - $lbFactory->commitMasterChanges( __METHOD__ ); - $this->assertTrue( $called, 'Called when lb-transaction is committed' ); - - $called = false; - $lbFactory->beginMasterChanges( __METHOD__ ); - $db->onTransactionCommitOrIdle( $callback, __METHOD__ ); - $this->assertFalse( $called, 'Not called when lb-transaction is active' ); - - $lbFactory->rollbackMasterChanges( __METHOD__ ); - $this->assertFalse( $called, 'Not called when lb-transaction is rolled back' ); - - $lbFactory->commitMasterChanges( __METHOD__ ); - $this->assertFalse( $called, 'Not called in next round commit' ); - - $db->setFlag( DBO_TRX ); - try { - $db->onTransactionCommitOrIdle( function () { - throw new RuntimeException( 'test' ); - } ); - $this->fail( "Exception not thrown" ); - } catch ( RuntimeException $e ) { - $this->assertTrue( $db->getFlag( DBO_TRX ) ); - } - } - - /** - * @covers Wikimedia\Rdbms\Database::onTransactionPreCommitOrIdle - * @covers Wikimedia\Rdbms\Database::runOnTransactionPreCommitCallbacks - */ - public function testTransactionPreCommitOrIdle() { - $db = $this->getMockDB( [ 'isOpen' ] ); - $db->method( 'isOpen' )->willReturn( true ); - $db->clearFlag( DBO_TRX ); - - $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX is not set' ); - - $called = false; - $db->onTransactionPreCommitOrIdle( - function ( IDatabase $db ) use ( &$called ) { - $called = true; - }, - __METHOD__ - ); - $this->assertTrue( $called, 'Called when idle' ); - - $db->begin( __METHOD__ ); - $called = false; - $db->onTransactionPreCommitOrIdle( - function ( IDatabase $db ) use ( &$called ) { - $called = true; - }, - __METHOD__ - ); - $this->assertFalse( $called, 'Not called when transaction is active' ); - $db->commit( __METHOD__ ); - $this->assertTrue( $called, 'Called when transaction is committed' ); - } - - /** - * @covers Wikimedia\Rdbms\Database::onTransactionPreCommitOrIdle - * @covers Wikimedia\Rdbms\Database::runOnTransactionPreCommitCallbacks - */ - public function testTransactionPreCommitOrIdle_TRX() { - $db = $this->getMockDB( [ 'isOpen', 'ping', 'getDBname' ] ); - $db->method( 'isOpen' )->willReturn( true ); - $db->method( 'ping' )->willReturn( true ); - $db->method( 'getDBname' )->willReturn( 'unittest' ); - $db->setFlag( DBO_TRX ); - - $lbFactory = LBFactorySingle::newFromConnection( $db ); - // Ask for the connection so that LB sets internal state - // about this connection being the master connection - $lb = $lbFactory->getMainLB(); - $conn = $lb->openConnection( $lb->getWriterIndex() ); - $this->assertSame( $db, $conn, 'Same DB instance' ); - - $this->assertFalse( $lb->hasMasterChanges() ); - $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX is set' ); - $called = false; - $callback = function ( IDatabase $db ) use ( &$called ) { - $called = true; - }; - $db->onTransactionPreCommitOrIdle( $callback, __METHOD__ ); - $this->assertTrue( $called, 'Called when idle if DBO_TRX is set' ); - $called = false; - $lbFactory->commitMasterChanges(); - $this->assertFalse( $called ); - - $called = false; - $lbFactory->beginMasterChanges( __METHOD__ ); - $db->onTransactionPreCommitOrIdle( $callback, __METHOD__ ); - $this->assertFalse( $called, 'Not called when lb-transaction is active' ); - $lbFactory->commitMasterChanges( __METHOD__ ); - $this->assertTrue( $called, 'Called when lb-transaction is committed' ); - - $called = false; - $lbFactory->beginMasterChanges( __METHOD__ ); - $db->onTransactionPreCommitOrIdle( $callback, __METHOD__ ); - $this->assertFalse( $called, 'Not called when lb-transaction is active' ); - - $lbFactory->rollbackMasterChanges( __METHOD__ ); - $this->assertFalse( $called, 'Not called when lb-transaction is rolled back' ); - - $lbFactory->commitMasterChanges( __METHOD__ ); - $this->assertFalse( $called, 'Not called in next round commit' ); - } - - /** - * @covers Wikimedia\Rdbms\Database::onTransactionResolution - * @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks - */ - public function testTransactionResolution() { - $db = $this->db; - - $db->clearFlag( DBO_TRX ); - $db->begin( __METHOD__ ); - $called = false; - $db->onTransactionResolution( function ( $trigger, IDatabase $db ) use ( &$called ) { - $called = true; - $db->setFlag( DBO_TRX ); - } ); - $db->commit( __METHOD__ ); - $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); - $this->assertTrue( $called, 'Callback reached' ); - - $db->clearFlag( DBO_TRX ); - $db->begin( __METHOD__ ); - $called = false; - $db->onTransactionResolution( function ( $trigger, IDatabase $db ) use ( &$called ) { - $called = true; - $db->setFlag( DBO_TRX ); - } ); - $db->rollback( __METHOD__ ); - $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); - $this->assertTrue( $called, 'Callback reached' ); - } - - /** - * @covers Wikimedia\Rdbms\Database::setTransactionListener - */ - public function testTransactionListener() { - $db = $this->db; - - $db->setTransactionListener( 'ping', function () use ( $db, &$called ) { - $called = true; - } ); - - $called = false; - $db->begin( __METHOD__ ); - $db->commit( __METHOD__ ); - $this->assertTrue( $called, 'Callback reached' ); - - $called = false; - $db->begin( __METHOD__ ); - $db->commit( __METHOD__ ); - $this->assertTrue( $called, 'Callback still reached' ); - - $called = false; - $db->begin( __METHOD__ ); - $db->rollback( __METHOD__ ); - $this->assertTrue( $called, 'Callback reached' ); - - $db->setTransactionListener( 'ping', null ); - $called = false; - $db->begin( __METHOD__ ); - $db->commit( __METHOD__ ); - $this->assertFalse( $called, 'Callback not reached' ); - } - - /** - * Use this mock instead of DatabaseTestHelper for cases where - * DatabaseTestHelper is too inflexibile due to mocking too much - * or being too restrictive about fname matching (e.g. for tests - * that assert behaviour when the name is a mismatch, we need to - * catch the error here instead of there). - * - * @return Database - */ - private function getMockDB( $methods = [] ) { - static $abstractMethods = [ - 'fetchAffectedRowCount', - 'closeConnection', - 'dataSeek', - 'doQuery', - 'fetchObject', 'fetchRow', - 'fieldInfo', 'fieldName', - 'getSoftwareLink', 'getServerVersion', - 'getType', - 'indexInfo', - 'insertId', - 'lastError', 'lastErrno', - 'numFields', 'numRows', - 'open', - 'strencode', - 'tableExists' - ]; - $db = $this->getMockBuilder( Database::class ) - ->disableOriginalConstructor() - ->setMethods( array_values( array_unique( array_merge( - $abstractMethods, - $methods - ) ) ) ) - ->getMock(); - $wdb = TestingAccessWrapper::newFromObject( $db ); - $wdb->trxProfiler = new TransactionProfiler(); - $wdb->connLogger = new \Psr\Log\NullLogger(); - $wdb->queryLogger = new \Psr\Log\NullLogger(); - $wdb->currentDomain = DatabaseDomain::newUnspecified(); - return $db; - } - - /** - * @covers Wikimedia\Rdbms\Database::flushSnapshot - */ - public function testFlushSnapshot() { - $db = $this->getMockDB( [ 'isOpen' ] ); - $db->method( 'isOpen' )->willReturn( true ); - - $db->flushSnapshot( __METHOD__ ); // ok - $db->flushSnapshot( __METHOD__ ); // ok - - $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR ); - $db->query( 'SELECT 1', __METHOD__ ); - $this->assertTrue( (bool)$db->trxLevel(), "Transaction started." ); - $db->flushSnapshot( __METHOD__ ); // ok - $db->restoreFlags( $db::RESTORE_PRIOR ); - - $this->assertFalse( (bool)$db->trxLevel(), "Transaction cleared." ); - } - - /** - * @covers Wikimedia\Rdbms\Database::getScopedLockAndFlush - * @covers Wikimedia\Rdbms\Database::lock - * @covers Wikimedia\Rdbms\Database::unlock - * @covers Wikimedia\Rdbms\Database::lockIsFree - */ - public function testGetScopedLock() { - $db = $this->getMockDB( [ 'isOpen', 'getDBname' ] ); - $db->method( 'isOpen' )->willReturn( true ); - $db->method( 'getDBname' )->willReturn( 'unittest' ); - - $this->assertEquals( 0, $db->trxLevel() ); - $this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) ); - $this->assertEquals( true, $db->lock( 'x', __METHOD__ ) ); - $this->assertEquals( false, $db->lockIsFree( 'x', __METHOD__ ) ); - $this->assertEquals( true, $db->unlock( 'x', __METHOD__ ) ); - $this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) ); - $this->assertEquals( 0, $db->trxLevel() ); - - $db->setFlag( DBO_TRX ); - $this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) ); - $this->assertEquals( true, $db->lock( 'x', __METHOD__ ) ); - $this->assertEquals( false, $db->lockIsFree( 'x', __METHOD__ ) ); - $this->assertEquals( true, $db->unlock( 'x', __METHOD__ ) ); - $this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) ); - $db->clearFlag( DBO_TRX ); - - // Pending writes with DBO_TRX - $this->assertEquals( 0, $db->trxLevel() ); - $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) ); - $db->setFlag( DBO_TRX ); - $db->query( "DELETE FROM test WHERE t = 1" ); // trigger DBO_TRX transaction before lock - try { - $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 ); - $this->fail( "Exception not reached" ); - } catch ( DBUnexpectedError $e ) { - $this->assertEquals( 1, $db->trxLevel(), "Transaction not committed." ); - $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ), 'Lock not acquired' ); - } - $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS ); - // Pending writes without DBO_TRX - $db->clearFlag( DBO_TRX ); - $this->assertEquals( 0, $db->trxLevel() ); - $this->assertTrue( $db->lockIsFree( 'meow2', __METHOD__ ) ); - $db->begin( __METHOD__ ); - $db->query( "DELETE FROM test WHERE t = 1" ); // trigger DBO_TRX transaction before lock - try { - $lock = $db->getScopedLockAndFlush( 'meow2', __METHOD__, 1 ); - $this->fail( "Exception not reached" ); - } catch ( DBUnexpectedError $e ) { - $this->assertEquals( 1, $db->trxLevel(), "Transaction not committed." ); - $this->assertTrue( $db->lockIsFree( 'meow2', __METHOD__ ), 'Lock not acquired' ); - } - $db->rollback( __METHOD__ ); - // No pending writes, with DBO_TRX - $db->setFlag( DBO_TRX ); - $this->assertEquals( 0, $db->trxLevel() ); - $this->assertTrue( $db->lockIsFree( 'wuff', __METHOD__ ) ); - $db->query( "SELECT 1", __METHOD__ ); - $this->assertEquals( 1, $db->trxLevel() ); - $lock = $db->getScopedLockAndFlush( 'wuff', __METHOD__, 1 ); - $this->assertEquals( 0, $db->trxLevel() ); - $this->assertFalse( $db->lockIsFree( 'wuff', __METHOD__ ), 'Lock already acquired' ); - $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS ); - // No pending writes, without DBO_TRX - $db->clearFlag( DBO_TRX ); - $this->assertEquals( 0, $db->trxLevel() ); - $this->assertTrue( $db->lockIsFree( 'wuff2', __METHOD__ ) ); - $db->begin( __METHOD__ ); - try { - $lock = $db->getScopedLockAndFlush( 'wuff2', __METHOD__, 1 ); - $this->fail( "Exception not reached" ); - } catch ( DBUnexpectedError $e ) { - $this->assertEquals( 1, $db->trxLevel(), "Transaction not committed." ); - $this->assertFalse( $db->lockIsFree( 'wuff2', __METHOD__ ), 'Lock not acquired' ); - } - $db->rollback( __METHOD__ ); - } - - /** - * @covers Wikimedia\Rdbms\Database::getFlag - * @covers Wikimedia\Rdbms\Database::setFlag - * @covers Wikimedia\Rdbms\Database::restoreFlags - */ - public function testFlagSetting() { - $db = $this->db; - $origTrx = $db->getFlag( DBO_TRX ); - $origSsl = $db->getFlag( DBO_SSL ); - - $origTrx - ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR ) - : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR ); - $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) ); - - $origSsl - ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR ) - : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR ); - $this->assertEquals( !$origSsl, $db->getFlag( DBO_SSL ) ); - - $db->restoreFlags( $db::RESTORE_INITIAL ); - $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) ); - $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) ); - - $origTrx - ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR ) - : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR ); - $origSsl - ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR ) - : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR ); - - $db->restoreFlags(); - $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) ); - $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) ); - - $db->restoreFlags(); - $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) ); - $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) ); - } - - /** - * @expectedException UnexpectedValueException - * @covers Wikimedia\Rdbms\Database::setFlag - */ - public function testDBOIgnoreSet() { - $db = $this->getMockBuilder( DatabaseMysqli::class ) - ->disableOriginalConstructor() - ->setMethods( null ) - ->getMock(); - - $db->setFlag( Database::DBO_IGNORE ); - } - - /** - * @expectedException UnexpectedValueException - * @covers Wikimedia\Rdbms\Database::clearFlag - */ - public function testDBOIgnoreClear() { - $db = $this->getMockBuilder( DatabaseMysqli::class ) - ->disableOriginalConstructor() - ->setMethods( null ) - ->getMock(); - - $db->clearFlag( Database::DBO_IGNORE ); - } - - /** - * @covers Wikimedia\Rdbms\Database::tablePrefix - * @covers Wikimedia\Rdbms\Database::dbSchema - */ - public function testSchemaAndPrefixMutators() { - $ud = DatabaseDomain::newUnspecified(); - - $this->assertEquals( $ud->getId(), $this->db->getDomainID() ); - - $old = $this->db->tablePrefix(); - $oldDomain = $this->db->getDomainId(); - $this->assertInternalType( 'string', $old, 'Prefix is string' ); - $this->assertSame( $old, $this->db->tablePrefix(), "Prefix unchanged" ); - $this->assertSame( $old, $this->db->tablePrefix( 'xxx_' ) ); - $this->assertSame( 'xxx_', $this->db->tablePrefix(), "Prefix set" ); - $this->db->tablePrefix( $old ); - $this->assertNotEquals( 'xxx_', $this->db->tablePrefix() ); - $this->assertSame( $oldDomain, $this->db->getDomainId() ); - - $old = $this->db->dbSchema(); - $oldDomain = $this->db->getDomainId(); - $this->assertInternalType( 'string', $old, 'Schema is string' ); - $this->assertSame( $old, $this->db->dbSchema(), "Schema unchanged" ); - - $this->db->selectDB( 'y' ); - $this->assertSame( $old, $this->db->dbSchema( 'xxx' ) ); - $this->assertSame( 'xxx', $this->db->dbSchema(), "Schema set" ); - $this->db->dbSchema( $old ); - $this->assertNotEquals( 'xxx', $this->db->dbSchema() ); - $this->assertSame( "y", $this->db->getDomainId() ); - } - - /** - * @covers Wikimedia\Rdbms\Database::tablePrefix - * @covers Wikimedia\Rdbms\Database::dbSchema - * @expectedException DBUnexpectedError - */ - public function testSchemaWithNoDB() { - $ud = DatabaseDomain::newUnspecified(); - - $this->assertEquals( $ud->getId(), $this->db->getDomainID() ); - $this->assertSame( '', $this->db->dbSchema() ); - - $this->db->dbSchema( 'xxx' ); - } - - /** - * @covers Wikimedia\Rdbms\Database::selectDomain - */ - public function testSelectDomain() { - $oldDomain = $this->db->getDomainId(); - $oldDatabase = $this->db->getDBname(); - $oldSchema = $this->db->dbSchema(); - $oldPrefix = $this->db->tablePrefix(); - - $this->db->selectDomain( 'testselectdb-xxx_' ); - $this->assertSame( 'testselectdb', $this->db->getDBname() ); - $this->assertSame( '', $this->db->dbSchema() ); - $this->assertSame( 'xxx_', $this->db->tablePrefix() ); - - $this->db->selectDomain( $oldDomain ); - $this->assertSame( $oldDatabase, $this->db->getDBname() ); - $this->assertSame( $oldSchema, $this->db->dbSchema() ); - $this->assertSame( $oldPrefix, $this->db->tablePrefix() ); - $this->assertSame( $oldDomain, $this->db->getDomainId() ); - - $this->db->selectDomain( 'testselectdb-schema-xxx_' ); - $this->assertSame( 'testselectdb', $this->db->getDBname() ); - $this->assertSame( 'schema', $this->db->dbSchema() ); - $this->assertSame( 'xxx_', $this->db->tablePrefix() ); - - $this->db->selectDomain( $oldDomain ); - $this->assertSame( $oldDatabase, $this->db->getDBname() ); - $this->assertSame( $oldSchema, $this->db->dbSchema() ); - $this->assertSame( $oldPrefix, $this->db->tablePrefix() ); - $this->assertSame( $oldDomain, $this->db->getDomainId() ); - } - -} diff --git a/tests/phpunit/includes/libs/services/ServiceContainerTest.php b/tests/phpunit/includes/libs/services/ServiceContainerTest.php deleted file mode 100644 index 6e51883cfb..0000000000 --- a/tests/phpunit/includes/libs/services/ServiceContainerTest.php +++ /dev/null @@ -1,497 +0,0 @@ -newServiceContainer(); - $names = $services->getServiceNames(); - - $this->assertInternalType( 'array', $names ); - $this->assertEmpty( $names ); - - $name = 'TestService92834576'; - $services->defineService( $name, function () { - return null; - } ); - - $names = $services->getServiceNames(); - $this->assertContains( $name, $names ); - } - - public function testHasService() { - $services = $this->newServiceContainer(); - - $name = 'TestService92834576'; - $this->assertFalse( $services->hasService( $name ) ); - - $services->defineService( $name, function () { - return null; - } ); - - $this->assertTrue( $services->hasService( $name ) ); - } - - public function testGetService() { - $services = $this->newServiceContainer( [ 'Foo' ] ); - - $theService = new stdClass(); - $name = 'TestService92834576'; - $count = 0; - - $services->defineService( - $name, - function ( $actualLocator, $extra ) use ( $services, $theService, &$count ) { - $count++; - PHPUnit_Framework_Assert::assertSame( $services, $actualLocator ); - PHPUnit_Framework_Assert::assertSame( $extra, 'Foo' ); - return $theService; - } - ); - - $this->assertSame( $theService, $services->getService( $name ) ); - - $services->getService( $name ); - $this->assertSame( 1, $count, 'instantiator should be called exactly once!' ); - } - - public function testGetService_fail_unknown() { - $services = $this->newServiceContainer(); - - $name = 'TestService92834576'; - - $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class ); - - $services->getService( $name ); - } - - public function testPeekService() { - $services = $this->newServiceContainer(); - - $services->defineService( - 'Foo', - function () { - return new stdClass(); - } - ); - - $services->defineService( - 'Bar', - function () { - return new stdClass(); - } - ); - - // trigger instantiation of Foo - $services->getService( 'Foo' ); - - $this->assertInternalType( - 'object', - $services->peekService( 'Foo' ), - 'Peek should return the service object if it had been accessed before.' - ); - - $this->assertNull( - $services->peekService( 'Bar' ), - 'Peek should return null if the service was never accessed.' - ); - } - - public function testPeekService_fail_unknown() { - $services = $this->newServiceContainer(); - - $name = 'TestService92834576'; - - $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class ); - - $services->peekService( $name ); - } - - public function testDefineService() { - $services = $this->newServiceContainer(); - - $theService = new stdClass(); - $name = 'TestService92834576'; - - $services->defineService( $name, function ( $actualLocator ) use ( $services, $theService ) { - PHPUnit_Framework_Assert::assertSame( $services, $actualLocator ); - return $theService; - } ); - - $this->assertTrue( $services->hasService( $name ) ); - $this->assertSame( $theService, $services->getService( $name ) ); - } - - public function testDefineService_fail_duplicate() { - $services = $this->newServiceContainer(); - - $theService = new stdClass(); - $name = 'TestService92834576'; - - $services->defineService( $name, function () use ( $theService ) { - return $theService; - } ); - - $this->setExpectedException( Wikimedia\Services\ServiceAlreadyDefinedException::class ); - - $services->defineService( $name, function () use ( $theService ) { - return $theService; - } ); - } - - public function testApplyWiring() { - $services = $this->newServiceContainer(); - - $wiring = [ - 'Foo' => function () { - return 'Foo!'; - }, - 'Bar' => function () { - return 'Bar!'; - }, - ]; - - $services->applyWiring( $wiring ); - - $this->assertSame( 'Foo!', $services->getService( 'Foo' ) ); - $this->assertSame( 'Bar!', $services->getService( 'Bar' ) ); - } - - public function testImportWiring() { - $services = $this->newServiceContainer(); - - $wiring = [ - 'Foo' => function () { - return 'Foo!'; - }, - 'Bar' => function () { - return 'Bar!'; - }, - 'Car' => function () { - return 'FUBAR!'; - }, - ]; - - $services->applyWiring( $wiring ); - - $services->addServiceManipulator( 'Foo', function ( $service ) { - return $service . '+X'; - } ); - - $services->addServiceManipulator( 'Car', function ( $service ) { - return $service . '+X'; - } ); - - $newServices = $this->newServiceContainer(); - - // create a service with manipulator - $newServices->defineService( 'Foo', function () { - return 'Foo!'; - } ); - - $newServices->addServiceManipulator( 'Foo', function ( $service ) { - return $service . '+Y'; - } ); - - // create a service before importing, so we can later check that - // existing service instances survive importWiring() - $newServices->defineService( 'Car', function () { - return 'Car!'; - } ); - - // force instantiation - $newServices->getService( 'Car' ); - - // Define another service, so we can later check that extra wiring - // is not lost. - $newServices->defineService( 'Xar', function () { - return 'Xar!'; - } ); - - // import wiring, but skip `Bar` - $newServices->importWiring( $services, [ 'Bar' ] ); - - $this->assertNotContains( 'Bar', $newServices->getServiceNames(), 'Skip `Bar` service' ); - $this->assertSame( 'Foo!+Y+X', $newServices->getService( 'Foo' ) ); - - // import all wiring, but preserve existing service instance - $newServices->importWiring( $services ); - - $this->assertContains( 'Bar', $newServices->getServiceNames(), 'Import all services' ); - $this->assertSame( 'Bar!', $newServices->getService( 'Bar' ) ); - $this->assertSame( 'Car!', $newServices->getService( 'Car' ), 'Use existing service instance' ); - $this->assertSame( 'Xar!', $newServices->getService( 'Xar' ), 'Predefined services are kept' ); - } - - public function testLoadWiringFiles() { - $services = $this->newServiceContainer(); - - $wiringFiles = [ - __DIR__ . '/TestWiring1.php', - __DIR__ . '/TestWiring2.php', - ]; - - $services->loadWiringFiles( $wiringFiles ); - - $this->assertSame( 'Foo!', $services->getService( 'Foo' ) ); - $this->assertSame( 'Bar!', $services->getService( 'Bar' ) ); - } - - public function testLoadWiringFiles_fail_duplicate() { - $services = $this->newServiceContainer(); - - $wiringFiles = [ - __DIR__ . '/TestWiring1.php', - __DIR__ . '/./TestWiring1.php', - ]; - - // loading the same file twice should fail, because - $this->setExpectedException( Wikimedia\Services\ServiceAlreadyDefinedException::class ); - - $services->loadWiringFiles( $wiringFiles ); - } - - public function testRedefineService() { - $services = $this->newServiceContainer( [ 'Foo' ] ); - - $theService1 = new stdClass(); - $name = 'TestService92834576'; - - $services->defineService( $name, function () { - PHPUnit_Framework_Assert::fail( - 'The original instantiator function should not get called' - ); - } ); - - // redefine before instantiation - $services->redefineService( - $name, - function ( $actualLocator, $extra ) use ( $services, $theService1 ) { - PHPUnit_Framework_Assert::assertSame( $services, $actualLocator ); - PHPUnit_Framework_Assert::assertSame( 'Foo', $extra ); - return $theService1; - } - ); - - // force instantiation, check result - $this->assertSame( $theService1, $services->getService( $name ) ); - } - - public function testRedefineService_disabled() { - $services = $this->newServiceContainer( [ 'Foo' ] ); - - $theService1 = new stdClass(); - $name = 'TestService92834576'; - - $services->defineService( $name, function () { - return 'Foo'; - } ); - - // disable the service. we should be able to redefine it anyway. - $services->disableService( $name ); - - $services->redefineService( $name, function () use ( $theService1 ) { - return $theService1; - } ); - - // force instantiation, check result - $this->assertSame( $theService1, $services->getService( $name ) ); - } - - public function testRedefineService_fail_undefined() { - $services = $this->newServiceContainer(); - - $theService = new stdClass(); - $name = 'TestService92834576'; - - $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class ); - - $services->redefineService( $name, function () use ( $theService ) { - return $theService; - } ); - } - - public function testRedefineService_fail_in_use() { - $services = $this->newServiceContainer( [ 'Foo' ] ); - - $theService = new stdClass(); - $name = 'TestService92834576'; - - $services->defineService( $name, function () { - return 'Foo'; - } ); - - // create the service, so it can no longer be redefined - $services->getService( $name ); - - $this->setExpectedException( Wikimedia\Services\CannotReplaceActiveServiceException::class ); - - $services->redefineService( $name, function () use ( $theService ) { - return $theService; - } ); - } - - public function testAddServiceManipulator() { - $services = $this->newServiceContainer( [ 'Foo' ] ); - - $theService1 = new stdClass(); - $theService2 = new stdClass(); - $name = 'TestService92834576'; - - $services->defineService( - $name, - function ( $actualLocator, $extra ) use ( $services, $theService1 ) { - PHPUnit_Framework_Assert::assertSame( $services, $actualLocator ); - PHPUnit_Framework_Assert::assertSame( 'Foo', $extra ); - return $theService1; - } - ); - - $services->addServiceManipulator( - $name, - function ( - $theService, $actualLocator, $extra - ) use ( - $services, $theService1, $theService2 - ) { - PHPUnit_Framework_Assert::assertSame( $theService1, $theService ); - PHPUnit_Framework_Assert::assertSame( $services, $actualLocator ); - PHPUnit_Framework_Assert::assertSame( 'Foo', $extra ); - return $theService2; - } - ); - - // force instantiation, check result - $this->assertSame( $theService2, $services->getService( $name ) ); - } - - public function testAddServiceManipulator_fail_undefined() { - $services = $this->newServiceContainer(); - - $theService = new stdClass(); - $name = 'TestService92834576'; - - $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class ); - - $services->addServiceManipulator( $name, function () use ( $theService ) { - return $theService; - } ); - } - - public function testAddServiceManipulator_fail_in_use() { - $services = $this->newServiceContainer( [ 'Foo' ] ); - - $theService = new stdClass(); - $name = 'TestService92834576'; - - $services->defineService( $name, function () use ( $theService ) { - return $theService; - } ); - - // create the service, so it can no longer be redefined - $services->getService( $name ); - - $this->setExpectedException( Wikimedia\Services\CannotReplaceActiveServiceException::class ); - - $services->addServiceManipulator( $name, function () { - return 'Foo'; - } ); - } - - public function testDisableService() { - $services = $this->newServiceContainer( [ 'Foo' ] ); - - $destructible = $this->getMockBuilder( Wikimedia\Services\DestructibleService::class ) - ->getMock(); - $destructible->expects( $this->once() ) - ->method( 'destroy' ); - - $services->defineService( 'Foo', function () use ( $destructible ) { - return $destructible; - } ); - $services->defineService( 'Bar', function () { - return new stdClass(); - } ); - $services->defineService( 'Qux', function () { - return new stdClass(); - } ); - - // instantiate Foo and Bar services - $services->getService( 'Foo' ); - $services->getService( 'Bar' ); - - // disable service, should call destroy() once. - $services->disableService( 'Foo' ); - - // disabled service should still be listed - $this->assertContains( 'Foo', $services->getServiceNames() ); - - // getting other services should still work - $services->getService( 'Bar' ); - - // disable non-destructible service, and not-yet-instantiated service - $services->disableService( 'Bar' ); - $services->disableService( 'Qux' ); - - $this->assertNull( $services->peekService( 'Bar' ) ); - $this->assertNull( $services->peekService( 'Qux' ) ); - - // disabled service should still be listed - $this->assertContains( 'Bar', $services->getServiceNames() ); - $this->assertContains( 'Qux', $services->getServiceNames() ); - - $this->setExpectedException( Wikimedia\Services\ServiceDisabledException::class ); - $services->getService( 'Qux' ); - } - - public function testDisableService_fail_undefined() { - $services = $this->newServiceContainer(); - - $theService = new stdClass(); - $name = 'TestService92834576'; - - $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class ); - - $services->redefineService( $name, function () use ( $theService ) { - return $theService; - } ); - } - - public function testDestroy() { - $services = $this->newServiceContainer(); - - $destructible = $this->getMockBuilder( Wikimedia\Services\DestructibleService::class ) - ->getMock(); - $destructible->expects( $this->once() ) - ->method( 'destroy' ); - - $services->defineService( 'Foo', function () use ( $destructible ) { - return $destructible; - } ); - - $services->defineService( 'Bar', function () { - return new stdClass(); - } ); - - // create the service - $services->getService( 'Foo' ); - - // destroy the container - $services->destroy(); - - $this->setExpectedException( Wikimedia\Services\ContainerDisabledException::class ); - $services->getService( 'Bar' ); - } - -} diff --git a/tests/phpunit/includes/libs/services/TestWiring1.php b/tests/phpunit/includes/libs/services/TestWiring1.php deleted file mode 100644 index b6ff4eb3b4..0000000000 --- a/tests/phpunit/includes/libs/services/TestWiring1.php +++ /dev/null @@ -1,10 +0,0 @@ - function () { - return 'Foo!'; - }, -]; diff --git a/tests/phpunit/includes/libs/services/TestWiring2.php b/tests/phpunit/includes/libs/services/TestWiring2.php deleted file mode 100644 index dfff64f048..0000000000 --- a/tests/phpunit/includes/libs/services/TestWiring2.php +++ /dev/null @@ -1,10 +0,0 @@ - function () { - return 'Bar!'; - }, -]; diff --git a/tests/phpunit/includes/libs/stats/PrefixingStatsdDataFactoryProxyTest.php b/tests/phpunit/includes/libs/stats/PrefixingStatsdDataFactoryProxyTest.php deleted file mode 100644 index 46e23e36db..0000000000 --- a/tests/phpunit/includes/libs/stats/PrefixingStatsdDataFactoryProxyTest.php +++ /dev/null @@ -1,58 +0,0 @@ -getMock( - \Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface::class - ); - $innerFactory->expects( $this->once() ) - ->method( $method ) - ->with( 'testprefix.metricname' ); - - $proxy = new PrefixingStatsdDataFactoryProxy( $innerFactory, 'testprefix' ); - // 1,2,3,4 simply makes sure we provide enough parameters, without caring what they are - $proxy->$method( 'metricname', 1, 2, 3, 4 ); - } - - /** - * @dataProvider provideMethodNames - */ - public function testPrefixIsTrimmed( $method ) { - /** @var StatsdDataFactoryInterface|PHPUnit_Framework_MockObject_MockObject $innerFactory */ - $innerFactory = $this->getMock( - \Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface::class - ); - $innerFactory->expects( $this->once() ) - ->method( $method ) - ->with( 'testprefix.metricname' ); - - $proxy = new PrefixingStatsdDataFactoryProxy( $innerFactory, 'testprefix...' ); - // 1,2,3,4 simply makes sure we provide enough parameters, without caring what they are - $proxy->$method( 'metricname', 1, 2, 3, 4 ); - } - -} diff --git a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php deleted file mode 100644 index 278b441bbd..0000000000 --- a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php +++ /dev/null @@ -1,110 +0,0 @@ -mediaPath = __DIR__ . '/../../data/media/'; - } - - /** - * Put in a file, and see if the metadata coming out is as expected. - * @param string $filename - * @param array $expected The extracted metadata. - * @dataProvider provideGetMetadata - * @covers GIFMetadataExtractor::getMetadata - */ - public function testGetMetadata( $filename, $expected ) { - $actual = GIFMetadataExtractor::getMetadata( $this->mediaPath . $filename ); - $this->assertEquals( $expected, $actual ); - } - - public static function provideGetMetadata() { - $xmpNugget = << - - - - - The interwebs - - - - Bawolff - - - A file to test GIF - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -EOF; - $xmpNugget = str_replace( "\r", '', $xmpNugget ); // Windows compat - - return [ - [ - 'nonanimated.gif', - [ - 'comment' => [ 'GIF test file ⁕ Created with GIMP' ], - 'duration' => 0.1, - 'frameCount' => 1, - 'looped' => false, - 'xmp' => '', - ] - ], - [ - 'animated.gif', - [ - 'comment' => [ 'GIF test file . Created with GIMP' ], - 'duration' => 2.4, - 'frameCount' => 4, - 'looped' => true, - 'xmp' => '', - ] - ], - - [ - 'animated-xmp.gif', - [ - 'xmp' => $xmpNugget, - 'duration' => 2.4, - 'frameCount' => 4, - 'looped' => true, - 'comment' => [ 'GIƒ·test·file' ], - ] - ], - ]; - } -} diff --git a/tests/phpunit/includes/media/IPTCTest.php b/tests/phpunit/includes/media/IPTCTest.php deleted file mode 100644 index 4b3ba0755c..0000000000 --- a/tests/phpunit/includes/media/IPTCTest.php +++ /dev/null @@ -1,85 +0,0 @@ -assertEquals( 'UTF-8', $res ); - } - - /** - * @covers IPTC::parse - */ - public function testIPTCParseNoCharset88591() { - // basically IPTC for keyword with value of 0xBC which is 1/4 in iso-8859-1 - // This data doesn't specify a charset. We're supposed to guess - // (which basically means utf-8 if valid, windows 1252 (iso 8859-1) if not) - $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x06\x1c\x02\x19\x00\x01\xBC"; - $res = IPTC::parse( $iptcData ); - $this->assertEquals( [ '¼' ], $res['Keywords'] ); - } - - /** - * @covers IPTC::parse - */ - public function testIPTCParseNoCharset88591b() { - /* This one contains a sequence that's valid iso 8859-1 but not valid utf8 */ - /* \xC3 = Ã, \xB8 = ¸ */ - $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x09\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8"; - $res = IPTC::parse( $iptcData ); - $this->assertEquals( [ 'ÃÃø' ], $res['Keywords'] ); - } - - /** - * Same as testIPTCParseNoCharset88591b, but forcing the charset to utf-8. - * What should happen is the first "\xC3\xC3" should be dropped as invalid, - * leaving \xC3\xB8, which is ø - * @covers IPTC::parse - */ - public function testIPTCParseForcedUTFButInvalid() { - $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x11\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8" - . "\x1c\x01\x5A\x00\x03\x1B\x25\x47"; - $res = IPTC::parse( $iptcData ); - $this->assertEquals( [ 'ø' ], $res['Keywords'] ); - } - - /** - * @covers IPTC::parse - */ - public function testIPTCParseNoCharsetUTF8() { - $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x07\x1c\x02\x19\x00\x02¼"; - $res = IPTC::parse( $iptcData ); - $this->assertEquals( [ '¼' ], $res['Keywords'] ); - } - - /** - * Testing something that has 2 values for keyword - * @covers IPTC::parse - */ - public function testIPTCParseMulti() { - $iptcData = /* identifier */ "Photoshop 3.0\08BIM\4\4" - /* length */ . "\0\0\0\0\0\x0D" - . "\x1c\x02\x19" . "\x00\x01" . "\xBC" - . "\x1c\x02\x19" . "\x00\x02" . "\xBC\xBD"; - $res = IPTC::parse( $iptcData ); - $this->assertEquals( [ '¼', '¼½' ], $res['Keywords'] ); - } - - /** - * @covers IPTC::parse - */ - public function testIPTCParseUTF8() { - // This has the magic "\x1c\x01\x5A\x00\x03\x1B\x25\x47" which marks content as UTF8. - $iptcData = - "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x0F\x1c\x02\x19\x00\x02¼\x1c\x01\x5A\x00\x03\x1B\x25\x47"; - $res = IPTC::parse( $iptcData ); - $this->assertEquals( [ '¼' ], $res['Keywords'] ); - } -} diff --git a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php deleted file mode 100644 index c943cef906..0000000000 --- a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php +++ /dev/null @@ -1,128 +0,0 @@ -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/media/MediaHandlerTest.php b/tests/phpunit/includes/media/MediaHandlerTest.php deleted file mode 100644 index 7a052f6035..0000000000 --- a/tests/phpunit/includes/media/MediaHandlerTest.php +++ /dev/null @@ -1,68 +0,0 @@ -assertEquals( $expected, - $result, - "($width, $height, $max) wanted: {$expected}x$y, got: {z$result}x$y2" ); - } - - public static function provideTestFitBoxWidth() { - return array_merge( - static::generateTestFitBoxWidthData( 50, 50, [ - 50 => 50, - 17 => 17, - 18 => 18 ] - ), - static::generateTestFitBoxWidthData( 366, 300, [ - 50 => 61, - 17 => 21, - 18 => 22 ] - ), - static::generateTestFitBoxWidthData( 300, 366, [ - 50 => 41, - 17 => 14, - 18 => 15 ] - ), - static::generateTestFitBoxWidthData( 100, 400, [ - 50 => 12, - 17 => 4, - 18 => 4 ] - ) - ); - } - - /** - * Generate single test cases by combining the dimensions and tests contents - * - * It creates: - * [$width, $height, $max, $expected], - * [$width, $height, $max2, $expected2], ... - * out of parameters: - * $width, $height, { $max => $expected, $max2 => $expected2, ... } - * - * @param int $width - * @param int $height - * @param array $tests associative array of $max => $expected values - * @return array - */ - private static function generateTestFitBoxWidthData( $width, $height, $tests ) { - $result = []; - foreach ( $tests as $max => $expected ) { - $result[] = [ $width, $height, $max, $expected ]; - } - return $result; - } -} diff --git a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php deleted file mode 100644 index 6b94d0ae6c..0000000000 --- a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php +++ /dev/null @@ -1,201 +0,0 @@ -assertMetadata( $infile, $expected ); - } - - /** - * @dataProvider provideSvgFilesWithXMLMetadata - */ - public function testGetXMLMetadata( $infile, $expected ) { - $r = new XMLReader(); - $this->assertMetadata( $infile, $expected ); - } - - /** - * @dataProvider provideSvgUnits - */ - public function testScaleSVGUnit( $inUnit, $expected ) { - $this->assertEquals( - $expected, - SVGReader::scaleSVGUnit( $inUnit ), - 'SVG unit conversion and scaling failure' - ); - } - - function assertMetadata( $infile, $expected ) { - try { - $data = SVGMetadataExtractor::getMetadata( $infile ); - $this->assertEquals( $expected, $data, 'SVG metadata extraction test' ); - } catch ( MWException $e ) { - if ( $expected === false ) { - $this->assertTrue( true, 'SVG metadata extracted test (expected failure)' ); - } else { - throw $e; - } - } - } - - public static function provideSvgFiles() { - $base = __DIR__ . '/../../data/media'; - - return [ - [ - "$base/Wikimedia-logo.svg", - [ - 'width' => 1024, - 'height' => 1024, - 'originalWidth' => '1024', - 'originalHeight' => '1024', - 'translations' => [], - ] - ], - [ - "$base/QA_icon.svg", - [ - 'width' => 60, - 'height' => 60, - 'originalWidth' => '60', - 'originalHeight' => '60', - 'translations' => [], - ] - ], - [ - "$base/Gtk-media-play-ltr.svg", - [ - 'width' => 60, - 'height' => 60, - 'originalWidth' => '60.0000000', - 'originalHeight' => '60.0000000', - 'translations' => [], - ] - ], - [ - "$base/Toll_Texas_1.svg", - // This file triggered T33719, needs entity expansion in the xmlns checks - [ - 'width' => 385, - 'height' => 385, - 'originalWidth' => '385', - 'originalHeight' => '385.0004883', - 'translations' => [], - ] - ], - [ - "$base/Tux.svg", - [ - 'width' => 512, - 'height' => 594, - 'originalWidth' => '100%', - 'originalHeight' => '100%', - 'title' => 'Tux', - 'translations' => [], - 'description' => 'For more information see: http://commons.wikimedia.org/wiki/Image:Tux.svg', - ] - ], - [ - "$base/Speech_bubbles.svg", - [ - 'width' => 627, - 'height' => 461, - 'originalWidth' => '17.7cm', - 'originalHeight' => '13cm', - 'translations' => [ - 'de' => SVGReader::LANG_FULL_MATCH, - 'fr' => SVGReader::LANG_FULL_MATCH, - 'nl' => SVGReader::LANG_FULL_MATCH, - 'tlh-ca' => SVGReader::LANG_FULL_MATCH, - 'tlh' => SVGReader::LANG_PREFIX_MATCH - ], - ] - ], - [ - "$base/Soccer_ball_animated.svg", - [ - 'width' => 150, - 'height' => 150, - 'originalWidth' => '150', - 'originalHeight' => '150', - 'animated' => true, - 'translations' => [] - ], - ], - [ - "$base/comma_separated_viewbox.svg", - [ - 'width' => 512, - 'height' => 594, - 'originalWidth' => '100%', - 'originalHeight' => '100%', - 'translations' => [] - ], - ], - ]; - } - - public static function provideSvgFilesWithXMLMetadata() { - $base = __DIR__ . '/../../data/media'; - // phpcs:disable Generic.Files.LineLength - $metadata = ' - - image/svg+xml - - - '; - // phpcs:enable - - $metadata = str_replace( "\r", '', $metadata ); // Windows compat - return [ - [ - "$base/US_states_by_total_state_tax_revenue.svg", - [ - 'height' => 593, - 'metadata' => $metadata, - 'width' => 959, - 'originalWidth' => '958.69', - 'originalHeight' => '592.78998', - 'translations' => [], - ] - ], - ]; - } - - public static function provideSvgUnits() { - return [ - [ '1' , 1 ], - [ '1.1' , 1.1 ], - [ '0.1' , 0.1 ], - [ '.1' , 0.1 ], - [ '1e2' , 100 ], - [ '1E2' , 100 ], - [ '+1' , 1 ], - [ '-1' , -1 ], - [ '-1.1' , -1.1 ], - [ '1e+2' , 100 ], - [ '1e-2' , 0.01 ], - [ '10px' , 10 ], - [ '10pt' , 10 * 1.25 ], - [ '10pc' , 10 * 15 ], - [ '10mm' , 10 * 3.543307 ], - [ '10cm' , 10 * 35.43307 ], - [ '10in' , 10 * 90 ], - [ '10em' , 10 * 16 ], - [ '10ex' , 10 * 12 ], - [ '10%' , 51.2 ], - [ '10 px' , 10 ], - // Invalid values - [ '1e1.1', 10 ], - [ '10bp', 10 ], - [ 'p10', null ], - ]; - } -} diff --git a/tests/phpunit/includes/media/WebPHandlerTest.php b/tests/phpunit/includes/media/WebPHandlerTest.php deleted file mode 100644 index ac0ad98edd..0000000000 --- a/tests/phpunit/includes/media/WebPHandlerTest.php +++ /dev/null @@ -1,151 +0,0 @@ -tempFileName = tempnam( wfTempDir(), 'WEBP' ); - } - - public function tearDown() { - parent::tearDown(); - unlink( $this->tempFileName ); - } - - /** - * @dataProvider provideTestExtractMetaData - */ - public function testExtractMetaData( $header, $expectedResult ) { - // Put header into file - file_put_contents( $this->tempFileName, $header ); - - $this->assertEquals( $expectedResult, WebPHandler::extractMetadata( $this->tempFileName ) ); - } - - public function provideTestExtractMetaData() { - // phpcs:disable Generic.Files.LineLength - return [ - // Files from https://developers.google.com/speed/webp/gallery2 - [ "\x52\x49\x46\x46\x90\x68\x01\x00\x57\x45\x42\x50\x56\x50\x38\x4C\x83\x68\x01\x00\x2F\x8F\x01\x4B\x10\x8D\x38\x6C\xDB\x46\x92\xE0\xE0\x82\x7B\x6C", - [ 'compression' => 'lossless', 'width' => 400, 'height' => 301 ] ], - [ "\x52\x49\x46\x46\x64\x5B\x00\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x8F\x01\x00\x2C\x01\x00\x41\x4C\x50\x48\xE5\x0E", - [ 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 400, 'height' => 301 ] ], - [ "\x52\x49\x46\x46\xA8\x72\x00\x00\x57\x45\x42\x50\x56\x50\x38\x4C\x9B\x72\x00\x00\x2F\x81\x81\x62\x10\x8D\x40\x8C\x24\x39\x6E\x73\x73\x38\x01\x96", - [ 'compression' => 'lossless', 'width' => 386, 'height' => 395 ] ], - [ "\x52\x49\x46\x46\xE0\x42\x00\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x81\x01\x00\x8A\x01\x00\x41\x4C\x50\x48\x56\x10", - [ 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 386, 'height' => 395 ] ], - [ "\x52\x49\x46\x46\x70\x61\x02\x00\x57\x45\x42\x50\x56\x50\x38\x4C\x63\x61\x02\x00\x2F\x1F\xC3\x95\x10\x8D\xC8\x72\xDB\xC8\x92\x24\xD8\x91\xD9\x91", - [ 'compression' => 'lossless', 'width' => 800, 'height' => 600 ] ], - [ "\x52\x49\x46\x46\x1C\x1D\x01\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x1F\x03\x00\x57\x02\x00\x41\x4C\x50\x48\x25\x8B", - [ 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 800, 'height' => 600 ] ], - [ "\x52\x49\x46\x46\xFA\xC5\x00\x00\x57\x45\x42\x50\x56\x50\x38\x4C\xEE\xC5\x00\x00\x2F\xA4\x81\x28\x10\x8D\x40\x68\x24\xC9\x91\xA4\xAE\xF3\x97\x75", - [ 'compression' => 'lossless', 'width' => 421, 'height' => 163 ] ], - [ "\x52\x49\x46\x46\xF6\x5D\x00\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\xA4\x01\x00\xA2\x00\x00\x41\x4C\x50\x48\x38\x1A", - [ 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 421, 'height' => 163 ] ], - [ "\x52\x49\x46\x46\xC4\x96\x01\x00\x57\x45\x42\x50\x56\x50\x38\x4C\xB8\x96\x01\x00\x2F\x2B\xC1\x4A\x10\x11\x87\x6D\xDB\x48\x12\xFC\x60\xB0\x83\x24", - [ 'compression' => 'lossless', 'width' => 300, 'height' => 300 ] ], - [ "\x52\x49\x46\x46\x0A\x11\x01\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x2B\x01\x00\x2B\x01\x00\x41\x4C\x50\x48\x67\x6E", - [ 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 300, 'height' => 300 ] ], - - // Lossy files from https://developers.google.com/speed/webp/gallery1 - [ "\x52\x49\x46\x46\x68\x76\x00\x00\x57\x45\x42\x50\x56\x50\x38\x20\x5C\x76\x00\x00\xD2\xBE\x01\x9D\x01\x2A\x26\x02\x70\x01\x3E\xD5\x4E\x97\x43\xA2", - [ 'compression' => 'lossy', 'width' => 550, 'height' => 368 ] ], - [ "\x52\x49\x46\x46\xB0\xEC\x00\x00\x57\x45\x42\x50\x56\x50\x38\x20\xA4\xEC\x00\x00\xB2\x4B\x02\x9D\x01\x2A\x26\x02\x94\x01\x3E\xD1\x50\x96\x46\x26", - [ 'compression' => 'lossy', 'width' => 550, 'height' => 404 ] ], - [ "\x52\x49\x46\x46\x7A\x19\x03\x00\x57\x45\x42\x50\x56\x50\x38\x20\x6E\x19\x03\x00\xB2\xF8\x09\x9D\x01\x2A\x00\x05\xD0\x02\x3E\xAD\x46\x99\x4A\xA5", - [ 'compression' => 'lossy', 'width' => 1280, 'height' => 720 ] ], - [ "\x52\x49\x46\x46\x44\xB3\x02\x00\x57\x45\x42\x50\x56\x50\x38\x20\x38\xB3\x02\x00\x52\x57\x06\x9D\x01\x2A\x00\x04\x04\x03\x3E\xA5\x44\x96\x49\x26", - [ 'compression' => 'lossy', 'width' => 1024, 'height' => 772 ] ], - [ "\x52\x49\x46\x46\x02\x43\x01\x00\x57\x45\x42\x50\x56\x50\x38\x20\xF6\x42\x01\x00\x12\xC0\x05\x9D\x01\x2A\x00\x04\xF0\x02\x3E\x79\x34\x93\x47\xA4", - [ 'compression' => 'lossy', 'width' => 1024, 'height' => 752 ] ], - - // Animated file from https://groups.google.com/a/chromium.org/d/topic/blink-dev/Y8tRC4mdQz8/discussion - [ "\x52\x49\x46\x46\xD0\x0B\x02\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x12\x00\x00\x00\x3F\x01\x00\x3F\x01\x00\x41\x4E", - [ 'compression' => 'unknown', 'animated' => true, 'transparency' => true, 'width' => 320, 'height' => 320 ] ], - - // Error cases - [ '', false ], - [ ' ', false ], - [ 'RIFF ', false ], - [ 'RIFF1234WEBP ', false ], - [ 'RIFF1234WEBPVP8 ', false ], - [ 'RIFF1234WEBPVP8L ', false ], - ]; - // phpcs:enable - } - - /** - * @dataProvider provideTestWithFileExtractMetaData - */ - public function testWithFileExtractMetaData( $filename, $expectedResult ) { - $this->assertEquals( $expectedResult, WebPHandler::extractMetadata( $filename ) ); - } - - public function provideTestWithFileExtractMetaData() { - return [ - [ __DIR__ . '/../../data/media/2_webp_ll.webp', - [ - 'compression' => 'lossless', - 'width' => 386, - 'height' => 395 - ] - ], - [ __DIR__ . '/../../data/media/2_webp_a.webp', - [ - 'compression' => 'lossy', - 'animated' => false, - 'transparency' => true, - 'width' => 386, - 'height' => 395 - ] - ], - ]; - } - - /** - * @dataProvider provideTestGetImageSize - */ - public function testGetImageSize( $path, $expectedResult ) { - $handler = new WebPHandler(); - $this->assertEquals( $expectedResult, $handler->getImageSize( null, $path ) ); - } - - public function provideTestGetImageSize() { - return [ - // Public domain files from https://developers.google.com/speed/webp/gallery2 - [ __DIR__ . '/../../data/media/2_webp_a.webp', [ 386, 395 ] ], - [ __DIR__ . '/../../data/media/2_webp_ll.webp', [ 386, 395 ] ], - [ __DIR__ . '/../../data/media/webp_animated.webp', [ 300, 225 ] ], - - // Error cases - [ __FILE__, false ], - ]; - } - - /** - * Tests the WebP MIME detection. This should really be a separate test, but sticking it - * here for now. - * - * @dataProvider provideTestGetMimeType - */ - public function testGuessMimeType( $path ) { - $mime = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer(); - $this->assertEquals( 'image/webp', $mime->guessMimeType( $path, false ) ); - } - - public function provideTestGetMimeType() { - return [ - // Public domain files from https://developers.google.com/speed/webp/gallery2 - [ __DIR__ . '/../../data/media/2_webp_a.webp' ], - [ __DIR__ . '/../../data/media/2_webp_ll.webp' ], - [ __DIR__ . '/../../data/media/webp_animated.webp' ], - ]; - } -} - -/* Python code to extract a header and convert to PHP format: - * print '"%s"' % ''.implode( '\\x%02X' % ord(c) for c in urllib.urlopen(url).read(36) ) - */ diff --git a/tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php b/tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php deleted file mode 100644 index 45971daced..0000000000 --- a/tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php +++ /dev/null @@ -1,107 +0,0 @@ -cache = new MemcachedPhpBagOStuff( [ 'keyspace' => 'test', 'servers' => [] ] ); - } - - /** - * @covers MemcachedBagOStuff::makeKey - */ - public function testKeyNormalization() { - $this->assertEquals( - 'test:vanilla', - $this->cache->makeKey( 'vanilla' ) - ); - - $this->assertEquals( - 'test:punctuation_marks_are_ok:!@$^&*()', - $this->cache->makeKey( 'punctuation_marks_are_ok', '!@$^&*()' ) - ); - - $this->assertEquals( - 'test:but_spaces:hashes%23:and%0Anewlines:are_not', - $this->cache->makeKey( 'but spaces', 'hashes#', "and\nnewlines", 'are_not' ) - ); - - $this->assertEquals( - 'test:this:key:contains:%F0%9D%95%9E%F0%9D%95%A6%F0%9D%95%9D%F0%9D%95%A5%F0%9' . - 'D%95%9A%F0%9D%95%93%F0%9D%95%AA%F0%9D%95%A5%F0%9D%95%96:characters', - $this->cache->makeKey( 'this', 'key', 'contains', '𝕞𝕦𝕝𝕥𝕚𝕓𝕪𝕥𝕖', 'characters' ) - ); - - $this->assertEquals( - 'test:this:key:contains:#c118f92685a635cb843039de50014c9c', - $this->cache->makeKey( 'this', 'key', 'contains', '𝕥𝕠𝕠 𝕞𝕒𝕟𝕪 𝕞𝕦𝕝𝕥𝕚𝕓𝕪𝕥𝕖 𝕔𝕙𝕒𝕣𝕒𝕔𝕥𝕖𝕣𝕤' ) - ); - - $this->assertEquals( - 'test:BagOStuff-long-key:##dc89dcb43b28614da27660240af478b5', - $this->cache->makeKey( '𝕖𝕧𝕖𝕟', '𝕚𝕗', '𝕨𝕖', '𝕄𝔻𝟝', '𝕖𝕒𝕔𝕙', - '𝕒𝕣𝕘𝕦𝕞𝕖𝕟𝕥', '𝕥𝕙𝕚𝕤', '𝕜𝕖𝕪', '𝕨𝕠𝕦𝕝𝕕', '𝕤𝕥𝕚𝕝𝕝', '𝕓𝕖', '𝕥𝕠𝕠', '𝕝𝕠𝕟𝕘' ) - ); - - $this->assertEquals( - 'test:%23%235820ad1d105aa4dc698585c39df73e19', - $this->cache->makeKey( '##5820ad1d105aa4dc698585c39df73e19' ) - ); - - $this->assertEquals( - 'test:percent_is_escaped:!@$%25^&*()', - $this->cache->makeKey( 'percent_is_escaped', '!@$%^&*()' ) - ); - - $this->assertEquals( - 'test:colon_is_escaped:!@$%3A^&*()', - $this->cache->makeKey( 'colon_is_escaped', '!@$:^&*()' ) - ); - - $this->assertEquals( - 'test:long_key_part_hashed:#0244f7b1811d982dd932dd7de01465ac', - $this->cache->makeKey( 'long_key_part_hashed', str_repeat( 'y', 500 ) ) - ); - } - - /** - * @dataProvider validKeyProvider - * @covers MemcachedBagOStuff::validateKeyEncoding - */ - public function testValidateKeyEncoding( $key ) { - $this->assertSame( $key, $this->cache->validateKeyEncoding( $key ) ); - } - - public function validKeyProvider() { - return [ - 'empty' => [ '' ], - 'digits' => [ '09' ], - 'letters' => [ 'AZaz' ], - 'ASCII special characters' => [ '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' ], - ]; - } - - /** - * @dataProvider invalidKeyProvider - * @covers MemcachedBagOStuff::validateKeyEncoding - */ - public function testValidateKeyEncodingThrowsException( $key ) { - $this->setExpectedException( Exception::class ); - $this->cache->validateKeyEncoding( $key ); - } - - public function invalidKeyProvider() { - return [ - [ "\x00" ], - [ ' ' ], - [ "\x1F" ], - [ "\x7F" ], - [ "\x80" ], - [ "\xFF" ], - ]; - } -} diff --git a/tests/phpunit/includes/objectcache/RESTBagOStuffTest.php b/tests/phpunit/includes/objectcache/RESTBagOStuffTest.php deleted file mode 100644 index dfbca706d6..0000000000 --- a/tests/phpunit/includes/objectcache/RESTBagOStuffTest.php +++ /dev/null @@ -1,96 +0,0 @@ -client = - $this->getMockBuilder( MultiHttpClient::class ) - ->setConstructorArgs( [ [] ] ) - ->setMethods( [ 'run' ] ) - ->getMock(); - $this->bag = new RESTBagOStuff( [ 'client' => $this->client, 'url' => 'http://test/rest/' ] ); - } - - public function testGet() { - $this->client->expects( $this->once() )->method( 'run' )->with( [ - 'method' => 'GET', - 'url' => 'http://test/rest/42xyz42', - 'headers' => [] - // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) - ] )->willReturn( [ 200, 'OK', [], '"somedata"', 0 ] ); - $result = $this->bag->get( '42xyz42' ); - $this->assertEquals( 'somedata', $result ); - } - - public function testGetNotExist() { - $this->client->expects( $this->once() )->method( 'run' )->with( [ - 'method' => 'GET', - 'url' => 'http://test/rest/42xyz42', - 'headers' => [] - // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) - ] )->willReturn( [ 404, 'Not found', [], 'Nothing to see here', 0 ] ); - $result = $this->bag->get( '42xyz42' ); - $this->assertFalse( $result ); - } - - public function testGetBadClient() { - $this->client->expects( $this->once() )->method( 'run' )->with( [ - 'method' => 'GET', - 'url' => 'http://test/rest/42xyz42', - 'headers' => [] - // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) - ] )->willReturn( [ 0, '', [], '', 'cURL has failed you today' ] ); - $result = $this->bag->get( '42xyz42' ); - $this->assertFalse( $result ); - $this->assertEquals( BagOStuff::ERR_UNREACHABLE, $this->bag->getLastError() ); - } - - public function testGetBadServer() { - $this->client->expects( $this->once() )->method( 'run' )->with( [ - 'method' => 'GET', - 'url' => 'http://test/rest/42xyz42', - 'headers' => [] - // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) - ] )->willReturn( [ 500, 'Too busy', [], 'Server is too busy', '' ] ); - $result = $this->bag->get( '42xyz42' ); - $this->assertFalse( $result ); - $this->assertEquals( BagOStuff::ERR_UNEXPECTED, $this->bag->getLastError() ); - } - - public function testPut() { - $this->client->expects( $this->once() )->method( 'run' )->with( [ - 'method' => 'PUT', - 'url' => 'http://test/rest/42xyz42', - 'body' => '"postdata"', - 'headers' => [] - // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) - ] )->willReturn( [ 200, 'OK', [], 'Done', 0 ] ); - $result = $this->bag->set( '42xyz42', 'postdata' ); - $this->assertTrue( $result ); - } - - public function testDelete() { - $this->client->expects( $this->once() )->method( 'run' )->with( [ - 'method' => 'DELETE', - 'url' => 'http://test/rest/42xyz42', - 'headers' => [] - // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) - ] )->willReturn( [ 200, 'OK', [], 'Done', 0 ] ); - $result = $this->bag->delete( '42xyz42' ); - $this->assertTrue( $result ); - } -} diff --git a/tests/phpunit/includes/objectcache/RedisBagOStuffTest.php b/tests/phpunit/includes/objectcache/RedisBagOStuffTest.php deleted file mode 100644 index df5614d8c1..0000000000 --- a/tests/phpunit/includes/objectcache/RedisBagOStuffTest.php +++ /dev/null @@ -1,110 +0,0 @@ -getMockBuilder( RedisBagOStuff::class ) - ->disableOriginalConstructor() - ->getMock(); - $this->cache = TestingAccessWrapper::newFromObject( $cache ); - } - - /** - * @covers RedisBagOStuff::unserialize - * @dataProvider unserializeProvider - */ - public function testUnserialize( $expected, $input, $message ) { - $actual = $this->cache->unserialize( $input ); - $this->assertSame( $expected, $actual, $message ); - } - - public function unserializeProvider() { - return [ - [ - -1, - '-1', - 'String representation of \'-1\'', - ], - [ - 0, - '0', - 'String representation of \'0\'', - ], - [ - 1, - '1', - 'String representation of \'1\'', - ], - [ - -1.0, - 'd:-1;', - 'Serialized negative double', - ], - [ - 'foo', - 's:3:"foo";', - 'Serialized string', - ] - ]; - } - - /** - * @covers RedisBagOStuff::serialize - * @dataProvider serializeProvider - */ - public function testSerialize( $expected, $input, $message ) { - $actual = $this->cache->serialize( $input ); - $this->assertSame( $expected, $actual, $message ); - } - - public function serializeProvider() { - return [ - [ - -1, - -1, - '-1 as integer', - ], - [ - 0, - 0, - '0 as integer', - ], - [ - 1, - 1, - '1 as integer', - ], - [ - 'd:-1;', - -1.0, - 'Negative double', - ], - [ - 's:3:"2.1";', - '2.1', - 'Decimal string', - ], - [ - 's:1:"1";', - '1', - 'String representation of 1', - ], - [ - 's:3:"foo";', - 'foo', - 'String', - ], - ]; - } -} diff --git a/tests/phpunit/includes/page/ArticleTest.php b/tests/phpunit/includes/page/ArticleTest.php deleted file mode 100644 index df4a281701..0000000000 --- a/tests/phpunit/includes/page/ArticleTest.php +++ /dev/null @@ -1,57 +0,0 @@ -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/parser/ParserPreloadTest.php b/tests/phpunit/includes/parser/ParserPreloadTest.php deleted file mode 100644 index 560b921a8d..0000000000 --- a/tests/phpunit/includes/parser/ParserPreloadTest.php +++ /dev/null @@ -1,97 +0,0 @@ -testParserOptions = ParserOptions::newFromUserAndLang( new User, - MediaWikiServices::getInstance()->getContentLanguage() ); - - $this->testParser = new Parser(); - $this->testParser->Options( $this->testParserOptions ); - $this->testParser->clearState(); - - $this->title = Title::newFromText( 'Preload Test' ); - } - - protected function tearDown() { - parent::tearDown(); - - unset( $this->testParser ); - unset( $this->title ); - } - - public function testPreloadSimpleText() { - $this->assertPreloaded( 'simple', 'simple' ); - } - - public function testPreloadedPreIsUnstripped() { - $this->assertPreloaded( - '
monospaced
', - '
monospaced
', - '
 in preloaded text must be unstripped (T29467)'
-		);
-	}
-
-	public function testPreloadedNowikiIsUnstripped() {
-		$this->assertPreloaded(
-			'[[Dummy title]]',
-			'[[Dummy title]]',
-			' in preloaded text must be unstripped (T29467)'
-		);
-	}
-
-	protected function assertPreloaded( $expected, $text, $msg = '' ) {
-		$this->assertEquals(
-			$expected,
-			$this->testParser->getPreloadText(
-				$text,
-				$this->title,
-				$this->testParserOptions
-			),
-			$msg
-		);
-	}
-}
diff --git a/tests/phpunit/includes/parser/PreprocessorTest.php b/tests/phpunit/includes/parser/PreprocessorTest.php
deleted file mode 100644
index 6b3e05da51..0000000000
--- a/tests/phpunit/includes/parser/PreprocessorTest.php
+++ /dev/null
@@ -1,296 +0,0 @@
-mOptions = ParserOptions::newFromUserAndLang( new User,
-			MediaWikiServices::getInstance()->getContentLanguage() );
-
-		$this->mPreprocessors = [];
-		foreach ( self::$classNames as $className ) {
-			$this->mPreprocessors[$className] = new $className( $this );
-		}
-	}
-
-	function getStripList() {
-		return [ 'gallery', 'display map' /* Used by Maps, see r80025 CR */, '/foo' ];
-	}
-
-	protected static function addClassArg( $testCases ) {
-		$newTestCases = [];
-		foreach ( self::$classNames as $className ) {
-			foreach ( $testCases as $testCase ) {
-				array_unshift( $testCase, $className );
-				$newTestCases[] = $testCase;
-			}
-		}
-		return $newTestCases;
-	}
-
-	public static function provideCases() {
-		// phpcs:disable Generic.Files.LineLength
-		return self::addClassArg( [
-			[ "Foo", "Foo" ],
-			[ "", "<!-- Foo -->" ],
-			[ "", "<!-- Foo --><!-- Bar -->" ],
-			[ "  ", "<!-- Foo -->  <!-- Bar -->" ],
-			[ " \n ", "<!-- Foo --> \n <!-- Bar -->" ],
-			[ " \n \n", "<!-- Foo --> \n <!-- Bar -->\n" ],
-			[ "  \n", "<!-- Foo -->  <!-- Bar -->\n" ],
-			[ "Bar", "<!-->Bar" ],
-			[ "\n== Baz ==\n", "== Foo ==\n  <!-- Bar -->\n== Baz ==\n" ],
-			[ "", "gallery" ],
-			[ "Foo  Bar", "Foo gallery Bar" ],
-			[ "", "gallery</gallery>" ],
-			[ " ", "<foo> gallery</gallery>" ],
-			[ " ", "<foo> gallery<gallery></gallery>" ],
-			[ " Foo bar ", "<noinclude> Foo bar </noinclude>" ],
-			[ "\n{{Foo}}\n", "<noinclude>\n\n</noinclude>" ],
-			[ "\n{{Foo}}\n\n", "<noinclude>\n\n</noinclude>\n" ],
-			[ "foo bar", "<gallery>foo bar" ],
-			[ "<{{foo}}>", "<>" ],
-			[ "<{{{foo}}}>", "<foo>" ],
-			[ "", "gallery</gallery</gallery>" ],
-			[ "=== Foo === ", "=== Foo === " ],
-			[ "=== Foo === ", "==<!-- -->= Foo === " ],
-			[ "=== Foo === ", "=== Foo ==<!-- -->= " ],
-			[ "=== Foo ===\n", "=== Foo ===<!-- -->\n" ],
-			[ "=== Foo === \n", "=== Foo ===<!-- --> <!-- -->\n" ],
-			[ "== Foo ==\n== Bar == \n", "== Foo ==\n== Bar == \n" ],
-			[ "===========", "===========" ],
-			[ "Foo\n=\n==\n=\n", "Foo\n=\n==\n=\n" ],
-			[ "{{Foo}}", "" ],
-			[ "\n{{Foo}}", "\n" ],
-			[ "{{Foo|bar}}", "" ],
-			[ "{{Foo|bar}}a", "a" ],
-			[ "{{Foo|bar|baz}}", "" ],
-			[ "{{Foo|1=bar}}", "" ],
-			[ "{{Foo|=bar}}", "" ],
-			[ "{{Foo|bar=baz}}", "" ],
-			[ "{{Foo|{{bar}}=baz}}", "" ],
-			[ "{{Foo|1=bar|baz}}", "" ],
-			[ "{{Foo|1=bar|2=baz}}", "" ],
-			[ "{{Foo|bar|foo=baz}}", "" ],
-			[ "{{{1}}}", "1" ],
-			[ "{{{1|}}}", "1" ],
-			[ "{{{Foo}}}", "Foo" ],
-			[ "{{{Foo|}}}", "Foo" ],
-			[ "{{{Foo|bar|baz}}}", "Foobarbaz" ],
-			[ "{{Foo}}", "{<!-- -->{Foo}}" ],
-			[ "{{{{Foobar}}}}", "{Foobar}" ],
-			[ "{{{ {{Foo}} }}}", " <template><title>Foo " ],
-			[ "{{ {{{Foo}}} }}", "" ],
-			[ "{{{{{Foo}}}}}", "" ],
-			[ "{{{{{Foo}} }}}", "<template><title>Foo " ],
-			[ "{{{{{{Foo}}}}}}", "<tplarg><title>Foo" ],
-			[ "{{{{{{Foo}}}}}", "{" ],
-			[ "[[[Foo]]", "[[[Foo]]" ],
-			[ "{{Foo|[[[[bar]]|baz]]}}", "" ], // This test is important, since it means the difference between having the [[ rule stacked or not
-			[ "{{Foo|[[[[bar]|baz]]}}", "{{Foo|[[[[bar]|baz]]}}" ],
-			[ "{{Foo|Foo [[[[bar]|baz]]}}", "{{Foo|Foo [[[[bar]|baz]]}}" ],
-			[ "Foo BarBaz", "Foo display mapBar</display map             >Baz" ],
-			[ "Foo BarBaz", "Foo display map fooBar</display map             >Baz" ],
-			[ "Foo ", "Foo gallery bar="baz" " ],
-			[ "Foo ", "Foo gallery bar="1" baz=2 " ],
-			[ "Foo", "/fooFoo<//foo>" ], # Worth blacklisting IMHO
-			[ "{{#ifexpr: ({{{1|1}}} = 2) | Foo | Bar }}", "" ],
-			[ "{{#if: {{{1|}}} | Foo | {{Bar}} }}", "" ],
-			[ "{{#if: {{{1|}}} | Foo | [[Bar]] }}", "" ],
-			[ "{{#if: {{{1|}}} | [[Foo]] | Bar }}", "" ],
-			[ "{{#if: {{{1|}}} | 1 | {{#if: {{{1|}}} | 2 | 3 }} }}", "" ],
-			[ "{{ {{Foo}}", "{{ " ],
-			[ "{{Foobar {{Foo}} {{Bar}} {{Baz}} ", "{{Foobar    " ],
-			[ "[[Foo]] |", "[[Foo]] |" ],
-			[ "{{Foo|Bar|", "{{Foo|Bar|" ],
-			[ "[[Foo]", "[[Foo]" ],
-			[ "[[Foo|Bar]", "[[Foo|Bar]" ],
-			[ "{{Foo| [[Bar] }}", "{{Foo| [[Bar] }}" ],
-			[ "{{Foo| [[Bar|Baz] }}", "{{Foo| [[Bar|Baz] }}" ],
-			[ "{{Foo|bar=[[baz]}}", "{{Foo|bar=[[baz]}}" ],
-			[ "{{foo|", "{{foo|" ],
-			[ "{{foo|}", "{{foo|}" ],
-			[ "{{foo|} }}", "" ],
-			[ "{{foo|bar=|}", "{{foo|bar=|}" ],
-			[ "{{Foo|} Bar=", "{{Foo|} Bar=" ],
-			[ "{{Foo|} Bar=}}", "" ],
-			/* [ file_get_contents( __DIR__ . '/QuoteQuran.txt' ], file_get_contents( __DIR__ . '/QuoteQuranExpanded.txt' ) ], */
-		] );
-		// phpcs:enable
-	}
-
-	/**
-	 * Get XML preprocessor tree from the preprocessor (which may not be the
-	 * native XML-based one).
-	 *
-	 * @param string $className
-	 * @param string $wikiText
-	 * @return string
-	 */
-	protected function preprocessToXml( $className, $wikiText ) {
-		$preprocessor = $this->mPreprocessors[$className];
-		if ( method_exists( $preprocessor, 'preprocessToXml' ) ) {
-			return $this->normalizeXml( $preprocessor->preprocessToXml( $wikiText ) );
-		}
-
-		$dom = $preprocessor->preprocessToObj( $wikiText );
-		if ( is_callable( [ $dom, 'saveXML' ] ) ) {
-			return $dom->saveXML();
-		} else {
-			return $this->normalizeXml( $dom->__toString() );
-		}
-	}
-
-	/**
-	 * Normalize XML string to the form that a DOMDocument saves out.
-	 *
-	 * @param string $xml
-	 * @return string
-	 */
-	protected function normalizeXml( $xml ) {
-		// Normalize self-closing tags
-		$xml = preg_replace( '!<([a-z]+)/>!', '<$1>', str_replace( ' />', '/>', $xml ) );
-		// Remove  tags, which only occur in Preprocessor_Hash and
-		// have no semantic value
-		$xml = preg_replace( '!!', '', $xml );
-		return $xml;
-	}
-
-	/**
-	 * @dataProvider provideCases
-	 */
-	public function testPreprocessorOutput( $className, $wikiText, $expectedXml ) {
-		$this->assertEquals( $this->normalizeXml( $expectedXml ),
-			$this->preprocessToXml( $className, $wikiText ) );
-	}
-
-	/**
-	 * These are more complex test cases taken out of wiki articles.
-	 */
-	public static function provideFiles() {
-		// phpcs:disable Generic.Files.LineLength
-		return self::addClassArg( [
-			[ "QuoteQuran" ], # https://en.wikipedia.org/w/index.php?title=Template:QuoteQuran/sandbox&oldid=237348988 GFDL + CC BY-SA by Striver
-			[ "Factorial" ], # https://en.wikipedia.org/w/index.php?title=Template:Factorial&oldid=98548758 GFDL + CC BY-SA by Polonium
-			[ "All_system_messages" ], # https://tl.wiktionary.org/w/index.php?title=Suleras:All_system_messages&oldid=2765 GPL text generated by MediaWiki
-			[ "Fundraising" ], # https://tl.wiktionary.org/w/index.php?title=MediaWiki:Sitenotice&oldid=5716 GFDL + CC BY-SA, copied there by Sky Harbor.
-			[ "NestedTemplates" ], # T29936
-		] );
-		// phpcs:enable
-	}
-
-	/**
-	 * @dataProvider provideFiles
-	 */
-	public function testPreprocessorOutputFiles( $className, $filename ) {
-		$folder = __DIR__ . "/../../../parser/preprocess";
-		$wikiText = file_get_contents( "$folder/$filename.txt" );
-		$output = $this->preprocessToXml( $className, $wikiText );
-
-		$expectedFilename = "$folder/$filename.expected";
-		if ( file_exists( $expectedFilename ) ) {
-			$expectedXml = $this->normalizeXml( file_get_contents( $expectedFilename ) );
-			$this->assertEquals( $expectedXml, $output );
-		} else {
-			$tempFilename = tempnam( $folder, "$filename." );
-			file_put_contents( $tempFilename, $output );
-			$this->markTestIncomplete( "File $expectedFilename missing. Output stored as $tempFilename" );
-		}
-	}
-
-	/**
-	 * Tests from T30642 · https://phabricator.wikimedia.org/T30642
-	 */
-	public static function provideHeadings() {
-		// phpcs:disable Generic.Files.LineLength
-		return self::addClassArg( [
-			/* These should become headings: */
-			[ "== h ==", "== h ==<!--c1-->" ],
-			[ "== h == 	", "== h == 	<!--c1-->" ],
-			[ "== h == 	", "== h ==<!--c1--> 	" ],
-			[ "== h == 	 	", "== h == 	<!--c1--> 	" ],
-			[ "== h ==", "== h ==<!--c1--><!--c2-->" ],
-			[ "== h == 	", "== h == 	<!--c1--><!--c2-->" ],
-			[ "== h == 	", "== h ==<!--c1--><!--c2--> 	" ],
-			[ "== h == 	 	", "== h == 	<!--c1--><!--c2--> 	" ],
-			[ "== h == 	  ", "== h == 	<!--c1-->  <!--c2-->" ],
-			[ "== h ==   	", "== h ==<!--c1-->  <!--c2--> 	" ],
-			[ "== h == 	   	", "== h == 	<!--c1-->  <!--c2--> 	" ],
-			[ "== h ==", "== h ==<!--c1--><!--c2--><!--c3-->" ],
-			[ "== h ==  ", "== h ==<!--c1-->  <!--c2--><!--c3-->" ],
-			[ "== h ==  ", "== h ==<!--c1--><!--c2-->  <!--c3-->" ],
-			[ "== h ==    ", "== h ==<!--c1-->  <!--c2-->  <!--c3-->" ],
-			[ "== h ==  ", "== h ==  <!--c1--><!--c2--><!--c3-->" ],
-			[ "== h ==    ", "== h ==  <!--c1-->  <!--c2--><!--c3-->" ],
-			[ "== h ==    ", "== h ==  <!--c1--><!--c2-->  <!--c3-->" ],
-			[ "== h ==      ", "== h ==  <!--c1-->  <!--c2-->  <!--c3-->" ],
-			[ "== h ==  ", "== h ==<!--c1--><!--c2--><!--c3-->  " ],
-			[ "== h ==    ", "== h ==<!--c1-->  <!--c2--><!--c3-->  " ],
-			[ "== h ==    ", "== h ==<!--c1--><!--c2-->  <!--c3-->  " ],
-			[ "== h ==      ", "== h ==<!--c1-->  <!--c2-->  <!--c3-->  " ],
-			[ "== h ==    ", "== h ==  <!--c1--><!--c2--><!--c3-->  " ],
-			[ "== h ==      ", "== h ==  <!--c1-->  <!--c2--><!--c3-->  " ],
-			[ "== h ==      ", "== h ==  <!--c1--><!--c2-->  <!--c3-->  " ],
-			[ "== h ==        ", "== h ==  <!--c1-->  <!--c2-->  <!--c3-->  " ],
-			[ "== h == 	", "== h ==<!--c1--> 	<!--c2-->" ],
-			[ "== h == 	 	", "== h == 	<!--c1--> 	<!--c2-->" ],
-			[ "== h == 	 	", "== h ==<!--c1--> 	<!--c2--> 	" ],
-
-			/* These are not working: */
-			[ "== h == x   ", "== h == x <!--c1--><!--c2--><!--c3-->  " ],
-			[ "== h == x   ", "== h ==<!--c1--> x <!--c2--><!--c3-->  " ],
-			[ "== h == x ", "== h ==<!--c1--><!--c2--><!--c3--> x " ],
-		] );
-		// phpcs:enable
-	}
-
-	/**
-	 * @dataProvider provideHeadings
-	 */
-	public function testHeadings( $className, $wikiText, $expectedXml ) {
-		$this->assertEquals( $this->normalizeXml( $expectedXml ),
-			$this->preprocessToXml( $className, $wikiText ) );
-	}
-}
diff --git a/tests/phpunit/includes/parser/TidyTest.php b/tests/phpunit/includes/parser/TidyTest.php
deleted file mode 100644
index 898ef2d163..0000000000
--- a/tests/phpunit/includes/parser/TidyTest.php
+++ /dev/null
@@ -1,64 +0,0 @@
-markTestSkipped( 'Tidy not found' );
-		}
-	}
-
-	/**
-	 * @dataProvider provideTestWrapping
-	 */
-	public function testTidyWrapping( $expected, $text, $msg = '' ) {
-		$text = MWTidy::tidy( $text );
-		// We don't care about where Tidy wants to stick is 

s - $text = trim( preg_replace( '##', '', $text ) ); - // Windows, we love you! - $text = str_replace( "\r", '', $text ); - $this->assertEquals( $expected, $text, $msg ); - } - - public static function provideTestWrapping() { - $testMathML = <<<'MathML' - - - a - - - x - 2 - - + - b - - x - + - c - - -MathML; - return [ - [ - 'foo', - 'foo', - ' should survive tidy' - ], - [ - 'foo', - 'foo', - ' should survive tidy' - ], - [ 'foo', 'foo', ' should survive tidy' ], - [ "foo", 'foo', ' should survive tidy' ], - [ "foo", 'foo', ' should survive tidy' ], - [ $testMathML, $testMathML, ' should survive tidy' ], - ]; - } -} diff --git a/tests/phpunit/includes/password/PasswordFactoryTest.php b/tests/phpunit/includes/password/PasswordFactoryTest.php deleted file mode 100644 index a7b3557516..0000000000 --- a/tests/phpunit/includes/password/PasswordFactoryTest.php +++ /dev/null @@ -1,124 +0,0 @@ -assertEquals( [ '' ], array_keys( $pf->getTypes() ) ); - $this->assertEquals( '', $pf->getDefaultType() ); - - $pf = new PasswordFactory( [ - 'foo' => [ 'class' => 'FooPassword' ], - 'bar' => [ 'class' => 'BarPassword', 'baz' => 'boom' ], - ], 'foo' ); - $this->assertEquals( [ '', 'foo', 'bar' ], array_keys( $pf->getTypes() ) ); - $this->assertArraySubset( [ 'class' => 'BarPassword', 'baz' => 'boom' ], $pf->getTypes()['bar'] ); - $this->assertEquals( 'foo', $pf->getDefaultType() ); - } - - public function testRegister() { - $pf = new PasswordFactory; - $pf->register( 'foo', [ 'class' => InvalidPassword::class ] ); - $this->assertArrayHasKey( 'foo', $pf->getTypes() ); - } - - public function testSetDefaultType() { - $pf = new PasswordFactory; - $pf->register( '1', [ 'class' => InvalidPassword::class ] ); - $pf->register( '2', [ 'class' => InvalidPassword::class ] ); - $pf->setDefaultType( '1' ); - $this->assertSame( '1', $pf->getDefaultType() ); - $pf->setDefaultType( '2' ); - $this->assertSame( '2', $pf->getDefaultType() ); - } - - /** - * @expectedException Exception - */ - public function testSetDefaultTypeError() { - $pf = new PasswordFactory; - $pf->setDefaultType( 'bogus' ); - } - - public function testInit() { - $config = new HashConfig( [ - 'PasswordConfig' => [ - 'foo' => [ 'class' => InvalidPassword::class ], - ], - 'PasswordDefault' => 'foo' - ] ); - $pf = new PasswordFactory; - $pf->init( $config ); - $this->assertSame( 'foo', $pf->getDefaultType() ); - $this->assertArrayHasKey( 'foo', $pf->getTypes() ); - } - - public function testNewFromCiphertext() { - $pf = new PasswordFactory; - $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] ); - $pw = $pf->newFromCiphertext( ':B:salt:d529e941509eb9e9b9cfaeae1fe7ca23' ); - $this->assertInstanceOf( MWSaltedPassword::class, $pw ); - } - - public function provideNewFromCiphertextErrors() { - return [ [ 'blah' ], [ ':blah:' ] ]; - } - - /** - * @dataProvider provideNewFromCiphertextErrors - * @expectedException PasswordError - */ - public function testNewFromCiphertextErrors( $hash ) { - $pf = new PasswordFactory; - $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] ); - $pf->newFromCiphertext( $hash ); - } - - public function testNewFromType() { - $pf = new PasswordFactory; - $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] ); - $pw = $pf->newFromType( 'B' ); - $this->assertInstanceOf( MWSaltedPassword::class, $pw ); - } - - /** - * @expectedException PasswordError - */ - public function testNewFromTypeError() { - $pf = new PasswordFactory; - $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] ); - $pf->newFromType( 'bogus' ); - } - - public function testNewFromPlaintext() { - $pf = new PasswordFactory; - $pf->register( 'A', [ 'class' => MWOldPassword::class ] ); - $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] ); - $pf->setDefaultType( 'A' ); - - $this->assertInstanceOf( InvalidPassword::class, $pf->newFromPlaintext( null ) ); - $this->assertInstanceOf( MWOldPassword::class, $pf->newFromPlaintext( 'password' ) ); - $this->assertInstanceOf( MWSaltedPassword::class, - $pf->newFromPlaintext( 'password', $pf->newFromType( 'B' ) ) ); - } - - public function testNeedsUpdate() { - $pf = new PasswordFactory; - $pf->register( 'A', [ 'class' => MWOldPassword::class ] ); - $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] ); - $pf->setDefaultType( 'A' ); - - $this->assertFalse( $pf->needsUpdate( $pf->newFromType( 'A' ) ) ); - $this->assertTrue( $pf->needsUpdate( $pf->newFromType( 'B' ) ) ); - } - - public function testGenerateRandomPasswordString() { - $this->assertSame( 13, strlen( PasswordFactory::generateRandomPasswordString( 13 ) ) ); - } - - public function testNewInvalidPassword() { - $this->assertInstanceOf( InvalidPassword::class, PasswordFactory::newInvalidPassword() ); - } -} diff --git a/tests/phpunit/includes/password/PasswordTest.php b/tests/phpunit/includes/password/PasswordTest.php deleted file mode 100644 index 61a5147277..0000000000 --- a/tests/phpunit/includes/password/PasswordTest.php +++ /dev/null @@ -1,33 +0,0 @@ -newFromPlaintext( null ); - - $this->assertInstanceOf( InvalidPassword::class, $invalid ); - } -} diff --git a/tests/phpunit/includes/preferences/FiltersTest.php b/tests/phpunit/includes/preferences/FiltersTest.php deleted file mode 100644 index 60b01b880c..0000000000 --- a/tests/phpunit/includes/preferences/FiltersTest.php +++ /dev/null @@ -1,141 +0,0 @@ -filterFromForm( '0' ) ); - self::assertSame( 3, $filter->filterFromForm( '3' ) ); - self::assertSame( '123', $filter->filterForForm( '123' ) ); - } - - /** - * @covers MediaWiki\Preferences\TimezoneFilter::filterFromForm() - * @dataProvider provideTimezoneFilter - * - * @param string $input - * @param string $expected - */ - public function testTimezoneFilter( $input, $expected ) { - $filter = new TimezoneFilter(); - $result = $filter->filterFromForm( $input ); - self::assertEquals( $expected, $result ); - } - - public function provideTimezoneFilter() { - return [ - [ 'ZoneInfo', 'Offset|0' ], - [ 'ZoneInfo|bogus', 'Offset|0' ], - [ 'System', 'System' ], - [ '2:30', 'Offset|150' ], - ]; - } - - /** - * @covers MediaWiki\Preferences\MultiUsernameFilter::filterFromForm() - * @dataProvider provideMultiUsernameFilterFrom - * - * @param string $input - * @param string|null $expected - */ - public function testMultiUsernameFilterFrom( $input, $expected ) { - $filter = $this->makeMultiUsernameFilter(); - $result = $filter->filterFromForm( $input ); - self::assertSame( $expected, $result ); - } - - public function provideMultiUsernameFilterFrom() { - return [ - [ '', null ], - [ "\n\n\n", null ], - [ 'Foo', '1' ], - [ "\n\n\nFoo\nBar\n", "1\n2" ], - [ "Baz\nInvalid\nFoo", "3\n1" ], - [ "Invalid", null ], - [ "Invalid\n\n\nInvalid\n", null ], - ]; - } - - /** - * @covers MediaWiki\Preferences\MultiUsernameFilter::filterForForm() - * @dataProvider provideMultiUsernameFilterFor - * - * @param string $input - * @param string $expected - */ - public function testMultiUsernameFilterFor( $input, $expected ) { - $filter = $this->makeMultiUsernameFilter(); - $result = $filter->filterForForm( $input ); - self::assertSame( $expected, $result ); - } - - public function provideMultiUsernameFilterFor() { - return [ - [ '', '' ], - [ "\n", '' ], - [ '1', 'Foo' ], - [ "\n1\n\n2\377\n", "Foo\nBar" ], - [ "666\n667", '' ], - ]; - } - - private function makeMultiUsernameFilter() { - $userMapping = [ - 'Foo' => 1, - 'Bar' => 2, - 'Baz' => 3, - ]; - $flipped = array_flip( $userMapping ); - $idLookup = self::getMockBuilder( CentralIdLookup::class ) - ->disableOriginalConstructor() - ->setMethods( [ 'centralIdsFromNames', 'namesFromCentralIds' ] ) - ->getMockForAbstractClass(); - - $idLookup->method( 'centralIdsFromNames' ) - ->will( self::returnCallback( function ( $names ) use ( $userMapping ) { - $ids = []; - foreach ( $names as $name ) { - $ids[] = $userMapping[$name] ?? null; - } - return array_filter( $ids, 'is_numeric' ); - } ) ); - $idLookup->method( 'namesFromCentralIds' ) - ->will( self::returnCallback( function ( $ids ) use ( $flipped ) { - $names = []; - foreach ( $ids as $id ) { - $names[] = $flipped[$id] ?? null; - } - return array_filter( $names, 'is_string' ); - } ) ); - - return new MultiUsernameFilter( $idLookup ); - } -} diff --git a/tests/phpunit/includes/registration/ExtensionJsonValidatorTest.php b/tests/phpunit/includes/registration/ExtensionJsonValidatorTest.php deleted file mode 100644 index 46c697f8ba..0000000000 --- a/tests/phpunit/includes/registration/ExtensionJsonValidatorTest.php +++ /dev/null @@ -1,97 +0,0 @@ - - * - * 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. - * - */ - -/** - * @covers ExtensionJsonValidator - */ -class ExtensionJsonValidatorTest extends MediaWikiTestCase { - - /** - * @dataProvider provideValidate - */ - public function testValidate( $file, $expected ) { - // If a dependency is missing, skip this test. - $validator = new ExtensionJsonValidator( function ( $msg ) { - $this->markTestSkipped( $msg ); - } ); - - if ( is_string( $expected ) ) { - $this->setExpectedException( - ExtensionJsonValidationError::class, - $expected - ); - } - - $dir = __DIR__ . '/../../data/registration/'; - $this->assertSame( - $expected, - $validator->validate( $dir . $file ) - ); - } - - public function provideValidate() { - return [ - [ - 'notjson.txt', - 'notjson.txt is not valid JSON' - ], - [ - 'duplicate_keys.json', - 'Duplicate key: name' - ], - [ - 'no_manifest_version.json', - 'no_manifest_version.json does not have manifest_version set.' - ], - [ - 'old_manifest_version.json', - 'old_manifest_version.json is using a non-supported schema version' - ], - [ - 'newer_manifest_version.json', - 'newer_manifest_version.json is using a non-supported schema version' - ], - [ - 'bad_spdx.json', - "bad_spdx.json did not pass validation. -[license-name] Invalid SPDX license identifier, see " - ], - [ - 'invalid.json', - "invalid.json did not pass validation. -[license-name] Array value found, but a string is required" - ], - [ - 'good.json', - true - ], - [ - 'bad_url.json', 'bad_url.json did not pass validation. -[url] Should use HTTPS for www.mediawiki.org URLs' - ], - [ - 'bad_url2.json', 'bad_url2.json did not pass validation. -[url] Should use www.mediawiki.org domain -[url] Should use HTTPS for www.mediawiki.org URLs' - ] - ]; - } - -} diff --git a/tests/phpunit/includes/registration/ExtensionProcessorTest.php b/tests/phpunit/includes/registration/ExtensionProcessorTest.php deleted file mode 100644 index cdd5c63eff..0000000000 --- a/tests/phpunit/includes/registration/ExtensionProcessorTest.php +++ /dev/null @@ -1,829 +0,0 @@ -dir = __DIR__ . '/FooBar/extension.json'; - $this->dirname = dirname( $this->dir ); - } - - /** - * 'name' is absolutely required - * - * @var array - */ - public static $default = [ - 'name' => 'FooBar', - ]; - - public function testExtractInfo() { - // Test that attributes that begin with @ are ignored - $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, self::$default + [ - '@metadata' => [ 'foobarbaz' ], - 'AnAttribute' => [ 'omg' ], - 'AutoloadClasses' => [ 'FooBar' => 'includes/FooBar.php' ], - 'SpecialPages' => [ 'Foo' => 'SpecialFoo' ], - 'callback' => 'FooBar::onRegistration', - ], 1 ); - - $extracted = $processor->getExtractedInfo(); - $attributes = $extracted['attributes']; - $this->assertArrayHasKey( 'AnAttribute', $attributes ); - $this->assertArrayNotHasKey( '@metadata', $attributes ); - $this->assertArrayNotHasKey( 'AutoloadClasses', $attributes ); - $this->assertSame( - [ 'FooBar' => 'FooBar::onRegistration' ], - $extracted['callbacks'] - ); - $this->assertSame( - [ 'Foo' => 'SpecialFoo' ], - $extracted['globals']['wgSpecialPages'] - ); - } - - public function testExtractNamespaces() { - // Test that namespace IDs can be overwritten - if ( !defined( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X' ) ) { - define( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X', 123456 ); - } - - $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, self::$default + [ - 'namespaces' => [ - [ - 'id' => 332200, - 'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A', - 'name' => 'Test_A', - 'defaultcontentmodel' => 'TestModel', - 'gender' => [ - 'male' => 'Male test', - 'female' => 'Female test', - ], - 'subpages' => true, - 'content' => true, - 'protection' => 'userright', - ], - [ // Test_X will use ID 123456 not 334400 - 'id' => 334400, - 'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X', - 'name' => 'Test_X', - 'defaultcontentmodel' => 'TestModel' - ], - ] - ], 1 ); - - $extracted = $processor->getExtractedInfo(); - - $this->assertArrayHasKey( - 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A', - $extracted['defines'] - ); - $this->assertArrayNotHasKey( - 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X', - $extracted['defines'] - ); - - $this->assertSame( - $extracted['defines']['MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A'], - 332200 - ); - - $this->assertArrayHasKey( 'ExtensionNamespaces', $extracted['attributes'] ); - $this->assertArrayHasKey( 123456, $extracted['attributes']['ExtensionNamespaces'] ); - $this->assertArrayHasKey( 332200, $extracted['attributes']['ExtensionNamespaces'] ); - $this->assertArrayNotHasKey( 334400, $extracted['attributes']['ExtensionNamespaces'] ); - - $this->assertSame( 'Test_X', $extracted['attributes']['ExtensionNamespaces'][123456] ); - $this->assertSame( 'Test_A', $extracted['attributes']['ExtensionNamespaces'][332200] ); - $this->assertSame( - [ 'male' => 'Male test', 'female' => 'Female test' ], - $extracted['globals']['wgExtraGenderNamespaces'][332200] - ); - // A has subpages, X does not - $this->assertTrue( $extracted['globals']['wgNamespacesWithSubpages'][332200] ); - $this->assertArrayNotHasKey( 123456, $extracted['globals']['wgNamespacesWithSubpages'] ); - } - - public static function provideRegisterHooks() { - $merge = [ ExtensionRegistry::MERGE_STRATEGY => 'array_merge_recursive' ]; - // Format: - // Current $wgHooks - // Content in extension.json - // Expected value of $wgHooks - return [ - // No hooks - [ - [], - self::$default, - $merge, - ], - // No current hooks, adding one for "FooBaz" in string format - [ - [], - [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default, - [ 'FooBaz' => [ 'FooBazCallback' ] ] + $merge, - ], - // Hook for "FooBaz", adding another one - [ - [ 'FooBaz' => [ 'PriorCallback' ] ], - [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default, - [ 'FooBaz' => [ 'PriorCallback', 'FooBazCallback' ] ] + $merge, - ], - // No current hooks, adding one for "FooBaz" in verbose array format - [ - [], - [ 'Hooks' => [ 'FooBaz' => [ 'FooBazCallback' ] ] ] + self::$default, - [ 'FooBaz' => [ 'FooBazCallback' ] ] + $merge, - ], - // Hook for "BarBaz", adding one for "FooBaz" - [ - [ 'BarBaz' => [ 'BarBazCallback' ] ], - [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default, - [ - 'BarBaz' => [ 'BarBazCallback' ], - 'FooBaz' => [ 'FooBazCallback' ], - ] + $merge, - ], - // Callbacks for FooBaz wrapped in an array - [ - [], - [ 'Hooks' => [ 'FooBaz' => [ 'Callback1' ] ] ] + self::$default, - [ - 'FooBaz' => [ 'Callback1' ], - ] + $merge, - ], - // Multiple callbacks for FooBaz hook - [ - [], - [ 'Hooks' => [ 'FooBaz' => [ 'Callback1', 'Callback2' ] ] ] + self::$default, - [ - 'FooBaz' => [ 'Callback1', 'Callback2' ], - ] + $merge, - ], - ]; - } - - /** - * @dataProvider provideRegisterHooks - */ - public function testRegisterHooks( $pre, $info, $expected ) { - $processor = new MockExtensionProcessor( [ 'wgHooks' => $pre ] ); - $processor->extractInfo( $this->dir, $info, 1 ); - $extracted = $processor->getExtractedInfo(); - $this->assertEquals( $expected, $extracted['globals']['wgHooks'] ); - } - - public function testExtractConfig1() { - $processor = new ExtensionProcessor; - $info = [ - 'config' => [ - 'Bar' => 'somevalue', - 'Foo' => 10, - '@IGNORED' => 'yes', - ], - ] + self::$default; - $info2 = [ - 'config' => [ - '_prefix' => 'eg', - 'Bar' => 'somevalue' - ], - 'name' => 'FooBar2', - ]; - $processor->extractInfo( $this->dir, $info, 1 ); - $processor->extractInfo( $this->dir, $info2, 1 ); - $extracted = $processor->getExtractedInfo(); - $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] ); - $this->assertEquals( 10, $extracted['globals']['wgFoo'] ); - $this->assertArrayNotHasKey( 'wg@IGNORED', $extracted['globals'] ); - // Custom prefix: - $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] ); - } - - public function testExtractConfig2() { - $processor = new ExtensionProcessor; - $info = [ - 'config' => [ - 'Bar' => [ 'value' => 'somevalue' ], - 'Foo' => [ 'value' => 10 ], - 'Path' => [ 'value' => 'foo.txt', 'path' => true ], - 'Namespaces' => [ - 'value' => [ - '10' => true, - '12' => false, - ], - 'merge_strategy' => 'array_plus', - ], - ], - ] + self::$default; - $info2 = [ - 'config' => [ - 'Bar' => [ 'value' => 'somevalue' ], - ], - 'config_prefix' => 'eg', - 'name' => 'FooBar2', - ]; - $processor->extractInfo( $this->dir, $info, 2 ); - $processor->extractInfo( $this->dir, $info2, 2 ); - $extracted = $processor->getExtractedInfo(); - $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] ); - $this->assertEquals( 10, $extracted['globals']['wgFoo'] ); - $this->assertEquals( "{$this->dirname}/foo.txt", $extracted['globals']['wgPath'] ); - // Custom prefix: - $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] ); - $this->assertSame( - [ 10 => true, 12 => false, ExtensionRegistry::MERGE_STRATEGY => 'array_plus' ], - $extracted['globals']['wgNamespaces'] - ); - } - - /** - * @expectedException RuntimeException - */ - public function testDuplicateConfigKey1() { - $processor = new ExtensionProcessor; - $info = [ - 'config' => [ - 'Bar' => '', - ] - ] + self::$default; - $info2 = [ - 'config' => [ - 'Bar' => 'g', - ], - 'name' => 'FooBar2', - ]; - $processor->extractInfo( $this->dir, $info, 1 ); - $processor->extractInfo( $this->dir, $info2, 1 ); - } - - /** - * @expectedException RuntimeException - */ - public function testDuplicateConfigKey2() { - $processor = new ExtensionProcessor; - $info = [ - 'config' => [ - 'Bar' => [ 'value' => 'somevalue' ], - ] - ] + self::$default; - $info2 = [ - 'config' => [ - 'Bar' => [ 'value' => 'somevalue' ], - ], - 'name' => 'FooBar2', - ]; - $processor->extractInfo( $this->dir, $info, 2 ); - $processor->extractInfo( $this->dir, $info2, 2 ); - } - - public static function provideExtractExtensionMessagesFiles() { - $dir = __DIR__ . '/FooBar/'; - return [ - [ - [ 'ExtensionMessagesFiles' => [ 'FooBarAlias' => 'FooBar.alias.php' ] ], - [ 'wgExtensionMessagesFiles' => [ 'FooBarAlias' => $dir . 'FooBar.alias.php' ] ] - ], - [ - [ - 'ExtensionMessagesFiles' => [ - 'FooBarAlias' => 'FooBar.alias.php', - 'FooBarMagic' => 'FooBar.magic.i18n.php', - ], - ], - [ - 'wgExtensionMessagesFiles' => [ - 'FooBarAlias' => $dir . 'FooBar.alias.php', - 'FooBarMagic' => $dir . 'FooBar.magic.i18n.php', - ], - ], - ], - ]; - } - - /** - * @dataProvider provideExtractExtensionMessagesFiles - */ - public function testExtractExtensionMessagesFiles( $input, $expected ) { - $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, $input + self::$default, 1 ); - $out = $processor->getExtractedInfo(); - foreach ( $expected as $key => $value ) { - $this->assertEquals( $value, $out['globals'][$key] ); - } - } - - public static function provideExtractMessagesDirs() { - $dir = __DIR__ . '/FooBar/'; - return [ - [ - [ 'MessagesDirs' => [ 'VisualEditor' => 'i18n' ] ], - [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n' ] ] ] - ], - [ - [ 'MessagesDirs' => [ 'VisualEditor' => [ 'i18n', 'foobar' ] ] ], - [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n', $dir . 'foobar' ] ] ] - ], - ]; - } - - /** - * @dataProvider provideExtractMessagesDirs - */ - public function testExtractMessagesDirs( $input, $expected ) { - $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, $input + self::$default, 1 ); - $out = $processor->getExtractedInfo(); - foreach ( $expected as $key => $value ) { - $this->assertEquals( $value, $out['globals'][$key] ); - } - } - - public function testExtractCredits() { - $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, self::$default, 1 ); - $this->setExpectedException( Exception::class ); - $processor->extractInfo( $this->dir, self::$default, 1 ); - } - - /** - * @dataProvider provideExtractResourceLoaderModules - */ - public function testExtractResourceLoaderModules( - $input, - array $expectedGlobals, - array $expectedAttribs = [] - ) { - $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, $input + self::$default, 1 ); - $out = $processor->getExtractedInfo(); - foreach ( $expectedGlobals as $key => $value ) { - $this->assertEquals( $value, $out['globals'][$key] ); - } - foreach ( $expectedAttribs as $key => $value ) { - $this->assertEquals( $value, $out['attributes'][$key] ); - } - } - - public static function provideExtractResourceLoaderModules() { - $dir = __DIR__ . '/FooBar'; - return [ - // Generic module with localBasePath/remoteExtPath specified - [ - // Input - [ - 'ResourceModules' => [ - 'test.foo' => [ - 'styles' => 'foobar.js', - 'localBasePath' => '', - 'remoteExtPath' => 'FooBar', - ], - ], - ], - // Expected - [ - 'wgResourceModules' => [ - 'test.foo' => [ - 'styles' => 'foobar.js', - 'localBasePath' => $dir, - 'remoteExtPath' => 'FooBar', - ], - ], - ], - ], - // ResourceFileModulePaths specified: - [ - // Input - [ - 'ResourceFileModulePaths' => [ - 'localBasePath' => 'modules', - 'remoteExtPath' => 'FooBar/modules', - ], - 'ResourceModules' => [ - // No paths - 'test.foo' => [ - 'styles' => 'foo.js', - ], - // Different paths set - 'test.bar' => [ - 'styles' => 'bar.js', - 'localBasePath' => 'subdir', - 'remoteExtPath' => 'FooBar/subdir', - ], - // Custom class with no paths set - 'test.class' => [ - 'class' => 'FooBarModule', - 'extra' => 'argument', - ], - // Custom class with a localBasePath - 'test.class.with.path' => [ - 'class' => 'FooBarPathModule', - 'extra' => 'argument', - 'localBasePath' => '', - ] - ], - ], - // Expected - [ - 'wgResourceModules' => [ - 'test.foo' => [ - 'styles' => 'foo.js', - 'localBasePath' => "$dir/modules", - 'remoteExtPath' => 'FooBar/modules', - ], - 'test.bar' => [ - 'styles' => 'bar.js', - 'localBasePath' => "$dir/subdir", - 'remoteExtPath' => 'FooBar/subdir', - ], - 'test.class' => [ - 'class' => 'FooBarModule', - 'extra' => 'argument', - 'localBasePath' => "$dir/modules", - 'remoteExtPath' => 'FooBar/modules', - ], - 'test.class.with.path' => [ - 'class' => 'FooBarPathModule', - 'extra' => 'argument', - 'localBasePath' => $dir, - 'remoteExtPath' => 'FooBar/modules', - ] - ], - ], - ], - // ResourceModuleSkinStyles with file module paths - [ - // Input - [ - 'ResourceFileModulePaths' => [ - 'localBasePath' => '', - 'remoteSkinPath' => 'FooBar', - ], - 'ResourceModuleSkinStyles' => [ - 'foobar' => [ - 'test.foo' => 'foo.css', - ] - ], - ], - // Expected - [ - 'wgResourceModuleSkinStyles' => [ - 'foobar' => [ - 'test.foo' => 'foo.css', - 'localBasePath' => $dir, - 'remoteSkinPath' => 'FooBar', - ], - ], - ], - ], - // ResourceModuleSkinStyles with file module paths and an override - [ - // Input - [ - 'ResourceFileModulePaths' => [ - 'localBasePath' => '', - 'remoteSkinPath' => 'FooBar', - ], - 'ResourceModuleSkinStyles' => [ - 'foobar' => [ - 'test.foo' => 'foo.css', - 'remoteSkinPath' => 'BarFoo' - ], - ], - ], - // Expected - [ - 'wgResourceModuleSkinStyles' => [ - 'foobar' => [ - 'test.foo' => 'foo.css', - 'localBasePath' => $dir, - 'remoteSkinPath' => 'BarFoo', - ], - ], - ], - ], - 'QUnit test module' => [ - // Input - [ - 'QUnitTestModule' => [ - 'localBasePath' => '', - 'remoteExtPath' => 'Foo', - 'scripts' => 'bar.js', - ], - ], - // Expected - [], - [ - 'QUnitTestModules' => [ - 'test.FooBar' => [ - 'localBasePath' => $dir, - 'remoteExtPath' => 'Foo', - 'scripts' => 'bar.js', - ], - ], - ], - ], - ]; - } - - public static function provideSetToGlobal() { - return [ - [ - [ 'wgAPIModules', 'wgAvailableRights' ], - [], - [ - 'APIModules' => [ 'foobar' => 'ApiFooBar' ], - 'AvailableRights' => [ 'foobar', 'unfoobar' ], - ], - [ - 'wgAPIModules' => [ 'foobar' => 'ApiFooBar' ], - 'wgAvailableRights' => [ 'foobar', 'unfoobar' ], - ], - ], - [ - [ 'wgAPIModules', 'wgAvailableRights' ], - [ - 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz' ], - 'wgAvailableRights' => [ 'barbaz' ] - ], - [ - 'APIModules' => [ 'foobar' => 'ApiFooBar' ], - 'AvailableRights' => [ 'foobar', 'unfoobar' ], - ], - [ - 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz', 'foobar' => 'ApiFooBar' ], - 'wgAvailableRights' => [ 'barbaz', 'foobar', 'unfoobar' ], - ], - ], - [ - [ 'wgGroupPermissions' ], - [ - 'wgGroupPermissions' => [ - 'sysop' => [ 'delete' ] - ], - ], - [ - 'GroupPermissions' => [ - 'sysop' => [ 'undelete' ], - 'user' => [ 'edit' ] - ], - ], - [ - 'wgGroupPermissions' => [ - 'sysop' => [ 'delete', 'undelete' ], - 'user' => [ 'edit' ] - ], - ] - ] - ]; - } - - /** - * Attributes under manifest_version 2 - */ - public function testExtractAttributes() { - $processor = new ExtensionProcessor(); - // Load FooBar extension - $processor->extractInfo( $this->dir, [ 'name' => 'FooBar' ], 2 ); - $processor->extractInfo( - $this->dir, - [ - 'name' => 'Baz', - 'attributes' => [ - // Loaded - 'FooBar' => [ - 'Plugins' => [ - 'ext.baz.foobar', - ], - ], - // Not loaded - 'FizzBuzz' => [ - 'MorePlugins' => [ - 'ext.baz.fizzbuzz', - ], - ], - ], - ], - 2 - ); - - $info = $processor->getExtractedInfo(); - $this->assertArrayHasKey( 'FooBarPlugins', $info['attributes'] ); - $this->assertSame( [ 'ext.baz.foobar' ], $info['attributes']['FooBarPlugins'] ); - $this->assertArrayNotHasKey( 'FizzBuzzMorePlugins', $info['attributes'] ); - } - - /** - * Attributes under manifest_version 1 - */ - public function testAttributes1() { - $processor = new ExtensionProcessor(); - $processor->extractInfo( - $this->dir, - [ - 'name' => 'FooBar', - 'FooBarPlugins' => [ - 'ext.baz.foobar', - ], - 'FizzBuzzMorePlugins' => [ - 'ext.baz.fizzbuzz', - ], - ], - 1 - ); - $processor->extractInfo( - $this->dir, - [ - 'name' => 'FooBar2', - 'FizzBuzzMorePlugins' => [ - 'ext.bar.fizzbuzz', - ] - ], - 1 - ); - - $info = $processor->getExtractedInfo(); - $this->assertArrayHasKey( 'FooBarPlugins', $info['attributes'] ); - $this->assertSame( [ 'ext.baz.foobar' ], $info['attributes']['FooBarPlugins'] ); - $this->assertArrayHasKey( 'FizzBuzzMorePlugins', $info['attributes'] ); - $this->assertSame( - [ 'ext.baz.fizzbuzz', 'ext.bar.fizzbuzz' ], - $info['attributes']['FizzBuzzMorePlugins'] - ); - } - - public function testAttributes1_notarray() { - $processor = new ExtensionProcessor(); - $this->setExpectedException( - InvalidArgumentException::class, - "The value for 'FooBarPlugins' should be an array (from {$this->dir})" - ); - $processor->extractInfo( - $this->dir, - [ - 'FooBarPlugins' => 'ext.baz.foobar', - ] + self::$default, - 1 - ); - } - - public function testExtractPathBasedGlobal() { - $processor = new ExtensionProcessor(); - $processor->extractInfo( - $this->dir, - [ - 'ParserTestFiles' => [ - 'tests/parserTests.txt', - 'tests/extraParserTests.txt', - ], - 'ServiceWiringFiles' => [ - 'includes/ServiceWiring.php' - ], - ] + self::$default, - 1 - ); - $globals = $processor->getExtractedInfo()['globals']; - $this->assertArrayHasKey( 'wgParserTestFiles', $globals ); - $this->assertSame( [ - "{$this->dirname}/tests/parserTests.txt", - "{$this->dirname}/tests/extraParserTests.txt" - ], $globals['wgParserTestFiles'] ); - $this->assertArrayHasKey( 'wgServiceWiringFiles', $globals ); - $this->assertSame( [ - "{$this->dirname}/includes/ServiceWiring.php" - ], $globals['wgServiceWiringFiles'] ); - } - - public function testGetRequirements() { - $info = self::$default + [ - 'requires' => [ - 'MediaWiki' => '>= 1.25.0', - 'platform' => [ - 'php' => '>= 5.5.9' - ], - 'extensions' => [ - 'Bar' => '*' - ] - ] - ]; - $processor = new ExtensionProcessor(); - $this->assertSame( - $info['requires'], - $processor->getRequirements( $info, false ) - ); - $this->assertSame( - [], - $processor->getRequirements( [], false ) - ); - } - - public function testGetDevRequirements() { - $info = self::$default + [ - 'dev-requires' => [ - 'MediaWiki' => '>= 1.31.0', - 'platform' => [ - 'ext-foo' => '*', - ], - 'skins' => [ - 'Baz' => '*', - ], - 'extensions' => [ - 'Biz' => '*', - ], - ], - ]; - $processor = new ExtensionProcessor(); - $this->assertSame( - $info['dev-requires'], - $processor->getRequirements( $info, true ) - ); - // Set some standard requirements, so we can test merging - $info['requires'] = [ - 'MediaWiki' => '>= 1.25.0', - 'platform' => [ - 'php' => '>= 5.5.9' - ], - 'extensions' => [ - 'Bar' => '*' - ] - ]; - $this->assertSame( - [ - 'MediaWiki' => '>= 1.25.0 >= 1.31.0', - 'platform' => [ - 'php' => '>= 5.5.9', - 'ext-foo' => '*', - ], - 'extensions' => [ - 'Bar' => '*', - 'Biz' => '*', - ], - 'skins' => [ - 'Baz' => '*', - ], - ], - $processor->getRequirements( $info, true ) - ); - - // If there's no dev-requires, it just returns requires - unset( $info['dev-requires'] ); - $this->assertSame( - $info['requires'], - $processor->getRequirements( $info, true ) - ); - } - - public function testGetExtraAutoloaderPaths() { - $processor = new ExtensionProcessor(); - $this->assertSame( - [ "{$this->dirname}/vendor/autoload.php" ], - $processor->getExtraAutoloaderPaths( $this->dirname, [ - 'load_composer_autoloader' => true, - ] ) - ); - } - - /** - * Verify that extension.schema.json is in sync with ExtensionProcessor - * - * @coversNothing - */ - public function testGlobalSettingsDocumentedInSchema() { - global $IP; - $globalSettings = TestingAccessWrapper::newFromClass( - ExtensionProcessor::class )->globalSettings; - - $version = ExtensionRegistry::MANIFEST_VERSION; - $schema = FormatJson::decode( - file_get_contents( "$IP/docs/extension.schema.v$version.json" ), - true - ); - $missing = []; - foreach ( $globalSettings as $global ) { - if ( !isset( $schema['properties'][$global] ) ) { - $missing[] = $global; - } - } - - $this->assertEquals( [], $missing, - "The following global settings are not documented in docs/extension.schema.json" ); - } -} - -/** - * Allow overriding the default value of $this->globals - * so we can test merging - */ -class MockExtensionProcessor extends ExtensionProcessor { - public function __construct( $globals = [] ) { - $this->globals = $globals + $this->globals; - } -} diff --git a/tests/phpunit/includes/registration/VersionCheckerTest.php b/tests/phpunit/includes/registration/VersionCheckerTest.php deleted file mode 100644 index e824e3f02c..0000000000 --- a/tests/phpunit/includes/registration/VersionCheckerTest.php +++ /dev/null @@ -1,479 +0,0 @@ -assertEquals( $expected, !(bool)$checker->checkArray( [ - 'FakeExtension' => [ - 'MediaWiki' => $constraint, - ], - ] ) ); - } - - public static function provideMediaWikiCheck() { - return [ - // [ $wgVersion, constraint, expected ] - [ '1.25alpha', '>= 1.26', false ], - [ '1.25.0', '>= 1.26', false ], - [ '1.26alpha', '>= 1.26', true ], - [ '1.26alpha', '>= 1.26.0', true ], - [ '1.26alpha', '>= 1.26.0-stable', false ], - [ '1.26.0', '>= 1.26.0-stable', true ], - [ '1.26.1', '>= 1.26.0-stable', true ], - [ '1.27.1', '>= 1.26.0-stable', true ], - [ '1.26alpha', '>= 1.26.1', false ], - [ '1.26alpha', '>= 1.26alpha', true ], - [ '1.26alpha', '>= 1.25', true ], - [ '1.26.0-alpha.14', '>= 1.26.0-alpha.15', false ], - [ '1.26.0-alpha.14', '>= 1.26.0-alpha.10', true ], - [ '1.26.1', '>= 1.26.2, <=1.26.0', false ], - [ '1.26.1', '^1.26.2', false ], - // Accept anything for un-parsable version strings - [ '1.26mwf14', '== 1.25alpha', true ], - [ 'totallyinvalid', '== 1.0', true ], - ]; - } - - /** - * @dataProvider providePhpValidCheck - */ - public function testPhpValidCheck( $phpVersion, $constraint, $expected ) { - $checker = new VersionChecker( '1.0.0', $phpVersion, [] ); - $this->assertEquals( $expected, !(bool)$checker->checkArray( [ - 'FakeExtension' => [ - 'platform' => [ - 'php' => $constraint, - ], - ], - ] ) ); - } - - public static function providePhpValidCheck() { - return [ - // [ phpVersion, constraint, expected ] - [ '7.0.23', '>= 7.0.0', true ], - [ '7.0.23', '^7.1.0', false ], - [ '7.0.23', '7.0.23', true ], - ]; - } - - /** - * @expectedException UnexpectedValueException - */ - public function testPhpInvalidConstraint() { - $checker = new VersionChecker( '1.0.0', '7.0.0', [] ); - $checker->checkArray( [ - 'FakeExtension' => [ - 'platform' => [ - 'php' => 'totallyinvalid', - ], - ], - ] ); - } - - /** - * @dataProvider providePhpInvalidVersion - * @expectedException UnexpectedValueException - */ - public function testPhpInvalidVersion( $phpVersion ) { - $checker = new VersionChecker( '1.0.0', $phpVersion, [] ); - } - - public static function providePhpInvalidVersion() { - return [ - // [ phpVersion ] - [ '7.abc' ], - [ '5.a.x' ], - ]; - } - - /** - * @dataProvider provideType - */ - public function testType( $given, $expected ) { - $checker = new VersionChecker( - '1.0.0', - '7.0.0', - [ 'phpLoadedExtension' ], - [ - 'presentAbility' => true, - 'presentAbilityWithMessage' => true, - 'missingAbility' => false, - 'missingAbilityWithMessage' => false, - ], - [ - 'presentAbilityWithMessage' => 'Present.', - 'missingAbilityWithMessage' => 'Missing.', - ] - ); - $checker->setLoadedExtensionsAndSkins( [ - 'FakeDependency' => [ - 'version' => '1.0.0', - ], - 'NoVersionGiven' => [], - ] ); - $this->assertEquals( $expected, $checker->checkArray( [ - 'FakeExtension' => $given, - ] ) ); - } - - public static function provideType() { - return [ - // valid type - [ - [ - 'extensions' => [ - 'FakeDependency' => '1.0.0', - ], - ], - [], - ], - [ - [ - 'MediaWiki' => '1.0.0', - ], - [], - ], - [ - [ - 'extensions' => [ - 'NoVersionGiven' => '*', - ], - ], - [], - ], - [ - [ - 'extensions' => [ - 'NoVersionGiven' => '1.0', - ], - ], - [ - [ - 'incompatible' => 'FakeExtension', - 'type' => 'incompatible-extensions', - 'msg' => 'NoVersionGiven does not expose its version, but FakeExtension requires: 1.0.', - ], - ], - ], - [ - [ - 'extensions' => [ - 'Missing' => '*', - ], - ], - [ - [ - 'missing' => 'Missing', - 'type' => 'missing-extensions', - 'msg' => 'FakeExtension requires Missing to be installed.', - ], - ], - ], - [ - [ - 'extensions' => [ - 'FakeDependency' => '2.0.0', - ], - ], - [ - [ - 'incompatible' => 'FakeExtension', - 'type' => 'incompatible-extensions', - // phpcs:ignore Generic.Files.LineLength.TooLong - 'msg' => 'FakeExtension is not compatible with the current installed version of FakeDependency (1.0.0), it requires: 2.0.0.', - ], - ], - ], - [ - [ - 'skins' => [ - 'FakeSkin' => '*', - ], - ], - [ - [ - 'missing' => 'FakeSkin', - 'type' => 'missing-skins', - 'msg' => 'FakeExtension requires FakeSkin to be installed.', - ], - ], - ], - [ - [ - 'platform' => [ - 'ext-phpLoadedExtension' => '*', - ], - ], - [], - ], - [ - [ - 'platform' => [ - 'ext-phpMissingExtension' => '*', - ], - ], - [ - [ - 'missing' => 'phpMissingExtension', - 'type' => 'missing-phpExtension', - // phpcs:ignore Generic.Files.LineLength.TooLong - 'msg' => 'FakeExtension requires phpMissingExtension PHP extension to be installed.', - ], - ], - ], - [ - [ - 'platform' => [ - 'ability-presentAbility' => true, - ], - ], - [], - ], - [ - [ - 'platform' => [ - 'ability-presentAbilityWithMessage' => true, - ], - ], - [], - ], - [ - [ - 'platform' => [ - 'ability-presentAbility' => false, - ], - ], - [], - ], - [ - [ - 'platform' => [ - 'ability-presentAbilityWithMessage' => false, - ], - ], - [], - ], - [ - [ - 'platform' => [ - 'ability-missingAbility' => true, - ], - ], - [ - [ - 'missing' => 'missingAbility', - 'type' => 'missing-ability', - 'msg' => 'FakeExtension requires "missingAbility" ability', - ], - ], - ], - [ - [ - 'platform' => [ - 'ability-missingAbilityWithMessage' => true, - ], - ], - [ - [ - 'missing' => 'missingAbilityWithMessage', - 'type' => 'missing-ability', - // phpcs:ignore Generic.Files.LineLength.TooLong - 'msg' => 'FakeExtension requires "missingAbilityWithMessage" ability: Missing.', - ], - ], - ], - [ - [ - 'platform' => [ - 'ability-missingAbility' => false, - ], - ], - [], - ], - [ - [ - 'platform' => [ - 'ability-missingAbilityWithMessage' => false, - ], - ], - [], - ], - ]; - } - - /** - * Check, if a non-parsable version constraint does not throw an exception or - * returns any error message. - */ - public function testInvalidConstraint() { - $checker = new VersionChecker( '1.0.0', '7.0.0', [] ); - $checker->setLoadedExtensionsAndSkins( [ - 'FakeDependency' => [ - 'version' => 'not really valid', - ], - ] ); - $this->assertEquals( [ - [ - 'type' => 'invalid-version', - 'msg' => "FakeDependency does not have a valid version string.", - ], - ], $checker->checkArray( [ - 'FakeExtension' => [ - 'extensions' => [ - 'FakeDependency' => '1.24.3', - ], - ], - ] ) ); - - $checker = new VersionChecker( '1.0.0', '7.0.0', [] ); - $checker->setLoadedExtensionsAndSkins( [ - 'FakeDependency' => [ - 'version' => '1.24.3', - ], - ] ); - - $this->setExpectedException( UnexpectedValueException::class ); - $checker->checkArray( [ - 'FakeExtension' => [ - 'FakeDependency' => 'not really valid', - ], - ] ); - } - - public function provideInvalidDependency() { - return [ - [ - [ - 'FakeExtension' => [ - 'platform' => [ - 'undefinedPlatformDependency' => '*', - ], - ], - ], - 'undefinedPlatformDependency', - ], - [ - [ - 'FakeExtension' => [ - 'platform' => [ - 'phpLoadedExtension' => '*', - ], - ], - ], - 'phpLoadedExtension', - ], - [ - [ - 'FakeExtension' => [ - 'platform' => [ - 'ability-invalidAbility' => true, - ], - ], - ], - 'ability-invalidAbility', - ], - [ - [ - 'FakeExtension' => [ - 'platform' => [ - 'presentAbility' => true, - ], - ], - ], - 'presentAbility', - ], - [ - [ - 'FakeExtension' => [ - 'undefinedDependencyType' => '*', - ], - ], - 'undefinedDependencyType', - ], - // T197478 - [ - [ - 'FakeExtension' => [ - 'skin' => [ - 'FakeSkin' => '*', - ], - ], - ], - 'skin', - ], - ]; - } - - /** - * @dataProvider provideInvalidDependency - */ - public function testInvalidDependency( $depencency, $type ) { - $checker = new VersionChecker( - '1.0.0', - '7.0.0', - [ 'phpLoadedExtension' ], - [ - 'presentAbility' => true, - 'missingAbility' => false, - ] - ); - $this->setExpectedException( - UnexpectedValueException::class, - "Dependency type $type unknown in FakeExtension" - ); - $checker->checkArray( $depencency ); - } - - public function testInvalidPhpExtensionConstraint() { - $checker = new VersionChecker( '1.0.0', '7.0.0', [ 'phpLoadedExtension' ] ); - $this->setExpectedException( - UnexpectedValueException::class, - 'Version constraints for PHP extensions are not supported in FakeExtension' - ); - $checker->checkArray( [ - 'FakeExtension' => [ - 'platform' => [ - 'ext-phpLoadedExtension' => '1.0.0', - ], - ], - ] ); - } - - /** - * @dataProvider provideInvalidAbilityType - */ - public function testInvalidAbilityType( $value ) { - $checker = new VersionChecker( '1.0.0', '7.0.0', [], [ 'presentAbility' => true ] ); - $this->setExpectedException( - UnexpectedValueException::class, - 'Only booleans are allowed to to indicate the presence of abilities in FakeExtension' - ); - $checker->checkArray( [ - 'FakeExtension' => [ - 'platform' => [ - 'ability-presentAbility' => $value, - ], - ], - ] ); - } - - public function provideInvalidAbilityType() { - return [ - [ null ], - [ 1 ], - [ '1' ], - ]; - } - -} diff --git a/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php b/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php deleted file mode 100644 index e178e96f41..0000000000 --- a/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php +++ /dev/null @@ -1,138 +0,0 @@ - 'qqx', - 'modules' => 'test.default', - 'only' => 'scripts', - 'skin' => 'fallback', - 'target' => 'test', - ] ); - return new ResourceLoaderContext( - new ResourceLoader( ResourceLoaderTestCase::getMinimalConfig() ), - $request - ); - } - - public function testChangeModules() { - $derived = new DerivativeResourceLoaderContext( self::makeContext() ); - $this->assertSame( $derived->getModules(), [ 'test.default' ], 'inherit from parent' ); - - $derived->setModules( [ 'test.override' ] ); - $this->assertSame( $derived->getModules(), [ 'test.override' ] ); - } - - public function testChangeLanguageAndDirection() { - $derived = new DerivativeResourceLoaderContext( self::makeContext() ); - $this->assertSame( $derived->getLanguage(), 'qqx', 'inherit from parent' ); - $this->assertSame( $derived->getDirection(), 'ltr', 'inherit from parent' ); - - $derived->setLanguage( 'nl' ); - $this->assertSame( $derived->getLanguage(), 'nl' ); - $this->assertSame( $derived->getDirection(), 'ltr' ); - - // Changing the language must clear cache of computed direction - $derived->setLanguage( 'he' ); - $this->assertSame( $derived->getDirection(), 'rtl' ); - $this->assertSame( $derived->getLanguage(), 'he' ); - - // Overriding the direction explicitly is allowed - $derived->setDirection( 'ltr' ); - $this->assertSame( $derived->getDirection(), 'ltr' ); - $this->assertSame( $derived->getLanguage(), 'he' ); - } - - public function testChangeSkin() { - $derived = new DerivativeResourceLoaderContext( self::makeContext() ); - $this->assertSame( $derived->getSkin(), 'fallback', 'inherit from parent' ); - - $derived->setSkin( 'myskin' ); - $this->assertSame( $derived->getSkin(), 'myskin' ); - } - - public function testChangeUser() { - $derived = new DerivativeResourceLoaderContext( self::makeContext() ); - $this->assertSame( $derived->getUser(), null, 'inherit from parent' ); - - $derived->setUser( 'MyUser' ); - $this->assertSame( $derived->getUser(), 'MyUser' ); - } - - public function testChangeDebug() { - $derived = new DerivativeResourceLoaderContext( self::makeContext() ); - $this->assertSame( $derived->getDebug(), false, 'inherit from parent' ); - - $derived->setDebug( true ); - $this->assertSame( $derived->getDebug(), true ); - } - - public function testChangeOnly() { - $derived = new DerivativeResourceLoaderContext( self::makeContext() ); - $this->assertSame( $derived->getOnly(), 'scripts', 'inherit from parent' ); - - $derived->setOnly( 'styles' ); - $this->assertSame( $derived->getOnly(), 'styles' ); - - $derived->setOnly( null ); - $this->assertSame( $derived->getOnly(), null ); - } - - public function testChangeVersion() { - $derived = new DerivativeResourceLoaderContext( self::makeContext() ); - $this->assertSame( $derived->getVersion(), null ); - - $derived->setVersion( 'hw1' ); - $this->assertSame( $derived->getVersion(), 'hw1' ); - } - - public function testChangeRaw() { - $derived = new DerivativeResourceLoaderContext( self::makeContext() ); - $this->assertSame( $derived->getRaw(), false, 'inherit from parent' ); - - $derived->setRaw( true ); - $this->assertSame( $derived->getRaw(), true ); - } - - public function testChangeHash() { - $derived = new DerivativeResourceLoaderContext( self::makeContext() ); - $this->assertSame( $derived->getHash(), 'qqx|fallback|||scripts|||||', 'inherit' ); - - $derived->setLanguage( 'nl' ); - $derived->setUser( 'Example' ); - // Assert that subclass is able to clear parent class "hash" member - $this->assertSame( $derived->getHash(), 'nl|fallback||Example|scripts|||||' ); - } - - public function testChangeContentOverrides() { - $derived = new DerivativeResourceLoaderContext( self::makeContext() ); - $this->assertNull( $derived->getContentOverrideCallback(), 'default' ); - - $override = function ( Title $t ) { - return null; - }; - $derived->setContentOverrideCallback( $override ); - $this->assertSame( $override, $derived->getContentOverrideCallback(), 'changed' ); - - $derived2 = new DerivativeResourceLoaderContext( $derived ); - $this->assertSame( - $override, - $derived2->getContentOverrideCallback(), - 'change via a second derivative layer' - ); - } - - public function testImmutableAccessors() { - $context = self::makeContext(); - $derived = new DerivativeResourceLoaderContext( $context ); - $this->assertSame( $derived->getRequest(), $context->getRequest() ); - $this->assertSame( $derived->getResourceLoader(), $context->getResourceLoader() ); - } -} diff --git a/tests/phpunit/includes/resourceloader/MessageBlobStoreTest.php b/tests/phpunit/includes/resourceloader/MessageBlobStoreTest.php deleted file mode 100644 index e094d92b9d..0000000000 --- a/tests/phpunit/includes/resourceloader/MessageBlobStoreTest.php +++ /dev/null @@ -1,197 +0,0 @@ -wanCache = new WANObjectCache( [ - 'cache' => new HashBagOStuff() - ] ); - - $this->clock = 1301655600.000; - $this->wanCache->setMockTime( $this->clock ); - } - - public function testBlobCreation() { - $module = $this->makeModule( [ 'mainpage' ] ); - $rl = new EmptyResourceLoader(); - $rl->register( $module->getName(), $module ); - - $blobStore = $this->makeBlobStore( null, $rl ); - $blob = $blobStore->getBlob( $module, 'en' ); - - $this->assertEquals( '{"mainpage":"Main Page"}', $blob, 'Generated blob' ); - } - - public function testBlobCreation_empty() { - $module = $this->makeModule( [] ); - $rl = new EmptyResourceLoader(); - $rl->register( $module->getName(), $module ); - - $blobStore = $this->makeBlobStore( null, $rl ); - $blob = $blobStore->getBlob( $module, 'en' ); - - $this->assertEquals( '{}', $blob, 'Generated blob' ); - } - - public function testBlobCreation_unknownMessage() { - $module = $this->makeModule( [ 'i-dont-exist', 'mainpage', 'i-dont-exist2' ] ); - $rl = new EmptyResourceLoader(); - $rl->register( $module->getName(), $module ); - $blobStore = $this->makeBlobStore( null, $rl ); - - // Generating a blob should continue without errors, - // with keys of unknown messages excluded from the blob. - $blob = $blobStore->getBlob( $module, 'en' ); - $this->assertEquals( '{"mainpage":"Main Page"}', $blob, 'Generated blob' ); - } - - public function testMessageCachingAndPurging() { - $module = $this->makeModule( [ 'example' ] ); - $rl = new EmptyResourceLoader(); - $rl->register( $module->getName(), $module ); - $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl ); - - // Advance this new WANObjectCache instance to a normal state, - // by doing one "get" and letting its hold off period expire. - // Without this, the first real "get" would lazy-initialise the - // checkKey and thus reject the first "set". - $blobStore->getBlob( $module, 'en' ); - $this->clock += 20; - - // Arrange version 1 of a message - $blobStore->expects( $this->once() ) - ->method( 'fetchMessage' ) - ->will( $this->returnValue( 'First version' ) ); - - // Assert - $blob = $blobStore->getBlob( $module, 'en' ); - $this->assertEquals( '{"example":"First version"}', $blob, 'Blob for v1' ); - - // Arrange version 2 - $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl ); - $blobStore->expects( $this->once() ) - ->method( 'fetchMessage' ) - ->will( $this->returnValue( 'Second version' ) ); - $this->clock += 20; - - // Assert - // We do not validate whether a cached message is up-to-date. - // Instead, changes to messages will send us a purge. - // When cache is not purged or expired, it must be used. - $blob = $blobStore->getBlob( $module, 'en' ); - $this->assertEquals( '{"example":"First version"}', $blob, 'Reuse cached v1 blob' ); - - // Purge cache - $blobStore->updateMessage( 'example' ); - $this->clock += 20; - - // Assert - $blob = $blobStore->getBlob( $module, 'en' ); - $this->assertEquals( '{"example":"Second version"}', $blob, 'Updated blob for v2' ); - } - - public function testPurgeEverything() { - $module = $this->makeModule( [ 'example' ] ); - $rl = new EmptyResourceLoader(); - $rl->register( $module->getName(), $module ); - $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl ); - // Advance this new WANObjectCache instance to a normal state. - $blobStore->getBlob( $module, 'en' ); - $this->clock += 20; - - // Arrange version 1 and 2 - $blobStore->expects( $this->exactly( 2 ) ) - ->method( 'fetchMessage' ) - ->will( $this->onConsecutiveCalls( 'First', 'Second' ) ); - - // Assert - $blob = $blobStore->getBlob( $module, 'en' ); - $this->assertEquals( '{"example":"First"}', $blob, 'Blob for v1' ); - - $this->clock += 20; - - // Assert - $blob = $blobStore->getBlob( $module, 'en' ); - $this->assertEquals( '{"example":"First"}', $blob, 'Blob for v1 again' ); - - // Purge everything - $blobStore->clear(); - $this->clock += 20; - - // Assert - $blob = $blobStore->getBlob( $module, 'en' ); - $this->assertEquals( '{"example":"Second"}', $blob, 'Blob for v2' ); - } - - public function testValidateAgainstModuleRegistry() { - // Arrange version 1 of a module - $module = $this->makeModule( [ 'foo' ] ); - $rl = new EmptyResourceLoader(); - $rl->register( $module->getName(), $module ); - $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl ); - $blobStore->expects( $this->once() ) - ->method( 'fetchMessage' ) - ->will( $this->returnValueMap( [ - // message key, language code, message value - [ 'foo', 'en', 'Hello' ], - ] ) ); - - // Assert - $blob = $blobStore->getBlob( $module, 'en' ); - $this->assertEquals( '{"foo":"Hello"}', $blob, 'Blob for v1' ); - - // Arrange version 2 of module - // While message values may be out of date, the set of messages returned - // must always match the set of message keys required by the module. - // We do not receive purges for this because no messages were changed. - $module = $this->makeModule( [ 'foo', 'bar' ] ); - $rl = new EmptyResourceLoader(); - $rl->register( $module->getName(), $module ); - $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl ); - $blobStore->expects( $this->exactly( 2 ) ) - ->method( 'fetchMessage' ) - ->will( $this->returnValueMap( [ - // message key, language code, message value - [ 'foo', 'en', 'Hello' ], - [ 'bar', 'en', 'World' ], - ] ) ); - - // Assert - $blob = $blobStore->getBlob( $module, 'en' ); - $this->assertEquals( '{"foo":"Hello","bar":"World"}', $blob, 'Blob for v2' ); - } - - public function testSetLoggedIsVoid() { - $blobStore = $this->makeBlobStore(); - $this->assertSame( null, $blobStore->setLogger( new Psr\Log\NullLogger() ) ); - } - - private function makeBlobStore( $methods = null, $rl = null ) { - $blobStore = $this->getMockBuilder( MessageBlobStore::class ) - ->setConstructorArgs( [ $rl ?? $this->createMock( ResourceLoader::class ) ] ) - ->setMethods( $methods ) - ->getMock(); - - $access = TestingAccessWrapper::newFromObject( $blobStore ); - $access->wanCache = $this->wanCache; - return $blobStore; - } - - private function makeModule( array $messages ) { - $module = new ResourceLoaderTestModule( [ 'messages' => $messages ] ); - $module->setName( 'test.blobstore' ); - return $module; - } -} diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderClientHtmlTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderClientHtmlTest.php deleted file mode 100644 index 03a3e24ad3..0000000000 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderClientHtmlTest.php +++ /dev/null @@ -1,434 +0,0 @@ -getResourceLoader()->register( self::makeSampleModules() ); - - $client = new ResourceLoaderClientHtml( $context ); - $client->setModules( [ - 'test', - 'test.private', - 'test.shouldembed.empty', - 'test.shouldembed', - 'test.user', - 'test.unregistered', - ] ); - $client->setModuleStyles( [ - 'test.styles.mixed', - 'test.styles.user.empty', - 'test.styles.private', - 'test.styles.pure', - 'test.styles.shouldembed', - 'test.styles.deprecated', - 'test.unregistered.styles', - ] ); - - $expected = [ - 'states' => [ - // The below are NOT queued for loading via `mw.loader.load(Array)`. - // Instead we tell the client to set their state to "loading" so that - // if they are needed as dependencies, the client will not try to - // load them on-demand, because the server is taking care of them already. - // Either: - // - Embedded as inline scripts in the HTML (e.g. user-private code, and - // previews). Once that script tag is reached, the state is "loaded". - // - Loaded directly from the HTML with a dedicated HTTP request (e.g. - // user scripts, which vary by a 'user' and 'version' parameter that - // the static user-agnostic startup module won't have). - 'test.private' => 'loading', - 'test.shouldembed' => 'loading', - 'test.user' => 'loading', - // The below are known to the server to be empty scripts, or to be - // synchronously loaded stylesheets. These start in the "ready" state. - 'test.shouldembed.empty' => 'ready', - 'test.styles.pure' => 'ready', - 'test.styles.user.empty' => 'ready', - 'test.styles.private' => 'ready', - 'test.styles.shouldembed' => 'ready', - 'test.styles.deprecated' => 'ready', - ], - 'general' => [ - 'test', - ], - 'styles' => [ - 'test.styles.pure', - 'test.styles.deprecated', - ], - 'embed' => [ - 'styles' => [ 'test.styles.private', 'test.styles.shouldembed' ], - 'general' => [ - 'test.private', - 'test.shouldembed', - 'test.user', - ], - ], - 'styleDeprecations' => [ - Xml::encodeJsCall( - 'mw.log.warn', - [ 'This page is using the deprecated ResourceLoader module "test.styles.deprecated". -Deprecation message.' ] - ) - ], - ]; - - $access = TestingAccessWrapper::newFromObject( $client ); - $this->assertEquals( $expected, $access->getData() ); - } - - public function testGetHeadHtml() { - $context = self::makeContext(); - $context->getResourceLoader()->register( self::makeSampleModules() ); - - $client = new ResourceLoaderClientHtml( $context, [ - 'nonce' => false, - ] ); - $client->setConfig( [ 'key' => 'value' ] ); - $client->setModules( [ - 'test', - 'test.private', - ] ); - $client->setModuleStyles( [ - 'test.styles.pure', - 'test.styles.private', - 'test.styles.deprecated', - ] ); - $client->setExemptStates( [ - 'test.exempt' => 'ready', - ] ); - - // phpcs:disable Generic.Files.LineLength - $expected = '' . "\n" - . '' . "\n" - . '' . "\n" - . '' . "\n" - . ''; - // phpcs:enable - $expected = self::expandVariables( $expected ); - - $this->assertSame( $expected, (string)$client->getHeadHtml() ); - } - - /** - * Confirm that 'target' is passed down to the startup module's load url. - */ - public function testGetHeadHtmlWithTarget() { - $client = new ResourceLoaderClientHtml( - self::makeContext(), - [ 'target' => 'example' ] - ); - - // phpcs:disable Generic.Files.LineLength - $expected = '' . "\n" - . ''; - // phpcs:enable - - $this->assertSame( $expected, (string)$client->getHeadHtml() ); - } - - /** - * Confirm that 'safemode' is passed down to startup. - */ - public function testGetHeadHtmlWithSafemode() { - $client = new ResourceLoaderClientHtml( - self::makeContext(), - [ 'safemode' => '1' ] - ); - - // phpcs:disable Generic.Files.LineLength - $expected = '' . "\n" - . ''; - // phpcs:enable - - $this->assertSame( $expected, (string)$client->getHeadHtml() ); - } - - /** - * Confirm that a null 'target' is the same as no target. - */ - public function testGetHeadHtmlWithNullTarget() { - $client = new ResourceLoaderClientHtml( - self::makeContext(), - [ 'target' => null ] - ); - - // phpcs:disable Generic.Files.LineLength - $expected = '' . "\n" - . ''; - // phpcs:enable - - $this->assertSame( $expected, (string)$client->getHeadHtml() ); - } - - public function testGetBodyHtml() { - $context = self::makeContext(); - $context->getResourceLoader()->register( self::makeSampleModules() ); - - $client = new ResourceLoaderClientHtml( $context, [ 'nonce' => false ] ); - $client->setConfig( [ 'key' => 'value' ] ); - $client->setModules( [ - 'test', - 'test.private.bottom', - ] ); - $client->setModuleStyles( [ - 'test.styles.deprecated', - ] ); - // phpcs:disable Generic.Files.LineLength - $expected = ''; - // phpcs:enable - - $this->assertSame( $expected, (string)$client->getBodyHtml() ); - } - - public static function provideMakeLoad() { - // phpcs:disable Generic.Files.LineLength - return [ - [ - 'context' => [], - 'modules' => [ 'test.unknown' ], - 'only' => ResourceLoaderModule::TYPE_STYLES, - 'extra' => [], - 'output' => '', - ], - [ - 'context' => [], - 'modules' => [ 'test.styles.private' ], - 'only' => ResourceLoaderModule::TYPE_STYLES, - 'extra' => [], - 'output' => '', - ], - [ - 'context' => [], - 'modules' => [ 'test.private' ], - 'only' => ResourceLoaderModule::TYPE_COMBINED, - 'extra' => [], - 'output' => '', - ], - [ - 'context' => [], - // Eg. startup module - 'modules' => [ 'test.scripts.raw' ], - 'only' => ResourceLoaderModule::TYPE_SCRIPTS, - 'extra' => [], - 'output' => '', - ], - [ - 'context' => [], - 'modules' => [ 'test.scripts.raw' ], - 'only' => ResourceLoaderModule::TYPE_SCRIPTS, - 'extra' => [ 'sync' => '1' ], - 'output' => '', - ], - [ - 'context' => [], - 'modules' => [ 'test.scripts.user' ], - 'only' => ResourceLoaderModule::TYPE_SCRIPTS, - 'extra' => [], - 'output' => '', - ], - [ - 'context' => [], - 'modules' => [ 'test.user' ], - 'only' => ResourceLoaderModule::TYPE_COMBINED, - 'extra' => [], - 'output' => '', - ], - [ - 'context' => [ 'debug' => 'true' ], - 'modules' => [ 'test.styles.pure', 'test.styles.mixed' ], - 'only' => ResourceLoaderModule::TYPE_STYLES, - 'extra' => [], - 'output' => '' . "\n" - . '', - ], - [ - 'context' => [ 'debug' => 'false' ], - 'modules' => [ 'test.styles.pure', 'test.styles.mixed' ], - 'only' => ResourceLoaderModule::TYPE_STYLES, - 'extra' => [], - 'output' => '', - ], - [ - 'context' => [], - 'modules' => [ 'test.styles.noscript' ], - 'only' => ResourceLoaderModule::TYPE_STYLES, - 'extra' => [], - 'output' => '', - ], - [ - 'context' => [], - 'modules' => [ 'test.shouldembed' ], - 'only' => ResourceLoaderModule::TYPE_COMBINED, - 'extra' => [], - 'output' => '', - ], - [ - 'context' => [], - 'modules' => [ 'test.styles.shouldembed' ], - 'only' => ResourceLoaderModule::TYPE_STYLES, - 'extra' => [], - 'output' => '', - ], - [ - 'context' => [], - 'modules' => [ 'test.scripts.shouldembed' ], - 'only' => ResourceLoaderModule::TYPE_SCRIPTS, - 'extra' => [], - 'output' => '', - ], - [ - 'context' => [], - 'modules' => [ 'test', 'test.shouldembed' ], - 'only' => ResourceLoaderModule::TYPE_COMBINED, - 'extra' => [], - 'output' => '', - ], - [ - 'context' => [], - 'modules' => [ 'test.styles.pure', 'test.styles.shouldembed' ], - 'only' => ResourceLoaderModule::TYPE_STYLES, - 'extra' => [], - 'output' => - '' . "\n" - . '' - ], - [ - 'context' => [], - 'modules' => [ 'test.ordering.a', 'test.ordering.e', 'test.ordering.b', 'test.ordering.d', 'test.ordering.c' ], - 'only' => ResourceLoaderModule::TYPE_STYLES, - 'extra' => [], - 'output' => - '' . "\n" - . '' . "\n" - . '' - ], - ]; - // phpcs:enable - } - - /** - * @dataProvider provideMakeLoad - * @covers ResourceLoaderClientHtml - * @covers ResourceLoaderModule::getModuleContent - * @covers ResourceLoader - */ - public function testMakeLoad( - array $contextQuery, - array $modules, - $type, - array $extraQuery, - $expected - ) { - $context = self::makeContext( $contextQuery ); - $context->getResourceLoader()->register( self::makeSampleModules() ); - $actual = ResourceLoaderClientHtml::makeLoad( $context, $modules, $type, $extraQuery, false ); - $expected = self::expandVariables( $expected ); - $this->assertSame( $expected, (string)$actual ); - } - - public function testGetDocumentAttributes() { - $client = new ResourceLoaderClientHtml( self::makeContext() ); - $this->assertInternalType( 'array', $client->getDocumentAttributes() ); - } - - private static function expandVariables( $text ) { - return strtr( $text, [ - '{blankVer}' => ResourceLoaderTestCase::BLANK_VERSION - ] ); - } - - private static function makeContext( $extraQuery = [] ) { - $conf = new HashConfig( [ - 'ResourceModuleSkinStyles' => [], - 'ResourceModules' => [], - 'EnableJavaScriptTest' => false, - 'LoadScript' => '/w/load.php', - ] ); - return new ResourceLoaderContext( - new ResourceLoader( $conf ), - new FauxRequest( array_merge( [ - 'lang' => 'nl', - 'skin' => 'fallback', - 'user' => 'Example', - 'target' => 'phpunit', - ], $extraQuery ) ) - ); - } - - private static function makeModule( array $options = [] ) { - return new ResourceLoaderTestModule( $options ); - } - - private static function makeSampleModules() { - $modules = [ - 'test' => [], - 'test.private' => [ 'group' => 'private' ], - 'test.shouldembed.empty' => [ 'shouldEmbed' => true, 'isKnownEmpty' => true ], - 'test.shouldembed' => [ 'shouldEmbed' => true ], - 'test.user' => [ 'group' => 'user' ], - - 'test.styles.pure' => [ 'type' => ResourceLoaderModule::LOAD_STYLES ], - 'test.styles.mixed' => [], - 'test.styles.noscript' => [ - 'type' => ResourceLoaderModule::LOAD_STYLES, - 'group' => 'noscript', - ], - 'test.styles.user' => [ - 'type' => ResourceLoaderModule::LOAD_STYLES, - 'group' => 'user', - ], - 'test.styles.user.empty' => [ - 'type' => ResourceLoaderModule::LOAD_STYLES, - 'group' => 'user', - 'isKnownEmpty' => true, - ], - 'test.styles.private' => [ - 'type' => ResourceLoaderModule::LOAD_STYLES, - 'group' => 'private', - 'styles' => '.private{}', - ], - 'test.styles.shouldembed' => [ - 'type' => ResourceLoaderModule::LOAD_STYLES, - 'shouldEmbed' => true, - 'styles' => '.shouldembed{}', - ], - 'test.styles.deprecated' => [ - 'type' => ResourceLoaderModule::LOAD_STYLES, - 'deprecated' => 'Deprecation message.', - ], - - 'test.scripts' => [], - 'test.scripts.user' => [ 'group' => 'user' ], - 'test.scripts.user.empty' => [ 'group' => 'user', 'isKnownEmpty' => true ], - 'test.scripts.raw' => [ 'isRaw' => true ], - 'test.scripts.shouldembed' => [ 'shouldEmbed' => true ], - - 'test.ordering.a' => [ 'shouldEmbed' => false ], - 'test.ordering.b' => [ 'shouldEmbed' => false ], - 'test.ordering.c' => [ 'shouldEmbed' => true, 'styles' => '.orderingC{}' ], - 'test.ordering.d' => [ 'shouldEmbed' => true, 'styles' => '.orderingD{}' ], - 'test.ordering.e' => [ 'shouldEmbed' => false ], - ]; - return array_map( function ( $options ) { - return self::makeModule( $options ); - }, $modules ); - } -} diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderContextTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderContextTest.php deleted file mode 100644 index 2ec8ea987a..0000000000 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderContextTest.php +++ /dev/null @@ -1,122 +0,0 @@ - false, - 'LoadScript' => '/w/load.php', - // For ResourceLoader::register() - 'ResourceModuleSkinStyles' => [], - ] ) ); - } - - public function testEmpty() { - $ctx = new ResourceLoaderContext( $this->getResourceLoader(), new FauxRequest( [] ) ); - - // Request parameters - $this->assertEquals( [], $ctx->getModules() ); - $this->assertEquals( 'qqx', $ctx->getLanguage() ); - $this->assertEquals( false, $ctx->getDebug() ); - $this->assertEquals( null, $ctx->getOnly() ); - $this->assertEquals( 'fallback', $ctx->getSkin() ); - $this->assertEquals( null, $ctx->getUser() ); - $this->assertNull( $ctx->getContentOverrideCallback() ); - - // Misc - $this->assertEquals( 'ltr', $ctx->getDirection() ); - $this->assertEquals( 'qqx|fallback||||||||', $ctx->getHash() ); - $this->assertInstanceOf( User::class, $ctx->getUserObj() ); - } - - public function testDummy() { - $this->assertInstanceOf( - ResourceLoaderContext::class, - ResourceLoaderContext::newDummyContext() - ); - } - - public function testAccessors() { - $ctx = new ResourceLoaderContext( $this->getResourceLoader(), new FauxRequest( [] ) ); - $this->assertInstanceOf( ResourceLoader::class, $ctx->getResourceLoader() ); - $this->assertInstanceOf( Config::class, $ctx->getConfig() ); - $this->assertInstanceOf( WebRequest::class, $ctx->getRequest() ); - $this->assertInstanceOf( Psr\Log\LoggerInterface::class, $ctx->getLogger() ); - } - - public function testTypicalRequest() { - $ctx = new ResourceLoaderContext( $this->getResourceLoader(), new FauxRequest( [ - 'debug' => 'false', - 'lang' => 'zh', - 'modules' => 'foo|foo.quux,baz,bar|baz.quux', - 'only' => 'styles', - 'skin' => 'fallback', - ] ) ); - - // Request parameters - $this->assertEquals( - $ctx->getModules(), - [ 'foo', 'foo.quux', 'foo.baz', 'foo.bar', 'baz.quux' ] - ); - $this->assertEquals( false, $ctx->getDebug() ); - $this->assertEquals( 'zh', $ctx->getLanguage() ); - $this->assertEquals( 'styles', $ctx->getOnly() ); - $this->assertEquals( 'fallback', $ctx->getSkin() ); - $this->assertEquals( null, $ctx->getUser() ); - - // Misc - $this->assertEquals( 'ltr', $ctx->getDirection() ); - $this->assertEquals( 'zh|fallback|||styles|||||', $ctx->getHash() ); - } - - public function testShouldInclude() { - $ctx = new ResourceLoaderContext( $this->getResourceLoader(), new FauxRequest( [] ) ); - $this->assertTrue( $ctx->shouldIncludeScripts(), 'Scripts in combined' ); - $this->assertTrue( $ctx->shouldIncludeStyles(), 'Styles in combined' ); - $this->assertTrue( $ctx->shouldIncludeMessages(), 'Messages in combined' ); - - $ctx = new ResourceLoaderContext( $this->getResourceLoader(), new FauxRequest( [ - 'only' => 'styles' - ] ) ); - $this->assertFalse( $ctx->shouldIncludeScripts(), 'Scripts not in styles-only' ); - $this->assertTrue( $ctx->shouldIncludeStyles(), 'Styles in styles-only' ); - $this->assertFalse( $ctx->shouldIncludeMessages(), 'Messages not in styles-only' ); - - $ctx = new ResourceLoaderContext( $this->getResourceLoader(), new FauxRequest( [ - 'only' => 'scripts' - ] ) ); - $this->assertTrue( $ctx->shouldIncludeScripts(), 'Scripts in scripts-only' ); - $this->assertFalse( $ctx->shouldIncludeStyles(), 'Styles not in scripts-only' ); - $this->assertFalse( $ctx->shouldIncludeMessages(), 'Messages not in scripts-only' ); - } - - public function testGetUser() { - $ctx = new ResourceLoaderContext( $this->getResourceLoader(), new FauxRequest( [] ) ); - $this->assertSame( null, $ctx->getUser() ); - $this->assertTrue( $ctx->getUserObj()->isAnon() ); - - $ctx = new ResourceLoaderContext( $this->getResourceLoader(), new FauxRequest( [ - 'user' => 'Example' - ] ) ); - $this->assertSame( 'Example', $ctx->getUser() ); - $this->assertEquals( 'Example', $ctx->getUserObj()->getName() ); - } - - public function testMsg() { - $ctx = new ResourceLoaderContext( $this->getResourceLoader(), new FauxRequest( [ - 'lang' => 'en' - ] ) ); - $msg = $ctx->msg( 'mainpage' ); - $this->assertInstanceOf( Message::class, $msg ); - $this->assertSame( 'Main Page', $msg->useDatabase( false )->plain() ); - } -} diff --git a/tests/phpunit/includes/search/SearchIndexFieldTest.php b/tests/phpunit/includes/search/SearchIndexFieldTest.php deleted file mode 100644 index 8b4119e0d3..0000000000 --- a/tests/phpunit/includes/search/SearchIndexFieldTest.php +++ /dev/null @@ -1,56 +0,0 @@ -getMockBuilder( SearchIndexFieldDefinition::class ) - ->setMethods( [ 'getMapping' ] ) - ->setConstructorArgs( [ $n1, $t1 ] ) - ->getMock(); - $field2 = - $this->getMockBuilder( SearchIndexFieldDefinition::class ) - ->setMethods( [ 'getMapping' ] ) - ->setConstructorArgs( [ $n2, $t2 ] ) - ->getMock(); - - if ( $result ) { - $this->assertNotFalse( $field1->merge( $field2 ) ); - } else { - $this->assertFalse( $field1->merge( $field2 ) ); - } - - $field1->setFlag( 0xFF ); - $this->assertFalse( $field1->merge( $field2 ) ); - - $field1->setMergeCallback( - function ( $a, $b ) { - return "test"; - } - ); - $this->assertEquals( "test", $field1->merge( $field2 ) ); - } - -} diff --git a/tests/phpunit/includes/search/SearchSuggestionSetTest.php b/tests/phpunit/includes/search/SearchSuggestionSetTest.php deleted file mode 100644 index 02fa5e9cac..0000000000 --- a/tests/phpunit/includes/search/SearchSuggestionSetTest.php +++ /dev/null @@ -1,111 +0,0 @@ -assertEquals( 0, $set->getSize() ); - $set->append( new SearchSuggestion( 3 ) ); - $this->assertEquals( 3, $set->getWorstScore() ); - $this->assertEquals( 3, $set->getBestScore() ); - - $suggestion = new SearchSuggestion( 4 ); - $set->append( $suggestion ); - $this->assertEquals( 2, $set->getWorstScore() ); - $this->assertEquals( 3, $set->getBestScore() ); - $this->assertEquals( 2, $suggestion->getScore() ); - - $suggestion = new SearchSuggestion( 2 ); - $set->append( $suggestion ); - $this->assertEquals( 1, $set->getWorstScore() ); - $this->assertEquals( 3, $set->getBestScore() ); - $this->assertEquals( 1, $suggestion->getScore() ); - - $scores = $set->map( function ( $s ) { - return $s->getScore(); - } ); - $sorted = $scores; - asort( $sorted ); - $this->assertEquals( $sorted, $scores ); - } - - /** - * Test that adding a new best suggestion will keep proper score - * ordering - * @covers SearchSuggestionSet::getWorstScore - * @covers SearchSuggestionSet::getBestScore - * @covers SearchSuggestionSet::prepend - */ - public function testInsertBest() { - $set = SearchSuggestionSet::emptySuggestionSet(); - $this->assertEquals( 0, $set->getSize() ); - $set->prepend( new SearchSuggestion( 3 ) ); - $this->assertEquals( 3, $set->getWorstScore() ); - $this->assertEquals( 3, $set->getBestScore() ); - - $suggestion = new SearchSuggestion( 4 ); - $set->prepend( $suggestion ); - $this->assertEquals( 3, $set->getWorstScore() ); - $this->assertEquals( 4, $set->getBestScore() ); - $this->assertEquals( 4, $suggestion->getScore() ); - - $suggestion = new SearchSuggestion( 0 ); - $set->prepend( $suggestion ); - $this->assertEquals( 3, $set->getWorstScore() ); - $this->assertEquals( 5, $set->getBestScore() ); - $this->assertEquals( 5, $suggestion->getScore() ); - - $suggestion = new SearchSuggestion( 2 ); - $set->prepend( $suggestion ); - $this->assertEquals( 3, $set->getWorstScore() ); - $this->assertEquals( 6, $set->getBestScore() ); - $this->assertEquals( 6, $suggestion->getScore() ); - - $scores = $set->map( function ( $s ) { - return $s->getScore(); - } ); - $sorted = $scores; - asort( $sorted ); - $this->assertEquals( $sorted, $scores ); - } - - /** - * @covers SearchSuggestionSet::shrink - */ - public function testShrink() { - $set = SearchSuggestionSet::emptySuggestionSet(); - for ( $i = 0; $i < 100; $i++ ) { - $set->append( new SearchSuggestion( 0 ) ); - } - $set->shrink( 10 ); - $this->assertEquals( 10, $set->getSize() ); - - $set->shrink( 0 ); - $this->assertEquals( 0, $set->getSize() ); - } - - // TODO: test for fromTitles -} diff --git a/tests/phpunit/includes/session/MetadataMergeExceptionTest.php b/tests/phpunit/includes/session/MetadataMergeExceptionTest.php deleted file mode 100644 index 8cb4302a4e..0000000000 --- a/tests/phpunit/includes/session/MetadataMergeExceptionTest.php +++ /dev/null @@ -1,30 +0,0 @@ - 'bar' ]; - - $ex = new MetadataMergeException(); - $this->assertInstanceOf( \UnexpectedValueException::class, $ex ); - $this->assertSame( [], $ex->getContext() ); - - $ex2 = new MetadataMergeException( 'Message', 42, $ex, $data ); - $this->assertSame( 'Message', $ex2->getMessage() ); - $this->assertSame( 42, $ex2->getCode() ); - $this->assertSame( $ex, $ex2->getPrevious() ); - $this->assertSame( $data, $ex2->getContext() ); - - $ex->setContext( $data ); - $this->assertSame( $data, $ex->getContext() ); - } - -} diff --git a/tests/phpunit/includes/session/SessionIdTest.php b/tests/phpunit/includes/session/SessionIdTest.php deleted file mode 100644 index 2b06d971a6..0000000000 --- a/tests/phpunit/includes/session/SessionIdTest.php +++ /dev/null @@ -1,22 +0,0 @@ -assertSame( 'foo', $id->getId() ); - $this->assertSame( 'foo', (string)$id ); - $id->setId( 'bar' ); - $this->assertSame( 'bar', $id->getId() ); - $this->assertSame( 'bar', (string)$id ); - } - -} diff --git a/tests/phpunit/includes/session/SessionInfoTest.php b/tests/phpunit/includes/session/SessionInfoTest.php deleted file mode 100644 index 8f7b2a6e70..0000000000 --- a/tests/phpunit/includes/session/SessionInfoTest.php +++ /dev/null @@ -1,356 +0,0 @@ -fail( 'Expected exception not thrown', 'priority < min' ); - } catch ( \InvalidArgumentException $ex ) { - $this->assertSame( 'Invalid priority', $ex->getMessage(), 'priority < min' ); - } - - try { - new SessionInfo( SessionInfo::MAX_PRIORITY + 1, [] ); - $this->fail( 'Expected exception not thrown', 'priority > max' ); - } catch ( \InvalidArgumentException $ex ) { - $this->assertSame( 'Invalid priority', $ex->getMessage(), 'priority > max' ); - } - - try { - new SessionInfo( SessionInfo::MIN_PRIORITY, [ 'id' => 'ABC?' ] ); - $this->fail( 'Expected exception not thrown', 'bad session ID' ); - } catch ( \InvalidArgumentException $ex ) { - $this->assertSame( 'Invalid session ID', $ex->getMessage(), 'bad session ID' ); - } - - try { - new SessionInfo( SessionInfo::MIN_PRIORITY, [ 'userInfo' => new \stdClass ] ); - $this->fail( 'Expected exception not thrown', 'bad userInfo' ); - } catch ( \InvalidArgumentException $ex ) { - $this->assertSame( 'Invalid userInfo', $ex->getMessage(), 'bad userInfo' ); - } - - try { - new SessionInfo( SessionInfo::MIN_PRIORITY, [] ); - $this->fail( 'Expected exception not thrown', 'no provider, no id' ); - } catch ( \InvalidArgumentException $ex ) { - $this->assertSame( 'Must supply an ID when no provider is given', $ex->getMessage(), - 'no provider, no id' ); - } - - try { - new SessionInfo( SessionInfo::MIN_PRIORITY, [ 'copyFrom' => new \stdClass ] ); - $this->fail( 'Expected exception not thrown', 'bad copyFrom' ); - } catch ( \InvalidArgumentException $ex ) { - $this->assertSame( 'Invalid copyFrom', $ex->getMessage(), - 'bad copyFrom' ); - } - - $manager = new SessionManager(); - $provider = $this->getMockBuilder( SessionProvider::class ) - ->setMethods( [ 'persistsSessionId', 'canChangeUser', '__toString' ] ) - ->getMockForAbstractClass(); - $provider->setManager( $manager ); - $provider->expects( $this->any() )->method( 'persistsSessionId' ) - ->will( $this->returnValue( true ) ); - $provider->expects( $this->any() )->method( 'canChangeUser' ) - ->will( $this->returnValue( true ) ); - $provider->expects( $this->any() )->method( '__toString' ) - ->will( $this->returnValue( 'Mock' ) ); - - $provider2 = $this->getMockBuilder( SessionProvider::class ) - ->setMethods( [ 'persistsSessionId', 'canChangeUser', '__toString' ] ) - ->getMockForAbstractClass(); - $provider2->setManager( $manager ); - $provider2->expects( $this->any() )->method( 'persistsSessionId' ) - ->will( $this->returnValue( true ) ); - $provider2->expects( $this->any() )->method( 'canChangeUser' ) - ->will( $this->returnValue( true ) ); - $provider2->expects( $this->any() )->method( '__toString' ) - ->will( $this->returnValue( 'Mock2' ) ); - - try { - new SessionInfo( SessionInfo::MIN_PRIORITY, [ - 'provider' => $provider, - 'userInfo' => $anonInfo, - 'metadata' => 'foo', - ] ); - $this->fail( 'Expected exception not thrown', 'bad metadata' ); - } catch ( \InvalidArgumentException $ex ) { - $this->assertSame( 'Invalid metadata', $ex->getMessage(), 'bad metadata' ); - } - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [ - 'provider' => $provider, - 'userInfo' => $anonInfo - ] ); - $this->assertSame( $provider, $info->getProvider() ); - $this->assertNotNull( $info->getId() ); - $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() ); - $this->assertSame( $anonInfo, $info->getUserInfo() ); - $this->assertTrue( $info->isIdSafe() ); - $this->assertFalse( $info->forceUse() ); - $this->assertFalse( $info->wasPersisted() ); - $this->assertFalse( $info->wasRemembered() ); - $this->assertFalse( $info->forceHTTPS() ); - $this->assertNull( $info->getProviderMetadata() ); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [ - 'provider' => $provider, - 'userInfo' => $unverifiedUserInfo, - 'metadata' => [ 'Foo' ], - ] ); - $this->assertSame( $provider, $info->getProvider() ); - $this->assertNotNull( $info->getId() ); - $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() ); - $this->assertSame( $unverifiedUserInfo, $info->getUserInfo() ); - $this->assertTrue( $info->isIdSafe() ); - $this->assertFalse( $info->forceUse() ); - $this->assertFalse( $info->wasPersisted() ); - $this->assertFalse( $info->wasRemembered() ); - $this->assertFalse( $info->forceHTTPS() ); - $this->assertSame( [ 'Foo' ], $info->getProviderMetadata() ); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [ - 'provider' => $provider, - 'userInfo' => $userInfo - ] ); - $this->assertSame( $provider, $info->getProvider() ); - $this->assertNotNull( $info->getId() ); - $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() ); - $this->assertSame( $userInfo, $info->getUserInfo() ); - $this->assertTrue( $info->isIdSafe() ); - $this->assertFalse( $info->forceUse() ); - $this->assertFalse( $info->wasPersisted() ); - $this->assertTrue( $info->wasRemembered() ); - $this->assertFalse( $info->forceHTTPS() ); - $this->assertNull( $info->getProviderMetadata() ); - - $id = $manager->generateSessionId(); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [ - 'provider' => $provider, - 'id' => $id, - 'persisted' => true, - 'userInfo' => $anonInfo - ] ); - $this->assertSame( $provider, $info->getProvider() ); - $this->assertSame( $id, $info->getId() ); - $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() ); - $this->assertSame( $anonInfo, $info->getUserInfo() ); - $this->assertFalse( $info->isIdSafe() ); - $this->assertFalse( $info->forceUse() ); - $this->assertTrue( $info->wasPersisted() ); - $this->assertFalse( $info->wasRemembered() ); - $this->assertFalse( $info->forceHTTPS() ); - $this->assertNull( $info->getProviderMetadata() ); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [ - 'provider' => $provider, - 'id' => $id, - 'userInfo' => $userInfo - ] ); - $this->assertSame( $provider, $info->getProvider() ); - $this->assertSame( $id, $info->getId() ); - $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() ); - $this->assertSame( $userInfo, $info->getUserInfo() ); - $this->assertFalse( $info->isIdSafe() ); - $this->assertFalse( $info->forceUse() ); - $this->assertFalse( $info->wasPersisted() ); - $this->assertTrue( $info->wasRemembered() ); - $this->assertFalse( $info->forceHTTPS() ); - $this->assertNull( $info->getProviderMetadata() ); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [ - 'id' => $id, - 'persisted' => true, - 'userInfo' => $userInfo, - 'metadata' => [ 'Foo' ], - ] ); - $this->assertSame( $id, $info->getId() ); - $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() ); - $this->assertSame( $userInfo, $info->getUserInfo() ); - $this->assertFalse( $info->isIdSafe() ); - $this->assertFalse( $info->forceUse() ); - $this->assertTrue( $info->wasPersisted() ); - $this->assertFalse( $info->wasRemembered() ); - $this->assertFalse( $info->forceHTTPS() ); - $this->assertNull( $info->getProviderMetadata() ); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [ - 'id' => $id, - 'remembered' => true, - 'userInfo' => $userInfo, - ] ); - $this->assertFalse( $info->wasRemembered(), 'no provider' ); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [ - 'provider' => $provider, - 'id' => $id, - 'remembered' => true, - ] ); - $this->assertFalse( $info->wasRemembered(), 'no user' ); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [ - 'provider' => $provider, - 'id' => $id, - 'remembered' => true, - 'userInfo' => $anonInfo, - ] ); - $this->assertFalse( $info->wasRemembered(), 'anonymous user' ); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [ - 'provider' => $provider, - 'id' => $id, - 'remembered' => true, - 'userInfo' => $unverifiedUserInfo, - ] ); - $this->assertFalse( $info->wasRemembered(), 'unverified user' ); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [ - 'provider' => $provider, - 'id' => $id, - 'remembered' => false, - 'userInfo' => $userInfo, - ] ); - $this->assertFalse( $info->wasRemembered(), 'specific override' ); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [ - 'id' => $id, - 'idIsSafe' => true, - ] ); - $this->assertSame( $id, $info->getId() ); - $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() ); - $this->assertTrue( $info->isIdSafe() ); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [ - 'id' => $id, - 'forceUse' => true, - ] ); - $this->assertFalse( $info->forceUse(), 'no provider' ); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [ - 'provider' => $provider, - 'forceUse' => true, - ] ); - $this->assertFalse( $info->forceUse(), 'no id' ); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [ - 'provider' => $provider, - 'id' => $id, - 'forceUse' => true, - ] ); - $this->assertTrue( $info->forceUse(), 'correct use' ); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [ - 'id' => $id, - 'forceHTTPS' => 1, - ] ); - $this->assertTrue( $info->forceHTTPS() ); - - $fromInfo = new SessionInfo( SessionInfo::MIN_PRIORITY, [ - 'id' => $id . 'A', - 'provider' => $provider, - 'userInfo' => $userInfo, - 'idIsSafe' => true, - 'forceUse' => true, - 'persisted' => true, - 'remembered' => true, - 'forceHTTPS' => true, - 'metadata' => [ 'foo!' ], - ] ); - $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 4, [ - 'copyFrom' => $fromInfo, - ] ); - $this->assertSame( $id . 'A', $info->getId() ); - $this->assertSame( SessionInfo::MIN_PRIORITY + 4, $info->getPriority() ); - $this->assertSame( $provider, $info->getProvider() ); - $this->assertSame( $userInfo, $info->getUserInfo() ); - $this->assertTrue( $info->isIdSafe() ); - $this->assertTrue( $info->forceUse() ); - $this->assertTrue( $info->wasPersisted() ); - $this->assertTrue( $info->wasRemembered() ); - $this->assertTrue( $info->forceHTTPS() ); - $this->assertSame( [ 'foo!' ], $info->getProviderMetadata() ); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 4, [ - 'id' => $id . 'X', - 'provider' => $provider2, - 'userInfo' => $unverifiedUserInfo, - 'idIsSafe' => false, - 'forceUse' => false, - 'persisted' => false, - 'remembered' => false, - 'forceHTTPS' => false, - 'metadata' => null, - 'copyFrom' => $fromInfo, - ] ); - $this->assertSame( $id . 'X', $info->getId() ); - $this->assertSame( SessionInfo::MIN_PRIORITY + 4, $info->getPriority() ); - $this->assertSame( $provider2, $info->getProvider() ); - $this->assertSame( $unverifiedUserInfo, $info->getUserInfo() ); - $this->assertFalse( $info->isIdSafe() ); - $this->assertFalse( $info->forceUse() ); - $this->assertFalse( $info->wasPersisted() ); - $this->assertFalse( $info->wasRemembered() ); - $this->assertFalse( $info->forceHTTPS() ); - $this->assertNull( $info->getProviderMetadata() ); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [ - 'id' => $id, - ] ); - $this->assertSame( - '[' . SessionInfo::MIN_PRIORITY . "]null$id", - (string)$info, - 'toString' - ); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [ - 'provider' => $provider, - 'id' => $id, - 'persisted' => true, - 'userInfo' => $userInfo - ] ); - $this->assertSame( - '[' . SessionInfo::MIN_PRIORITY . "]Mock<+:{$userInfo->getId()}:UTSysop>$id", - (string)$info, - 'toString' - ); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [ - 'provider' => $provider, - 'id' => $id, - 'persisted' => true, - 'userInfo' => $unverifiedUserInfo - ] ); - $this->assertSame( - '[' . SessionInfo::MIN_PRIORITY . "]Mock<-:{$userInfo->getId()}:UTSysop>$id", - (string)$info, - 'toString' - ); - } - - public function testCompare() { - $id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; - $info1 = new SessionInfo( SessionInfo::MIN_PRIORITY + 1, [ 'id' => $id ] ); - $info2 = new SessionInfo( SessionInfo::MIN_PRIORITY + 2, [ 'id' => $id ] ); - - $this->assertTrue( SessionInfo::compare( $info1, $info2 ) < 0, '<' ); - $this->assertTrue( SessionInfo::compare( $info2, $info1 ) > 0, '>' ); - $this->assertTrue( SessionInfo::compare( $info1, $info1 ) === 0, '==' ); - } -} diff --git a/tests/phpunit/includes/session/SessionProviderTest.php b/tests/phpunit/includes/session/SessionProviderTest.php deleted file mode 100644 index 6ff6a97b8f..0000000000 --- a/tests/phpunit/includes/session/SessionProviderTest.php +++ /dev/null @@ -1,206 +0,0 @@ -getMockForAbstractClass( SessionProvider::class ); - $priv = TestingAccessWrapper::newFromObject( $provider ); - - $provider->setConfig( $config ); - $this->assertSame( $config, $priv->config ); - $provider->setLogger( $logger ); - $this->assertSame( $logger, $priv->logger ); - $provider->setManager( $manager ); - $this->assertSame( $manager, $priv->manager ); - $this->assertSame( $manager, $provider->getManager() ); - - $provider->invalidateSessionsForUser( new \User ); - - $this->assertSame( [], $provider->getVaryHeaders() ); - $this->assertSame( [], $provider->getVaryCookies() ); - $this->assertSame( null, $provider->suggestLoginUsername( new \FauxRequest ) ); - - $this->assertSame( get_class( $provider ), (string)$provider ); - - $this->assertNull( $provider->getRememberUserDuration() ); - - $this->assertNull( $provider->whyNoSession() ); - - $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [ - 'id' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - 'provider' => $provider, - ] ); - $metadata = [ 'foo' ]; - $this->assertTrue( $provider->refreshSessionInfo( $info, new \FauxRequest, $metadata ) ); - $this->assertSame( [ 'foo' ], $metadata ); - } - - /** - * @dataProvider provideNewSessionInfo - * @param bool $persistId Return value for ->persistsSessionId() - * @param bool $persistUser Return value for ->persistsSessionUser() - * @param bool $ok Whether a SessionInfo is provided - */ - public function testNewSessionInfo( $persistId, $persistUser, $ok ) { - $manager = new SessionManager(); - - $provider = $this->getMockBuilder( SessionProvider::class ) - ->setMethods( [ 'canChangeUser', 'persistsSessionId' ] ) - ->getMockForAbstractClass(); - $provider->expects( $this->any() )->method( 'persistsSessionId' ) - ->will( $this->returnValue( $persistId ) ); - $provider->expects( $this->any() )->method( 'canChangeUser' ) - ->will( $this->returnValue( $persistUser ) ); - $provider->setManager( $manager ); - - if ( $ok ) { - $info = $provider->newSessionInfo(); - $this->assertNotNull( $info ); - $this->assertFalse( $info->wasPersisted() ); - $this->assertTrue( $info->isIdSafe() ); - - $id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; - $info = $provider->newSessionInfo( $id ); - $this->assertNotNull( $info ); - $this->assertSame( $id, $info->getId() ); - $this->assertFalse( $info->wasPersisted() ); - $this->assertTrue( $info->isIdSafe() ); - } else { - $this->assertNull( $provider->newSessionInfo() ); - } - } - - public function testMergeMetadata() { - $provider = $this->getMockBuilder( SessionProvider::class ) - ->getMockForAbstractClass(); - - try { - $provider->mergeMetadata( - [ 'foo' => 1, 'baz' => 3 ], - [ 'bar' => 2, 'baz' => '3' ] - ); - $this->fail( 'Expected exception not thrown' ); - } catch ( MetadataMergeException $ex ) { - $this->assertSame( 'Key "baz" changed', $ex->getMessage() ); - $this->assertSame( - [ 'old_value' => 3, 'new_value' => '3' ], $ex->getContext() ); - } - - $res = $provider->mergeMetadata( - [ 'foo' => 1, 'baz' => 3 ], - [ 'bar' => 2, 'baz' => 3 ] - ); - $this->assertSame( [ 'bar' => 2, 'baz' => 3 ], $res ); - } - - public static function provideNewSessionInfo() { - return [ - [ false, false, false ], - [ true, false, false ], - [ false, true, false ], - [ true, true, true ], - ]; - } - - public function testImmutableSessions() { - $provider = $this->getMockBuilder( SessionProvider::class ) - ->setMethods( [ 'canChangeUser', 'persistsSessionId' ] ) - ->getMockForAbstractClass(); - $provider->expects( $this->any() )->method( 'canChangeUser' ) - ->will( $this->returnValue( true ) ); - $provider->preventSessionsForUser( 'Foo' ); - - $provider = $this->getMockBuilder( SessionProvider::class ) - ->setMethods( [ 'canChangeUser', 'persistsSessionId' ] ) - ->getMockForAbstractClass(); - $provider->expects( $this->any() )->method( 'canChangeUser' ) - ->will( $this->returnValue( false ) ); - try { - $provider->preventSessionsForUser( 'Foo' ); - $this->fail( 'Expected exception not thrown' ); - } catch ( \BadMethodCallException $ex ) { - $this->assertSame( - 'MediaWiki\\Session\\SessionProvider::preventSessionsForUser must be implemented ' . - 'when canChangeUser() is false', - $ex->getMessage() - ); - } - } - - public function testHashToSessionId() { - $config = new \HashConfig( [ - 'SecretKey' => 'Shhh!', - ] ); - - $provider = $this->getMockForAbstractClass( SessionProvider::class, - [], 'MockSessionProvider' ); - $provider->setConfig( $config ); - $priv = TestingAccessWrapper::newFromObject( $provider ); - - $this->assertSame( 'eoq8cb1mg7j30ui5qolafps4hg29k5bb', $priv->hashToSessionId( 'foobar' ) ); - $this->assertSame( '4do8j7tfld1g8tte9jqp3csfgmulaun9', - $priv->hashToSessionId( 'foobar', 'secret' ) ); - - try { - $priv->hashToSessionId( [] ); - $this->fail( 'Expected exception not thrown' ); - } catch ( \InvalidArgumentException $ex ) { - $this->assertSame( - '$data must be a string, array was passed', - $ex->getMessage() - ); - } - try { - $priv->hashToSessionId( '', false ); - $this->fail( 'Expected exception not thrown' ); - } catch ( \InvalidArgumentException $ex ) { - $this->assertSame( - '$key must be a string or null, boolean was passed', - $ex->getMessage() - ); - } - } - - public function testDescribe() { - $provider = $this->getMockForAbstractClass( SessionProvider::class, - [], 'MockSessionProvider' ); - - $this->assertSame( - 'MockSessionProvider sessions', - $provider->describe( \Language::factory( 'en' ) ) - ); - } - - public function testGetAllowedUserRights() { - $provider = $this->getMockForAbstractClass( SessionProvider::class ); - $backend = TestUtils::getDummySessionBackend(); - - try { - $provider->getAllowedUserRights( $backend ); - $this->fail( 'Expected exception not thrown' ); - } catch ( \InvalidArgumentException $ex ) { - $this->assertSame( - 'Backend\'s provider isn\'t $this', - $ex->getMessage() - ); - } - - TestingAccessWrapper::newFromObject( $backend )->provider = $provider; - $this->assertNull( $provider->getAllowedUserRights( $backend ) ); - } - -} diff --git a/tests/phpunit/includes/session/SessionTest.php b/tests/phpunit/includes/session/SessionTest.php deleted file mode 100644 index a74056d0ca..0000000000 --- a/tests/phpunit/includes/session/SessionTest.php +++ /dev/null @@ -1,373 +0,0 @@ -requests = [ -1 => 'dummy' ]; - TestingAccessWrapper::newFromObject( $backend )->id = new SessionId( 'abc' ); - - $session = new Session( $backend, 42, new \TestLogger ); - $priv = TestingAccessWrapper::newFromObject( $session ); - $this->assertSame( $backend, $priv->backend ); - $this->assertSame( 42, $priv->index ); - - $request = new \FauxRequest(); - $priv2 = TestingAccessWrapper::newFromObject( $session->sessionWithRequest( $request ) ); - $this->assertSame( $backend, $priv2->backend ); - $this->assertNotSame( $priv->index, $priv2->index ); - $this->assertSame( $request, $priv2->getRequest() ); - } - - /** - * @dataProvider provideMethods - * @param string $m Method to test - * @param array $args Arguments to pass to the method - * @param bool $index Whether the backend method gets passed the index - * @param bool $ret Whether the method returns a value - */ - public function testMethods( $m, $args, $index, $ret ) { - $mock = $this->getMockBuilder( DummySessionBackend::class ) - ->setMethods( [ $m, 'deregisterSession' ] ) - ->getMock(); - $mock->expects( $this->once() )->method( 'deregisterSession' ) - ->with( $this->identicalTo( 42 ) ); - - $tmp = $mock->expects( $this->once() )->method( $m ); - $expectArgs = []; - if ( $index ) { - $expectArgs[] = $this->identicalTo( 42 ); - } - foreach ( $args as $arg ) { - $expectArgs[] = $this->identicalTo( $arg ); - } - $tmp = call_user_func_array( [ $tmp, 'with' ], $expectArgs ); - - $retval = new \stdClass; - $tmp->will( $this->returnValue( $retval ) ); - - $session = TestUtils::getDummySession( $mock, 42 ); - - if ( $ret ) { - $this->assertSame( $retval, call_user_func_array( [ $session, $m ], $args ) ); - } else { - $this->assertNull( call_user_func_array( [ $session, $m ], $args ) ); - } - - // Trigger Session destructor - $session = null; - } - - public static function provideMethods() { - return [ - [ 'getId', [], false, true ], - [ 'getSessionId', [], false, true ], - [ 'resetId', [], false, true ], - [ 'getProvider', [], false, true ], - [ 'isPersistent', [], false, true ], - [ 'persist', [], false, false ], - [ 'unpersist', [], false, false ], - [ 'shouldRememberUser', [], false, true ], - [ 'setRememberUser', [ true ], false, false ], - [ 'getRequest', [], true, true ], - [ 'getUser', [], false, true ], - [ 'getAllowedUserRights', [], false, true ], - [ 'canSetUser', [], false, true ], - [ 'setUser', [ new \stdClass ], false, false ], - [ 'suggestLoginUsername', [], true, true ], - [ 'shouldForceHTTPS', [], false, true ], - [ 'setForceHTTPS', [ true ], false, false ], - [ 'getLoggedOutTimestamp', [], false, true ], - [ 'setLoggedOutTimestamp', [ 123 ], false, false ], - [ 'getProviderMetadata', [], false, true ], - [ 'save', [], false, false ], - [ 'delaySave', [], false, true ], - [ 'renew', [], false, false ], - ]; - } - - public function testDataAccess() { - $session = TestUtils::getDummySession(); - $backend = TestingAccessWrapper::newFromObject( $session )->backend; - - $this->assertEquals( 1, $session->get( 'foo' ) ); - $this->assertEquals( 'zero', $session->get( 0 ) ); - $this->assertFalse( $backend->dirty ); - - $this->assertEquals( null, $session->get( 'null' ) ); - $this->assertEquals( 'default', $session->get( 'null', 'default' ) ); - $this->assertFalse( $backend->dirty ); - - $session->set( 'foo', 55 ); - $this->assertEquals( 55, $backend->data['foo'] ); - $this->assertTrue( $backend->dirty ); - $backend->dirty = false; - - $session->set( 1, 'one' ); - $this->assertEquals( 'one', $backend->data[1] ); - $this->assertTrue( $backend->dirty ); - $backend->dirty = false; - - $session->set( 1, 'one' ); - $this->assertFalse( $backend->dirty ); - - $this->assertTrue( $session->exists( 'foo' ) ); - $this->assertTrue( $session->exists( 1 ) ); - $this->assertFalse( $session->exists( 'null' ) ); - $this->assertFalse( $session->exists( 100 ) ); - $this->assertFalse( $backend->dirty ); - - $session->remove( 'foo' ); - $this->assertArrayNotHasKey( 'foo', $backend->data ); - $this->assertTrue( $backend->dirty ); - $backend->dirty = false; - $session->remove( 1 ); - $this->assertArrayNotHasKey( 1, $backend->data ); - $this->assertTrue( $backend->dirty ); - $backend->dirty = false; - - $session->remove( 101 ); - $this->assertFalse( $backend->dirty ); - - $backend->data = [ 'a', 'b', '?' => 'c' ]; - $this->assertSame( 3, $session->count() ); - $this->assertSame( 3, count( $session ) ); - $this->assertFalse( $backend->dirty ); - - $data = []; - foreach ( $session as $key => $value ) { - $data[$key] = $value; - } - $this->assertEquals( $backend->data, $data ); - $this->assertFalse( $backend->dirty ); - - $this->assertEquals( $backend->data, iterator_to_array( $session ) ); - $this->assertFalse( $backend->dirty ); - } - - public function testArrayAccess() { - $logger = new \TestLogger; - $session = TestUtils::getDummySession( null, -1, $logger ); - $backend = TestingAccessWrapper::newFromObject( $session )->backend; - - $this->assertEquals( 1, $session['foo'] ); - $this->assertEquals( 'zero', $session[0] ); - $this->assertFalse( $backend->dirty ); - - $logger->setCollect( true ); - $this->assertEquals( null, $session['null'] ); - $logger->setCollect( false ); - $this->assertFalse( $backend->dirty ); - $this->assertSame( [ - [ LogLevel::DEBUG, 'Undefined index (auto-adds to session with a null value): null' ] - ], $logger->getBuffer() ); - $logger->clearBuffer(); - - $session['foo'] = 55; - $this->assertEquals( 55, $backend->data['foo'] ); - $this->assertTrue( $backend->dirty ); - $backend->dirty = false; - - $session[1] = 'one'; - $this->assertEquals( 'one', $backend->data[1] ); - $this->assertTrue( $backend->dirty ); - $backend->dirty = false; - - $session[1] = 'one'; - $this->assertFalse( $backend->dirty ); - - $session['bar'] = [ 'baz' => [] ]; - $session['bar']['baz']['quux'] = 2; - $this->assertEquals( [ 'baz' => [ 'quux' => 2 ] ], $backend->data['bar'] ); - - $logger->setCollect( true ); - $session['bar2']['baz']['quux'] = 3; - $logger->setCollect( false ); - $this->assertEquals( [ 'baz' => [ 'quux' => 3 ] ], $backend->data['bar2'] ); - $this->assertSame( [ - [ LogLevel::DEBUG, 'Undefined index (auto-adds to session with a null value): bar2' ] - ], $logger->getBuffer() ); - $logger->clearBuffer(); - - $backend->dirty = false; - $this->assertTrue( isset( $session['foo'] ) ); - $this->assertTrue( isset( $session[1] ) ); - $this->assertFalse( isset( $session['null'] ) ); - $this->assertFalse( isset( $session['missing'] ) ); - $this->assertFalse( isset( $session[100] ) ); - $this->assertFalse( $backend->dirty ); - - unset( $session['foo'] ); - $this->assertArrayNotHasKey( 'foo', $backend->data ); - $this->assertTrue( $backend->dirty ); - $backend->dirty = false; - unset( $session[1] ); - $this->assertArrayNotHasKey( 1, $backend->data ); - $this->assertTrue( $backend->dirty ); - $backend->dirty = false; - - unset( $session[101] ); - $this->assertFalse( $backend->dirty ); - } - - public function testClear() { - $session = TestUtils::getDummySession(); - $priv = TestingAccessWrapper::newFromObject( $session ); - - $backend = $this->getMockBuilder( DummySessionBackend::class ) - ->setMethods( [ 'canSetUser', 'setUser', 'save' ] ) - ->getMock(); - $backend->expects( $this->once() )->method( 'canSetUser' ) - ->will( $this->returnValue( true ) ); - $backend->expects( $this->once() )->method( 'setUser' ) - ->with( $this->callback( function ( $user ) { - return $user instanceof User && $user->isAnon(); - } ) ); - $backend->expects( $this->once() )->method( 'save' ); - $priv->backend = $backend; - $session->clear(); - $this->assertSame( [], $backend->data ); - $this->assertTrue( $backend->dirty ); - - $backend = $this->getMockBuilder( DummySessionBackend::class ) - ->setMethods( [ 'canSetUser', 'setUser', 'save' ] ) - ->getMock(); - $backend->data = []; - $backend->expects( $this->once() )->method( 'canSetUser' ) - ->will( $this->returnValue( true ) ); - $backend->expects( $this->once() )->method( 'setUser' ) - ->with( $this->callback( function ( $user ) { - return $user instanceof User && $user->isAnon(); - } ) ); - $backend->expects( $this->once() )->method( 'save' ); - $priv->backend = $backend; - $session->clear(); - $this->assertFalse( $backend->dirty ); - - $backend = $this->getMockBuilder( DummySessionBackend::class ) - ->setMethods( [ 'canSetUser', 'setUser', 'save' ] ) - ->getMock(); - $backend->expects( $this->once() )->method( 'canSetUser' ) - ->will( $this->returnValue( false ) ); - $backend->expects( $this->never() )->method( 'setUser' ); - $backend->expects( $this->once() )->method( 'save' ); - $priv->backend = $backend; - $session->clear(); - $this->assertSame( [], $backend->data ); - $this->assertTrue( $backend->dirty ); - } - - public function testTokens() { - $session = TestUtils::getDummySession(); - $priv = TestingAccessWrapper::newFromObject( $session ); - $backend = $priv->backend; - - $token = TestingAccessWrapper::newFromObject( $session->getToken() ); - $this->assertArrayHasKey( 'wsTokenSecrets', $backend->data ); - $this->assertArrayHasKey( 'default', $backend->data['wsTokenSecrets'] ); - $secret = $backend->data['wsTokenSecrets']['default']; - $this->assertSame( $secret, $token->secret ); - $this->assertSame( '', $token->salt ); - $this->assertTrue( $token->wasNew() ); - - $token = TestingAccessWrapper::newFromObject( $session->getToken( 'foo' ) ); - $this->assertSame( $secret, $token->secret ); - $this->assertSame( 'foo', $token->salt ); - $this->assertFalse( $token->wasNew() ); - - $backend->data['wsTokenSecrets']['secret'] = 'sekret'; - $token = TestingAccessWrapper::newFromObject( - $session->getToken( [ 'bar', 'baz' ], 'secret' ) - ); - $this->assertSame( 'sekret', $token->secret ); - $this->assertSame( 'bar|baz', $token->salt ); - $this->assertFalse( $token->wasNew() ); - - $session->resetToken( 'secret' ); - $this->assertArrayHasKey( 'wsTokenSecrets', $backend->data ); - $this->assertArrayHasKey( 'default', $backend->data['wsTokenSecrets'] ); - $this->assertArrayNotHasKey( 'secret', $backend->data['wsTokenSecrets'] ); - - $session->resetAllTokens(); - $this->assertArrayNotHasKey( 'wsTokenSecrets', $backend->data ); - } - - /** - * @dataProvider provideSecretsRoundTripping - * @param mixed $data - */ - public function testSecretsRoundTripping( $data ) { - $session = TestUtils::getDummySession(); - - // Simple round-trip - $session->setSecret( 'secret', $data ); - $this->assertNotEquals( $data, $session->get( 'secret' ) ); - $this->assertEquals( $data, $session->getSecret( 'secret', 'defaulted' ) ); - } - - public static function provideSecretsRoundTripping() { - return [ - [ 'Foobar' ], - [ 42 ], - [ [ 'foo', 'bar' => 'baz', 'subarray' => [ 1, 2, 3 ] ] ], - [ (object)[ 'foo', 'bar' => 'baz', 'subarray' => [ 1, 2, 3 ] ] ], - [ true ], - [ false ], - [ null ], - ]; - } - - public function testSecrets() { - $logger = new \TestLogger; - $session = TestUtils::getDummySession( null, -1, $logger ); - - // Simple defaulting - $this->assertEquals( 'defaulted', $session->getSecret( 'test', 'defaulted' ) ); - - // Bad encrypted data - $session->set( 'test', 'foobar' ); - $logger->setCollect( true ); - $this->assertEquals( 'defaulted', $session->getSecret( 'test', 'defaulted' ) ); - $logger->setCollect( false ); - $this->assertSame( [ - [ LogLevel::WARNING, 'Invalid sealed-secret format' ] - ], $logger->getBuffer() ); - $logger->clearBuffer(); - - // Tampered data - $session->setSecret( 'test', 'foobar' ); - $encrypted = $session->get( 'test' ); - $session->set( 'test', $encrypted . 'x' ); - $logger->setCollect( true ); - $this->assertEquals( 'defaulted', $session->getSecret( 'test', 'defaulted' ) ); - $logger->setCollect( false ); - $this->assertSame( [ - [ LogLevel::WARNING, 'Sealed secret has been tampered with, aborting.' ] - ], $logger->getBuffer() ); - $logger->clearBuffer(); - - // Unserializable data - $iv = random_bytes( 16 ); - list( $encKey, $hmacKey ) = TestingAccessWrapper::newFromObject( $session )->getSecretKeys(); - $ciphertext = openssl_encrypt( 'foobar', 'aes-256-ctr', $encKey, OPENSSL_RAW_DATA, $iv ); - $sealed = base64_encode( $iv ) . '.' . base64_encode( $ciphertext ); - $hmac = hash_hmac( 'sha256', $sealed, $hmacKey, true ); - $encrypted = base64_encode( $hmac ) . '.' . $sealed; - $session->set( 'test', $encrypted ); - \Wikimedia\suppressWarnings(); - $this->assertEquals( 'defaulted', $session->getSecret( 'test', 'defaulted' ) ); - \Wikimedia\restoreWarnings(); - } - -} diff --git a/tests/phpunit/includes/session/TokenTest.php b/tests/phpunit/includes/session/TokenTest.php deleted file mode 100644 index 47976527ca..0000000000 --- a/tests/phpunit/includes/session/TokenTest.php +++ /dev/null @@ -1,67 +0,0 @@ -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/CommandFactoryTest.php b/tests/phpunit/includes/shell/CommandFactoryTest.php deleted file mode 100644 index b031431af7..0000000000 --- a/tests/phpunit/includes/shell/CommandFactoryTest.php +++ /dev/null @@ -1,50 +0,0 @@ - 1000, - 'memory' => 1000, - 'time' => 30, - 'walltime' => 40, - ]; - - $factory = new CommandFactory( $limits, $cgroup, false ); - $factory->setLogger( $logger ); - $factory->logStderr(); - $command = $factory->create(); - $this->assertInstanceOf( Command::class, $command ); - - $wrapper = TestingAccessWrapper::newFromObject( $command ); - $this->assertSame( $logger, $wrapper->logger ); - $this->assertSame( $cgroup, $wrapper->cgroup ); - $this->assertSame( $limits, $wrapper->limits ); - $this->assertTrue( $wrapper->doLogStderr ); - } - - /** - * @covers MediaWiki\Shell\CommandFactory::create - */ - public function testFirejailCreate() { - $factory = new CommandFactory( [], false, 'firejail' ); - $factory->setLogger( new NullLogger() ); - $this->assertInstanceOf( FirejailCommand::class, $factory->create() ); - } -} diff --git a/tests/phpunit/includes/shell/CommandTest.php b/tests/phpunit/includes/shell/CommandTest.php deleted file mode 100644 index 2e03163885..0000000000 --- a/tests/phpunit/includes/shell/CommandTest.php +++ /dev/null @@ -1,181 +0,0 @@ -markTestSkipped( 'This test requires a POSIX environment.' ); - } - } - - /** - * @dataProvider provideExecute - */ - public function testExecute( $commandInput, $expectedExitCode, $expectedOutput ) { - $this->requirePosix(); - - $command = new Command(); - $result = $command - ->params( $commandInput ) - ->execute(); - - $this->assertSame( $expectedExitCode, $result->getExitCode() ); - $this->assertSame( $expectedOutput, $result->getStdout() ); - } - - public function provideExecute() { - return [ - 'success status' => [ 'true', 0, '' ], - 'failure status' => [ 'false', 1, '' ], - 'output' => [ [ 'echo', '-n', 'x', '>', 'y' ], 0, 'x > y' ], - ]; - } - - public function testEnvironment() { - $this->requirePosix(); - - $command = new Command(); - $result = $command - ->params( [ 'printenv', 'FOO' ] ) - ->environment( [ 'FOO' => 'bar' ] ) - ->execute(); - $this->assertSame( "bar\n", $result->getStdout() ); - } - - public function testStdout() { - $this->requirePosix(); - - $command = new Command(); - - $result = $command - ->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' ) - ->execute(); - - $this->assertNotContains( 'ThisIsStderr', $result->getStdout() ); - $this->assertEquals( "ThisIsStderr\n", $result->getStderr() ); - } - - public function testStdoutRedirection() { - $this->requirePosix(); - - $command = new Command(); - - $result = $command - ->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' ) - ->includeStderr( true ) - ->execute(); - - $this->assertEquals( "ThisIsStderr\n", $result->getStdout() ); - $this->assertNull( $result->getStderr() ); - } - - public function testOutput() { - global $IP; - - $this->requirePosix(); - chdir( $IP ); - - $command = new Command(); - $result = $command - ->params( [ 'ls', 'index.php' ] ) - ->execute(); - $this->assertRegExp( '/^index.php$/m', $result->getStdout() ); - $this->assertSame( null, $result->getStderr() ); - - $command = new Command(); - $result = $command - ->params( [ 'ls', 'index.php', 'no-such-file' ] ) - ->includeStderr() - ->execute(); - $this->assertRegExp( '/^index.php$/m', $result->getStdout() ); - $this->assertRegExp( '/^.+no-such-file.*$/m', $result->getStdout() ); - $this->assertSame( null, $result->getStderr() ); - - $command = new Command(); - $result = $command - ->params( [ 'ls', 'index.php', 'no-such-file' ] ) - ->execute(); - $this->assertRegExp( '/^index.php$/m', $result->getStdout() ); - $this->assertRegExp( '/^.+no-such-file.*$/m', $result->getStderr() ); - } - - /** - * Test that null values are skipped by params() and unsafeParams() - */ - public function testNullsAreSkipped() { - $command = TestingAccessWrapper::newFromObject( new Command ); - $command->params( 'echo', 'a', null, 'b' ); - $command->unsafeParams( 'c', null, 'd' ); - $this->assertEquals( "'echo' 'a' 'b' c d", $command->command ); - } - - public function testT69870() { - $commandLine = wfIsWindows() - // 333 = 331 + CRLF - ? ( 'for /l %i in (1, 1, 1001) do @echo ' . str_repeat( '*', 331 ) ) - : 'printf "%-333333s" "*"'; - - // Test several times because it involves a race condition that may randomly succeed or fail - for ( $i = 0; $i < 10; $i++ ) { - $command = new Command(); - $output = $command->unsafeParams( $commandLine ) - ->execute() - ->getStdout(); - $this->assertEquals( 333333, strlen( $output ) ); - } - } - - public function testLogStderr() { - $this->requirePosix(); - - $logger = new TestLogger( true, function ( $message, $level, $context ) { - return $level === Psr\Log\LogLevel::ERROR ? '1' : null; - }, true ); - $command = new Command(); - $command->setLogger( $logger ); - $command->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' ); - $command->execute(); - $this->assertEmpty( $logger->getBuffer() ); - - $command = new Command(); - $command->setLogger( $logger ); - $command->logStderr(); - $command->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' ); - $command->execute(); - $this->assertSame( 1, count( $logger->getBuffer() ) ); - $this->assertSame( trim( $logger->getBuffer()[0][2]['error'] ), 'ThisIsStderr' ); - } - - public function testInput() { - $this->requirePosix(); - - $command = new Command(); - $command->params( 'cat' ); - $command->input( 'abc' ); - $result = $command->execute(); - $this->assertSame( 'abc', $result->getStdout() ); - - // now try it with something that does not fit into a single block - $command = new Command(); - $command->params( 'cat' ); - $command->input( str_repeat( '!', 1000000 ) ); - $result = $command->execute(); - $this->assertSame( 1000000, strlen( $result->getStdout() ) ); - - // And try it with empty input - $command = new Command(); - $command->params( 'cat' ); - $command->input( '' ); - $result = $command->execute(); - $this->assertSame( '', $result->getStdout() ); - } -} diff --git a/tests/phpunit/includes/shell/FirejailCommandTest.php b/tests/phpunit/includes/shell/FirejailCommandTest.php deleted file mode 100644 index 681c3dcda0..0000000000 --- a/tests/phpunit/includes/shell/FirejailCommandTest.php +++ /dev/null @@ -1,85 +0,0 @@ - - * - * 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/CachingSiteStoreTest.php b/tests/phpunit/includes/site/CachingSiteStoreTest.php deleted file mode 100644 index f04d35ca02..0000000000 --- a/tests/phpunit/includes/site/CachingSiteStoreTest.php +++ /dev/null @@ -1,167 +0,0 @@ - - */ -class CachingSiteStoreTest extends MediaWikiTestCase { - - /** - * @covers CachingSiteStore::getSites - */ - public function testGetSites() { - $testSites = TestSites::getSites(); - - $store = new CachingSiteStore( - $this->getHashSiteStore( $testSites ), - ObjectCache::getLocalClusterInstance() - ); - - $sites = $store->getSites(); - - $this->assertInstanceOf( SiteList::class, $sites ); - - /** - * @var Site $site - */ - foreach ( $sites as $site ) { - $this->assertInstanceOf( Site::class, $site ); - } - - foreach ( $testSites as $site ) { - if ( $site->getGlobalId() !== null ) { - $this->assertTrue( $sites->hasSite( $site->getGlobalId() ) ); - } - } - } - - /** - * @covers CachingSiteStore::saveSites - */ - public function testSaveSites() { - $store = new CachingSiteStore( - new HashSiteStore(), ObjectCache::getLocalClusterInstance() - ); - - $sites = []; - - $site = new Site(); - $site->setGlobalId( 'ertrywuutr' ); - $site->setLanguageCode( 'en' ); - $sites[] = $site; - - $site = new MediaWikiSite(); - $site->setGlobalId( 'sdfhxujgkfpth' ); - $site->setLanguageCode( 'nl' ); - $sites[] = $site; - - $this->assertTrue( $store->saveSites( $sites ) ); - - $site = $store->getSite( 'ertrywuutr' ); - $this->assertInstanceOf( Site::class, $site ); - $this->assertEquals( 'en', $site->getLanguageCode() ); - - $site = $store->getSite( 'sdfhxujgkfpth' ); - $this->assertInstanceOf( Site::class, $site ); - $this->assertEquals( 'nl', $site->getLanguageCode() ); - } - - /** - * @covers CachingSiteStore::reset - */ - public function testReset() { - $dbSiteStore = $this->getMockBuilder( SiteStore::class ) - ->disableOriginalConstructor() - ->getMock(); - - $dbSiteStore->expects( $this->any() ) - ->method( 'getSite' ) - ->will( $this->returnValue( $this->getTestSite() ) ); - - $dbSiteStore->expects( $this->any() ) - ->method( 'getSites' ) - ->will( $this->returnCallback( function () { - $siteList = new SiteList(); - $siteList->setSite( $this->getTestSite() ); - - return $siteList; - } ) ); - - $store = new CachingSiteStore( $dbSiteStore, ObjectCache::getLocalClusterInstance() ); - - // initialize internal cache - $this->assertGreaterThan( 0, $store->getSites()->count(), 'count sites' ); - - $store->getSite( 'enwiki' )->setLanguageCode( 'en-ca' ); - - // sanity check: $store should have the new language code for 'enwiki' - $this->assertEquals( 'en-ca', $store->getSite( 'enwiki' )->getLanguageCode(), 'sanity check' ); - - // purge cache - $store->reset(); - - // the internal cache of $store should be updated, and now pulling - // the site from the 'fallback' DBSiteStore with the original language code. - $this->assertEquals( 'en', $store->getSite( 'enwiki' )->getLanguageCode(), 'reset' ); - } - - public function getTestSite() { - $enwiki = new MediaWikiSite(); - $enwiki->setGlobalId( 'enwiki' ); - $enwiki->setLanguageCode( 'en' ); - - return $enwiki; - } - - /** - * @covers CachingSiteStore::clear - */ - public function testClear() { - $store = new CachingSiteStore( - new HashSiteStore(), ObjectCache::getLocalClusterInstance() - ); - $this->assertTrue( $store->clear() ); - - $site = $store->getSite( 'enwiki' ); - $this->assertNull( $site ); - - $sites = $store->getSites(); - $this->assertEquals( 0, $sites->count() ); - } - - /** - * @param Site[] $sites - * - * @return SiteStore - */ - private function getHashSiteStore( array $sites ) { - $siteStore = new HashSiteStore(); - $siteStore->saveSites( $sites ); - - return $siteStore; - } - -} diff --git a/tests/phpunit/includes/site/HashSiteStoreTest.php b/tests/phpunit/includes/site/HashSiteStoreTest.php deleted file mode 100644 index 6269fd39dc..0000000000 --- a/tests/phpunit/includes/site/HashSiteStoreTest.php +++ /dev/null @@ -1,105 +0,0 @@ - - */ -class HashSiteStoreTest extends MediaWikiTestCase { - - /** - * @covers HashSiteStore::getSites - */ - public function testGetSites() { - $expectedSites = []; - - foreach ( TestSites::getSites() as $testSite ) { - $siteId = $testSite->getGlobalId(); - $expectedSites[$siteId] = $testSite; - } - - $siteStore = new HashSiteStore( $expectedSites ); - - $this->assertEquals( new SiteList( $expectedSites ), $siteStore->getSites() ); - } - - /** - * @covers HashSiteStore::saveSite - * @covers HashSiteStore::getSite - */ - public function testSaveSite() { - $store = new HashSiteStore(); - - $site = new Site(); - $site->setGlobalId( 'dewiki' ); - - $this->assertCount( 0, $store->getSites(), '0 sites in store' ); - - $store->saveSite( $site ); - - $this->assertCount( 1, $store->getSites(), 'Store has 1 sites' ); - $this->assertEquals( $site, $store->getSite( 'dewiki' ), 'Store has dewiki' ); - } - - /** - * @covers HashSiteStore::saveSites - */ - public function testSaveSites() { - $store = new HashSiteStore(); - - $sites = []; - - $site = new Site(); - $site->setGlobalId( 'enwiki' ); - $site->setLanguageCode( 'en' ); - $sites[] = $site; - - $site = new MediaWikiSite(); - $site->setGlobalId( 'eswiki' ); - $site->setLanguageCode( 'es' ); - $sites[] = $site; - - $this->assertCount( 0, $store->getSites(), '0 sites in store' ); - - $store->saveSites( $sites ); - - $this->assertCount( 2, $store->getSites(), 'Store has 2 sites' ); - $this->assertTrue( $store->getSites()->hasSite( 'enwiki' ), 'Store has enwiki' ); - $this->assertTrue( $store->getSites()->hasSite( 'eswiki' ), 'Store has eswiki' ); - } - - /** - * @covers HashSiteStore::clear - */ - public function testClear() { - $store = new HashSiteStore(); - - $site = new Site(); - $site->setGlobalId( 'arwiki' ); - $store->saveSite( $site ); - - $this->assertCount( 1, $store->getSites(), '1 site in store' ); - - $store->clear(); - $this->assertCount( 0, $store->getSites(), '0 sites in store' ); - } -} diff --git a/tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php b/tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php deleted file mode 100644 index 15894a3d9a..0000000000 --- a/tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php +++ /dev/null @@ -1,116 +0,0 @@ -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/site/SiteExporterTest.php b/tests/phpunit/includes/site/SiteExporterTest.php deleted file mode 100644 index 97a43f8d5b..0000000000 --- a/tests/phpunit/includes/site/SiteExporterTest.php +++ /dev/null @@ -1,148 +0,0 @@ -setExpectedException( InvalidArgumentException::class ); - - new SiteExporter( 'Foo' ); - } - - public function testExportSites() { - $foo = Site::newForType( Site::TYPE_UNKNOWN ); - $foo->setGlobalId( 'Foo' ); - - $acme = Site::newForType( Site::TYPE_UNKNOWN ); - $acme->setGlobalId( 'acme.com' ); - $acme->setGroup( 'Test' ); - $acme->addLocalId( Site::ID_INTERWIKI, 'acme' ); - $acme->setPath( Site::PATH_LINK, 'http://acme.com/' ); - - $tmp = tmpfile(); - $exporter = new SiteExporter( $tmp ); - - $exporter->exportSites( [ $foo, $acme ] ); - - fseek( $tmp, 0 ); - $xml = fread( $tmp, 16 * 1024 ); - - $this->assertContains( 'assertContains( '', $xml ); - $this->assertContains( 'Foo', $xml ); - $this->assertContains( '', $xml ); - $this->assertContains( 'acme.com', $xml ); - $this->assertContains( 'Test', $xml ); - $this->assertContains( 'acme', $xml ); - $this->assertContains( 'http://acme.com/', $xml ); - $this->assertContains( '', $xml ); - - // NOTE: HHVM (at least on wmf Jenkins) doesn't like file URLs. - $xsdFile = __DIR__ . '/../../../../docs/sitelist-1.0.xsd'; - $xsdData = file_get_contents( $xsdFile ); - - $document = new DOMDocument(); - $document->loadXML( $xml, LIBXML_NONET ); - $document->schemaValidateSource( $xsdData ); - } - - private function newSiteStore( SiteList $sites ) { - $store = $this->getMockBuilder( SiteStore::class )->getMock(); - - $store->expects( $this->once() ) - ->method( 'saveSites' ) - ->will( $this->returnCallback( function ( $moreSites ) use ( $sites ) { - foreach ( $moreSites as $site ) { - $sites->setSite( $site ); - } - } ) ); - - $store->expects( $this->any() ) - ->method( 'getSites' ) - ->will( $this->returnValue( new SiteList() ) ); - - return $store; - } - - public function provideRoundTrip() { - $foo = Site::newForType( Site::TYPE_UNKNOWN ); - $foo->setGlobalId( 'Foo' ); - - $acme = Site::newForType( Site::TYPE_UNKNOWN ); - $acme->setGlobalId( 'acme.com' ); - $acme->setGroup( 'Test' ); - $acme->addLocalId( Site::ID_INTERWIKI, 'acme' ); - $acme->setPath( Site::PATH_LINK, 'http://acme.com/' ); - - $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI ); - $dewiki->setGlobalId( 'dewiki' ); - $dewiki->setGroup( 'wikipedia' ); - $dewiki->setForward( true ); - $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' ); - $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' ); - $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' ); - $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' ); - $dewiki->setSource( 'meta.wikimedia.org' ); - - return [ - 'empty' => [ - new SiteList() - ], - - 'some' => [ - new SiteList( [ $foo, $acme, $dewiki ] ), - ], - ]; - } - - /** - * @dataProvider provideRoundTrip() - */ - public function testRoundTrip( SiteList $sites ) { - $tmp = tmpfile(); - $exporter = new SiteExporter( $tmp ); - - $exporter->exportSites( $sites ); - - fseek( $tmp, 0 ); - $xml = fread( $tmp, 16 * 1024 ); - - $actualSites = new SiteList(); - $store = $this->newSiteStore( $actualSites ); - - $importer = new SiteImporter( $store ); - $importer->importFromXML( $xml ); - - $this->assertEquals( $sites, $actualSites ); - } - -} diff --git a/tests/phpunit/includes/site/SiteImporterTest.php b/tests/phpunit/includes/site/SiteImporterTest.php deleted file mode 100644 index dbdbd6fcc2..0000000000 --- a/tests/phpunit/includes/site/SiteImporterTest.php +++ /dev/null @@ -1,200 +0,0 @@ -getMockBuilder( SiteStore::class )->getMock(); - - $store->expects( $this->once() ) - ->method( 'saveSites' ) - ->will( $this->returnCallback( function ( $sites ) use ( $expectedSites ) { - $this->assertSitesEqual( $expectedSites, $sites ); - } ) ); - - $store->expects( $this->any() ) - ->method( 'getSites' ) - ->will( $this->returnValue( new SiteList() ) ); - - $errorHandler = $this->getMockBuilder( Psr\Log\LoggerInterface::class )->getMock(); - $errorHandler->expects( $this->exactly( $errorCount ) ) - ->method( 'error' ); - - $importer = new SiteImporter( $store ); - $importer->setExceptionCallback( [ $errorHandler, 'error' ] ); - - return $importer; - } - - public function assertSitesEqual( $expected, $actual, $message = '' ) { - $this->assertEquals( - $this->getSerializedSiteList( $expected ), - $this->getSerializedSiteList( $actual ), - $message - ); - } - - public function provideImportFromXML() { - $foo = Site::newForType( Site::TYPE_UNKNOWN ); - $foo->setGlobalId( 'Foo' ); - - $acme = Site::newForType( Site::TYPE_UNKNOWN ); - $acme->setGlobalId( 'acme.com' ); - $acme->setGroup( 'Test' ); - $acme->addLocalId( Site::ID_INTERWIKI, 'acme' ); - $acme->setPath( Site::PATH_LINK, 'http://acme.com/' ); - - $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI ); - $dewiki->setGlobalId( 'dewiki' ); - $dewiki->setGroup( 'wikipedia' ); - $dewiki->setForward( true ); - $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' ); - $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' ); - $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' ); - $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' ); - $dewiki->setSource( 'meta.wikimedia.org' ); - - return [ - 'empty' => [ - '', - [], - ], - 'no sites' => [ - 'FooBla', - [], - ], - 'minimal' => [ - '' . - 'Foo' . - '', - [ $foo ], - ], - 'full' => [ - '' . - 'Foo' . - '' . - 'acme.com' . - 'acme' . - 'Test' . - 'http://acme.com/' . - '' . - '' . - 'meta.wikimedia.org' . - 'dewiki' . - 'wikipedia' . - 'de' . - 'wikipedia' . - '' . - 'http://de.wikipedia.org/w/' . - 'http://de.wikipedia.org/wiki/' . - '' . - '', - [ $foo, $acme, $dewiki ], - ], - 'skip' => [ - '' . - 'Foo' . - 'Foo' . - '' . - 'acme.com' . - 'acme' . - 'boop!' . - 'Test' . - 'http://acme.com/' . - '' . - '', - [ $foo, $acme ], - 1 - ], - ]; - } - - /** - * @dataProvider provideImportFromXML - */ - public function testImportFromXML( $xml, array $expectedSites, $errorCount = 0 ) { - $importer = $this->newSiteImporter( $expectedSites, $errorCount ); - $importer->importFromXML( $xml ); - } - - public function testImportFromXML_malformed() { - $this->setExpectedException( Exception::class ); - - $store = $this->getMockBuilder( SiteStore::class )->getMock(); - $importer = new SiteImporter( $store ); - $importer->importFromXML( 'THIS IS NOT XML' ); - } - - public function testImportFromFile() { - $foo = Site::newForType( Site::TYPE_UNKNOWN ); - $foo->setGlobalId( 'Foo' ); - - $acme = Site::newForType( Site::TYPE_UNKNOWN ); - $acme->setGlobalId( 'acme.com' ); - $acme->setGroup( 'Test' ); - $acme->addLocalId( Site::ID_INTERWIKI, 'acme' ); - $acme->setPath( Site::PATH_LINK, 'http://acme.com/' ); - - $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI ); - $dewiki->setGlobalId( 'dewiki' ); - $dewiki->setGroup( 'wikipedia' ); - $dewiki->setForward( true ); - $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' ); - $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' ); - $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' ); - $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' ); - $dewiki->setSource( 'meta.wikimedia.org' ); - - $importer = $this->newSiteImporter( [ $foo, $acme, $dewiki ], 0 ); - - $file = __DIR__ . '/SiteImporterTest.xml'; - $importer->importFromFile( $file ); - } - - /** - * @param Site[] $sites - * - * @return array[] - */ - private function getSerializedSiteList( $sites ) { - $serialized = []; - - foreach ( $sites as $site ) { - $key = $site->getGlobalId(); - $data = unserialize( $site->serialize() ); - - $serialized[$key] = $data; - } - - return $serialized; - } -} diff --git a/tests/phpunit/includes/site/SiteImporterTest.xml b/tests/phpunit/includes/site/SiteImporterTest.xml deleted file mode 100644 index 720b1faf1a..0000000000 --- a/tests/phpunit/includes/site/SiteImporterTest.xml +++ /dev/null @@ -1,19 +0,0 @@ - - Foo - - acme.com - acme - Test - http://acme.com/ - - - meta.wikimedia.org - dewiki - wikipedia - de - wikipedia - - http://de.wikipedia.org/w/ - http://de.wikipedia.org/wiki/ - - diff --git a/tests/phpunit/includes/skins/SkinFactoryTest.php b/tests/phpunit/includes/skins/SkinFactoryTest.php deleted file mode 100644 index 4289fd9188..0000000000 --- a/tests/phpunit/includes/skins/SkinFactoryTest.php +++ /dev/null @@ -1,82 +0,0 @@ -register( 'fallback', 'Fallback', function () { - return new SkinFallback(); - } ); - $this->assertTrue( true ); // No exception thrown - $this->setExpectedException( InvalidArgumentException::class ); - $factory->register( 'invalid', 'Invalid', 'Invalid callback' ); - } - - /** - * @covers SkinFactory::makeSkin - */ - public function testMakeSkinWithNoBuilders() { - $factory = new SkinFactory(); - $this->setExpectedException( SkinException::class ); - $factory->makeSkin( 'nobuilderregistered' ); - } - - /** - * @covers SkinFactory::makeSkin - */ - public function testMakeSkinWithInvalidCallback() { - $factory = new SkinFactory(); - $factory->register( 'unittest', 'Unittest', function () { - return true; // Not a Skin object - } ); - $this->setExpectedException( UnexpectedValueException::class ); - $factory->makeSkin( 'unittest' ); - } - - /** - * @covers SkinFactory::makeSkin - */ - public function testMakeSkinWithValidCallback() { - $factory = new SkinFactory(); - $factory->register( 'testfallback', 'TestFallback', function () { - return new SkinFallback(); - } ); - - $skin = $factory->makeSkin( 'testfallback' ); - $this->assertInstanceOf( Skin::class, $skin ); - $this->assertInstanceOf( SkinFallback::class, $skin ); - $this->assertEquals( 'fallback', $skin->getSkinName() ); - } - - /** - * @covers Skin::__construct - * @covers Skin::getSkinName - */ - public function testGetSkinName() { - $skin = new SkinFallback(); - $this->assertEquals( 'fallback', $skin->getSkinName(), 'Default' ); - $skin = new SkinFallback( 'testname' ); - $this->assertEquals( 'testname', $skin->getSkinName(), 'Constructor argument' ); - } - - /** - * @covers SkinFactory::getSkinNames - */ - public function testGetSkinNames() { - $factory = new SkinFactory(); - // A fake callback we can use that will never be called - $callback = function () { - // NOP - }; - $factory->register( 'skin1', 'Skin1', $callback ); - $factory->register( 'skin2', 'Skin2', $callback ); - $names = $factory->getSkinNames(); - $this->assertArrayHasKey( 'skin1', $names ); - $this->assertArrayHasKey( 'skin2', $names ); - $this->assertEquals( 'Skin1', $names['skin1'] ); - $this->assertEquals( 'Skin2', $names['skin2'] ); - } -} diff --git a/tests/phpunit/includes/skins/SkinTemplateTest.php b/tests/phpunit/includes/skins/SkinTemplateTest.php deleted file mode 100644 index 6ea5b40b0b..0000000000 --- a/tests/phpunit/includes/skins/SkinTemplateTest.php +++ /dev/null @@ -1,109 +0,0 @@ - - */ -class SkinTemplateTest extends MediaWikiTestCase { - /** - * @dataProvider makeListItemProvider - */ - public function testMakeListItem( $expected, $key, $item, $options, $message ) { - $template = $this->getMockForAbstractClass( BaseTemplate::class ); - - $this->assertEquals( - $expected, - $template->makeListItem( $key, $item, $options ), - $message - ); - } - - public function makeListItemProvider() { - return [ - [ - '

  • text
  • ', - '', - [ - 'class' => 'class', - 'itemtitle' => 'itemtitle', - 'href' => 'url', - 'title' => 'title', - 'text' => 'text' - ], - [], - 'Test makeListItem with normal values' - ] - ]; - } - - /** - * @return PHPUnit_Framework_MockObject_MockObject|OutputPage - */ - private function getMockOutputPage( $isSyndicated, $html ) { - $mock = $this->getMockBuilder( OutputPage::class ) - ->disableOriginalConstructor() - ->getMock(); - $mock->expects( $this->once() ) - ->method( 'isSyndicated' ) - ->will( $this->returnValue( $isSyndicated ) ); - $mock->expects( $this->any() ) - ->method( 'getHTML' ) - ->will( $this->returnValue( $html ) ); - return $mock; - } - - public function provideGetDefaultModules() { - $defaultStyles = [ - 'mediawiki.legacy.shared', - 'mediawiki.legacy.commonPrint', - ]; - $buttonStyle = 'mediawiki.ui.button'; - $feedStyle = 'mediawiki.feedlink'; - return [ - [ - false, - '', - $defaultStyles - ], - [ - true, - '', - array_merge( $defaultStyles, [ $feedStyle ] ) - ], - [ - false, - 'FOO mw-ui-button BAR', - array_merge( $defaultStyles, [ $buttonStyle ] ) - ], - [ - true, - 'FOO mw-ui-button BAR', - array_merge( $defaultStyles, [ $buttonStyle, $feedStyle ] ) - ], - ]; - } - - /** - * @covers Skin::getDefaultModules - * @dataProvider provideGetDefaultModules - */ - public function testgetDefaultModules( $isSyndicated, $html, $expectedModuleStyles ) { - $skin = new SkinTemplate(); - - $context = new DerivativeContext( $skin->getContext() ); - $context->setOutput( $this->getMockOutputPage( $isSyndicated, $html ) ); - $skin->setContext( $context ); - - $modules = $skin->getDefaultModules(); - - $actualStylesModule = call_user_func_array( 'array_merge', $modules['styles'] ); - $this->assertArraySubset( - $expectedModuleStyles, - $actualStylesModule, - 'style modules' - ); - } -} diff --git a/tests/phpunit/includes/skins/SkinTest.php b/tests/phpunit/includes/skins/SkinTest.php deleted file mode 100644 index 41ef2b796a..0000000000 --- a/tests/phpunit/includes/skins/SkinTest.php +++ /dev/null @@ -1,16 +0,0 @@ -getMockBuilder( Skin::class ) - ->setMethods( [ 'outputPage', 'setupSkinUserCss' ] ) - ->getMock(); - - $modules = $skin->getDefaultModules(); - $this->assertTrue( isset( $modules['core'] ), 'core key is set by default' ); - $this->assertTrue( isset( $modules['styles'] ), 'style key is set by default' ); - } -} diff --git a/tests/phpunit/includes/sparql/SparqlClientTest.php b/tests/phpunit/includes/sparql/SparqlClientTest.php deleted file mode 100644 index 62af48920b..0000000000 --- a/tests/phpunit/includes/sparql/SparqlClientTest.php +++ /dev/null @@ -1,191 +0,0 @@ -getMock( HttpRequestFactory::class ); - $requestFactory->method( 'create' )->willReturn( $request ); - return $requestFactory; - } - - private function getRequestMock( $content ) { - $request = $this->getMockBuilder( MWHttpRequest::class )->disableOriginalConstructor()->getMock(); - $request->method( 'execute' )->willReturn( \Status::newGood( 200 ) ); - $request->method( 'getContent' )->willReturn( $content ); - return $request; - } - - public function testQuery() { - $json = <<getRequestMock( $json ); - $client = new SparqlClient( 'http://acme.test/', $this->getRequestFactory( $request ) ); - - // values only - $result = $client->query( "TEST SPARQL" ); - $this->assertCount( 2, $result ); - $this->assertEquals( 'http://wikiba.se/ontology#Dump', $result[0]['x'] ); - $this->assertEquals( 'http://creativecommons.org/ns#license', $result[0]['y'] ); - $this->assertEquals( '0.1.0', $result[1]['z'] ); - $this->assertNull( $result[1]['y'] ); - // raw data format - $result = $client->query( "TEST SPARQL 2", true ); - $this->assertCount( 2, $result ); - $this->assertEquals( 'uri', $result[0]['x']['type'] ); - $this->assertEquals( 'http://wikiba.se/ontology#Dump', $result[0]['x']['value'] ); - $this->assertEquals( 'literal', $result[1]['z']['type'] ); - $this->assertEquals( '0.1.0', $result[1]['z']['value'] ); - $this->assertNull( $result[1]['y'] ); - } - - /** - * @expectedException \Mediawiki\Sparql\SparqlException - */ - public function testBadQuery() { - $request = $this->getMockBuilder( MWHttpRequest::class )->disableOriginalConstructor()->getMock(); - $client = new SparqlClient( 'http://acme.test/', $this->getRequestFactory( $request ) ); - - $request->method( 'execute' )->willReturn( \Status::newFatal( "Bad query" ) ); - $result = $client->query( "TEST SPARQL 3" ); - } - - public function optionsProvider() { - return [ - 'defaults' => [ - 'TEST тест SPARQL 4 ', - null, - null, - [ - 'http://acme.test/', - 'query=TEST+%D1%82%D0%B5%D1%81%D1%82+SPARQL+4+', - 'format=json', - 'maxQueryTimeMillis=30000', - ], - [ - 'method' => 'GET', - 'userAgent' => Http::userAgent() . " SparqlClient", - 'timeout' => 30 - ] - ], - 'big query' => [ - str_repeat( 'ZZ', SparqlClient::MAX_GET_SIZE ), - null, - null, - [ - 'format=json', - 'maxQueryTimeMillis=30000', - ], - [ - 'method' => 'POST', - 'postData' => 'query=' . str_repeat( 'ZZ', SparqlClient::MAX_GET_SIZE ), - ] - ], - 'timeout 1s' => [ - 'TEST SPARQL 4', - null, - 1, - [ - 'maxQueryTimeMillis=1000', - ], - [ - 'timeout' => 1 - ] - ], - 'more options' => [ - 'TEST SPARQL 5', - [ - 'userAgent' => 'My Test', - 'randomOption' => 'duck', - ], - null, - [], - [ - 'userAgent' => 'My Test', - 'randomOption' => 'duck', - ] - ], - - ]; - } - - /** - * @dataProvider optionsProvider - * @param string $sparql - * @param array|null $options - * @param int|null $timeout - * @param array $expectedUrl - * @param array $expectedOptions - */ - public function testOptions( $sparql, $options, $timeout, $expectedUrl, $expectedOptions ) { - $requestFactory = $this->getMock( HttpRequestFactory::class ); - $client = new SparqlClient( 'http://acme.test/', $requestFactory ); - - $request = $this->getRequestMock( '{}' ); - - $requestFactory->method( 'create' )->willReturnCallback( - function ( $url, $options ) use ( $request, $expectedUrl, $expectedOptions ) { - foreach ( $expectedUrl as $eurl ) { - $this->assertContains( $eurl, $url ); - } - foreach ( $expectedOptions as $ekey => $evalue ) { - $this->assertArrayHasKey( $ekey, $options ); - $this->assertEquals( $options[$ekey], $evalue ); - } - return $request; - } - ); - - if ( !is_null( $options ) ) { - $client->setClientOptions( $options ); - } - if ( !is_null( $timeout ) ) { - $client->setTimeout( $timeout ); - } - - $result = $client->query( $sparql ); - } - -} diff --git a/tests/phpunit/includes/specials/ImageListPagerTest.php b/tests/phpunit/includes/specials/ImageListPagerTest.php deleted file mode 100644 index 10c6d04c9e..0000000000 --- a/tests/phpunit/includes/specials/ImageListPagerTest.php +++ /dev/null @@ -1,21 +0,0 @@ -formatValue( 'invalid_field', 'invalid_value' ); - } -} diff --git a/tests/phpunit/includes/specials/SpecialUploadTest.php b/tests/phpunit/includes/specials/SpecialUploadTest.php deleted file mode 100644 index 95026c18d0..0000000000 --- a/tests/phpunit/includes/specials/SpecialUploadTest.php +++ /dev/null @@ -1,29 +0,0 @@ -assertEquals( $expected, $result ); - } - - public function provideGetInitialPageText() { - return [ - [ - 'expect' => "== Summary ==\nthis is a test\n", - 'params' => [ - 'this is a test' - ], - ], - [ - 'expect' => "== Summary ==\nthis is a test\n", - 'params' => [ - "== Summary ==\nthis is a test", - ], - ], - ]; - } -} diff --git a/tests/phpunit/includes/specials/UncategorizedCategoriesPageTest.php b/tests/phpunit/includes/specials/UncategorizedCategoriesPageTest.php deleted file mode 100644 index 80bd365f35..0000000000 --- a/tests/phpunit/includes/specials/UncategorizedCategoriesPageTest.php +++ /dev/null @@ -1,63 +0,0 @@ -getMockBuilder( RequestContext::class )->getMock(); - $mockContext->method( 'msg' )->willReturn( $msg ); - $special = new UncategorizedCategoriesPage(); - $special->setContext( $mockContext ); - $this->assertEquals( [ - 'tables' => [ - 0 => 'page', - 1 => 'categorylinks', - ], - 'fields' => [ - 'namespace' => 'page_namespace', - 'title' => 'page_title', - 'value' => 'page_title', - ], - 'conds' => [ - 0 => 'cl_from IS NULL', - 'page_namespace' => 14, - 'page_is_redirect' => 0, - ] + $expected, - 'join_conds' => [ - 'categorylinks' => [ - 0 => 'LEFT JOIN', - 1 => 'cl_from = page_id', - ], - ], - ], $special->getQueryInfo() ); - } - - public function provideTestGetQueryInfoData() { - return [ - [ - "* Stubs\n* Test\n* *\n* * test123", - [ 1 => "page_title not in ( 'Stubs','Test','*','*_test123' )" ] - ], - [ - "Stubs\n* Test\n* *\n* * test123", - [ 1 => "page_title not in ( 'Test','*','*_test123' )" ] - ], - [ - "* StubsTest\n* *\n* * test123", - [ 1 => "page_title not in ( 'StubsTest','*','*_test123' )" ] - ], - [ "", [] ], - [ "\n\n\n", [] ], - [ "\n", [] ], - [ "Test\n*Test2", [ 1 => "page_title not in ( 'Test2' )" ] ], - [ "Test", [] ], - [ "*Test\nTest2", [ 1 => "page_title not in ( 'Test' )" ] ], - [ "Test\nTest2", [] ], - ]; - } -} diff --git a/tests/phpunit/includes/tidy/RemexDriverTest.php b/tests/phpunit/includes/tidy/RemexDriverTest.php deleted file mode 100644 index 5ad8416b81..0000000000 --- a/tests/phpunit/includes/tidy/RemexDriverTest.php +++ /dev/null @@ -1,326 +0,0 @@ -x

    " - ], - [ - 'No p-wrap of blank node', - " ", - " " - ], - [ - 'p-wrap terminated by div', - "x
    ", - "

    x

    " - ], - [ - 'p-wrap not terminated by span', - "x", - "

    x

    " - ], - [ - 'An element is non-blank and so gets p-wrapped', - "", - "

    " - ], - [ - 'The blank flag is set after a block-level element', - "
    ", - "
    " - ], - [ - 'Blank detection between two block-level elements', - "
    ", - "
    " - ], - [ - 'But p-wrapping of non-blank content works after an element', - "
    x", - "

    x

    " - ], - [ - 'p-wrapping between two block-level elements', - "
    x
    ", - "

    x

    " - ], - [ - 'p-wrap inside blockquote', - "
    x
    ", - "

    x

    " - ], - [ - 'A comment is blank for p-wrapping purposes', - "", - "" - ], - [ - 'A comment is blank even when a p-wrap was opened by a text node', - " ", - " " - ], - [ - 'A comment does not open a p-wrap', - "x", - "

    x

    " - ], - [ - 'A comment does not close a p-wrap', - "x", - "

    x

    " - ], - [ - 'Empty li', - "
    ", - "
    " - ], - [ - 'li with element', - "
    ", - "
    " - ], - [ - 'li with text', - "
    • x
    ", - "
    • x
    " - ], - [ - 'Empty tr', - "
    ", - "
    " - ], - [ - 'Empty p', - "

    \n

    ", - "

    \n

    " - ], - [ - 'No p-wrapping of an inline element which contains a block element (T150317)', - "
    x
    ", - "
    x
    " - ], - [ - 'p-wrapping of an inline element which contains an inline element', - "x", - "

    x

    " - ], - [ - 'p-wrapping is enabled in a blockquote in an inline element', - "
    x
    ", - "

    x

    " - ], - [ - 'All bare text should be p-wrapped even when surrounded by block tags', - "
    x
    y
    z", - "

    x

    y

    z

    " - ], - [ - 'Split tag stack 1', - "x
    y
    z
    ", - "

    x

    y

    z

    " - ], - [ - 'Split tag stack 2', - "
    y
    z
    ", - "
    y

    z

    " - ], - [ - 'Split tag stack 3', - "x
    y
    ", - "

    x

    y
    " - ], - [ - 'Split tag stack 4 (modified to use splittable tag)', - "abc
    d
    e
    ", - "

    abc

    d

    e

    " - ], - [ - "Split tag stack regression check 1", - "x
    y
    ", - "

    x

    y
    " - ], - [ - "Split tag stack regression check 2 (modified to use splittable tag)", - "a
    d
    e
    ", - "

    a

    d

    e

    " - ], - // Simple tests from pwrap.js - [ - 'Simple pwrap test 1', - 'a', - '

    a

    ' - ], - [ - ' is not a splittable tag, but gets p-wrapped in simple wrapping scenarios', - 'a', - '

    a

    ' - ], - [ - 'Simple pwrap test 3', - 'x
    a
    b
    y', - '

    x

    a
    b

    y

    ' - ], - [ - 'Simple pwrap test 4', - 'x
    a
    b
    y', - '

    x

    a
    b

    y

    ' - ], - // Complex tests from pwrap.js - [ - 'Complex pwrap test 1', - 'x
    a
    y
    ', - '

    x

    a

    y

    ' - ], - [ - 'Complex pwrap test 2', - 'abc
    d
    e
    f', - '

    abc

    d

    ef

    ' - ], - [ - 'Complex pwrap test 3', - 'abc
    d
    e
    ', - '

    abc

    d

    e

    ' - ], - [ - 'Complex pwrap test 4', - 'x
    y
    ', - '

    x

    y
    ' - ], - [ - 'Complex pwrap test 5', - 'a
    d
    e
    ', - '

    a

    d

    e

    ' - ], - // phpcs:disable Generic.Files.LineLength - [ - 'Complex pwrap test 6', - 'a
    b
    cd
    e
    f
    g
    ', - // PHP 5 does not allow concatenation in initialisation of a class static variable - '

    a

    b

    cd

    e

    fg

    ' - ], - // phpcs:enable - /* FIXME the second causes a stack split which clones the even - * though no

    is actually generated - [ - 'Complex pwrap test 7', - '

    x
    y
    z
    ', - '
    x
    y
    z
    ' - ], - */ - // New local tests - [ - 'Blank text node after block end', - 'x
    y
    z
    ', - '

    x

    y

    z

    ' - ], - [ - 'Text node fostering (FIXME: wrap missing)', - 'x
    ', - 'x
    ' - ], - [ - 'Blockquote fostering', - '
    x
    ', - '

    x

    ' - ], - [ - 'Block element fostering', - '
    x', - '
    x
    ' - ], - [ - 'Formatting element fostering (FIXME: wrap missing)', - 'x', - 'x
    ' - ], - [ - 'AAA clone of p-wrapped element (FIXME: empty b)', - 'x

    yz

    ', - '

    x

    yz

    ', - ], - [ - 'AAA with fostering (FIXME: wrap missing)', - '1

    23

    ', - '1

    23

    ' - ], - [ - 'AAA causes reparent of p-wrapped text node (T178632)', - '
    x
    ', - '

    x

    ', - ], - [ - 'p-wrap ended by reparenting (T200827)', - '

    ', - '

    ', - ], - [ - 'style tag isn\'t p-wrapped (T186965)', - '', - '', - ], - [ - 'link tag isn\'t p-wrapped (T186965)', - '', - '', - ], - [ - 'style tag doesn\'t split p-wrapping (T208901)', - 'foo bar', - '

    foo bar

    ', - ], - [ - 'link tag doesn\'t split p-wrapping (T208901)', - 'foo bar', - '

    foo bar

    ', - ], - ]; - - public function provider() { - return self::$remexTidyTestData; - } - - /** - * @dataProvider provider - * @covers MediaWiki\Tidy\RemexCompatFormatter - * @covers MediaWiki\Tidy\RemexCompatMunger - * @covers MediaWiki\Tidy\RemexDriver - * @covers MediaWiki\Tidy\RemexMungerData - */ - public function testTidy( $desc, $input, $expected ) { - $r = new MediaWiki\Tidy\RemexDriver( [] ); - $result = $r->tidy( $input ); - $this->assertEquals( $expected, $result, $desc ); - } - - public function html5libProvider() { - $files = json_decode( file_get_contents( __DIR__ . '/html5lib-tests.json' ), true ); - $tests = []; - foreach ( $files as $file => $fileTests ) { - foreach ( $fileTests as $i => $test ) { - $tests[] = [ "$file:$i", $test['data'] ]; - } - } - return $tests; - } - - /** - * This is a quick and dirty test to make sure none of the html5lib tests - * generate exceptions. We don't really know what the expected output is. - * - * @dataProvider html5libProvider - * @coversNothing - */ - public function testHtml5Lib( $desc, $input ) { - $r = new MediaWiki\Tidy\RemexDriver( [] ); - $result = $r->tidy( $input ); - $this->assertTrue( true, $desc ); - } -} diff --git a/tests/phpunit/includes/tidy/html5lib-tests.json b/tests/phpunit/includes/tidy/html5lib-tests.json deleted file mode 100644 index 2b1c3e8cdf..0000000000 --- a/tests/phpunit/includes/tidy/html5lib-tests.json +++ /dev/null @@ -1,80692 +0,0 @@ -{ - "adoption01.dat": [ - { - "data": "

    ", - "errors": [ - "(1,3): expected-doctype-but-got-start-tag", - "(1,10): adoption-agency-1.3" - ], - "document": { - "props": { - "tags": { - "html": true, - "head": true, - "body": true, - "a": true, - "p": true - } - }, - "tree": [ - { - "tag": "html", - "children": [ - { - "tag": "head" - }, - { - "tag": "body", - "children": [ - { - "tag": "a" - }, - { - "tag": "p", - "children": [ - { - "tag": "a" - } - ] - } - ] - } - ] - } - ], - "html": "

    ", - "noQuirksBodyHtml": "

    " - } - }, - { - "data": "1

    23

    ", - "errors": [ - "(1,3): expected-doctype-but-got-start-tag", - "(1,12): adoption-agency-1.3" - ], - "document": { - "props": { - "tags": { - "html": true, - "head": true, - "body": true, - "a": true, - "p": true - } - }, - "tree": [ - { - "tag": "html", - "children": [ - { - "tag": "head" - }, - { - "tag": "body", - "children": [ - { - "tag": "a", - "children": [ - { - "text": "1" - } - ] - }, - { - "tag": "p", - "children": [ - { - "tag": "a", - "children": [ - { - "text": "2" - } - ] - }, - { - "text": "3" - } - ] - } - ] - } - ] - } - ], - "html": "1

    23

    ", - "noQuirksBodyHtml": "1

    23

    " - } - }, - { - "data": "1", - "errors": [ - "(1,3): expected-doctype-but-got-start-tag", - "(1,17): adoption-agency-1.3" - ], - "document": { - "props": { - "tags": { - "html": true, - "head": true, - "body": true, - "a": true, - "button": true - } - }, - "tree": [ - { - "tag": "html", - "children": [ - { - "tag": "head" - }, - { - "tag": "body", - "children": [ - { - "tag": "a", - "children": [ - { - "text": "1" - } - ] - }, - { - "tag": "button", - "children": [ - { - "tag": "a", - "children": [ - { - "text": "2" - } - ] - }, - { - "text": "3" - } - ] - } - ] - } - ] - } - ], - "html": "1", - "noQuirksBodyHtml": "1" - } - }, - { - "data": "123", - "errors": [ - "(1,3): expected-doctype-but-got-start-tag", - "(1,12): adoption-agency-1.3" - ], - "document": { - "props": { - "tags": { - "html": true, - "head": true, - "body": true, - "a": true, - "b": true - } - }, - "tree": [ - { - "tag": "html", - "children": [ - { - "tag": "head" - }, - { - "tag": "body", - "children": [ - { - "tag": "a", - "children": [ - { - "text": "1" - }, - { - "tag": "b", - "children": [ - { - "text": "2" - } - ] - } - ] - }, - { - "tag": "b", - "children": [ - { - "text": "3" - } - ] - } - ] - } - ] - } - ], - "html": "123", - "noQuirksBodyHtml": "123" - } - }, - { - "data": "1
    2
    34
    5
    ", - "errors": [ - "(1,3): expected-doctype-but-got-start-tag", - "(1,20): adoption-agency-1.3", - "(1,20): adoption-agency-1.3" - ], - "document": { - "props": { - "tags": { - "html": true, - "head": true, - "body": true, - "a": true, - "div": true - } - }, - "tree": [ - { - "tag": "html", - "children": [ - { - "tag": "head" - }, - { - "tag": "body", - "children": [ - { - "tag": "a", - "children": [ - { - "text": "1" - } - ] - }, - { - "tag": "div", - "children": [ - { - "tag": "a", - "children": [ - { - "text": "2" - } - ] - }, - { - "tag": "div", - "children": [ - { - "tag": "a", - "children": [ - { - "text": "3" - } - ] - }, - { - "text": "4" - } - ] - }, - { - "text": "5" - } - ] - } - ] - } - ] - } - ], - "html": "1
    2
    34
    5
    ", - "noQuirksBodyHtml": "1
    2
    34
    5
    " - } - }, - { - "data": "1

    23

    ", - "errors": [ - "(1,7): expected-doctype-but-got-start-tag", - "(1,10): unexpected-start-tag-implies-table-voodoo", - "(1,11): unexpected-character-implies-table-voodoo", - "(1,14): unexpected-start-tag-implies-table-voodoo", - "(1,15): unexpected-character-implies-table-voodoo", - "(1,19): unexpected-end-tag-implies-table-voodoo", - "(1,19): adoption-agency-1.3", - "(1,20): unexpected-character-implies-table-voodoo", - "(1,24): unexpected-end-tag-implies-table-voodoo", - "(1,24): eof-in-table" - ], - "document": { - "props": { - "tags": { - "html": true, - "head": true, - "body": true, - "a": true, - "p": true, - "table": true - } - }, - "tree": [ - { - "tag": "html", - "children": [ - { - "tag": "head" - }, - { - "tag": "body", - "children": [ - { - "tag": "a", - "children": [ - { - "text": "1" - } - ] - }, - { - "tag": "p", - "children": [ - { - "tag": "a", - "children": [ - { - "text": "2" - } - ] - }, - { - "text": "3" - } - ] - }, - { - "tag": "table" - } - ] - } - ] - } - ], - "html": "1

    23

    ", - "noQuirksBodyHtml": "1

    23

    " - } - }, - { - "data": "

    ", - "errors": [ - "(1,3): expected-doctype-but-got-start-tag", - "(1,16): adoption-agency-1.3", - "(1,16): expected-closing-tag-but-got-eof" - ], - "document": { - "props": { - "tags": { - "html": true, - "head": true, - "body": true, - "b": true, - "a": true, - "p": true - } - }, - "tree": [ - { - "tag": "html", - "children": [ - { - "tag": "head" - }, - { - "tag": "body", - "children": [ - { - "tag": "b", - "children": [ - { - "tag": "b", - "children": [ - { - "tag": "a" - }, - { - "tag": "p", - "children": [ - { - "tag": "a" - } - ] - } - ] - } - ] - } - ] - } - ] - } - ], - "html": "

    ", - "noQuirksBodyHtml": "

    " - } - }, - { - "data": "

    ", - "errors": [ - "(1,3): expected-doctype-but-got-start-tag", - "(1,16): adoption-agency-1.3", - "(1,16): expected-closing-tag-but-got-eof" - ], - "document": { - "props": { - "tags": { - "html": true, - "head": true, - "body": true, - "b": true, - "a": true, - "p": true - } - }, - "tree": [ - { - "tag": "html", - "children": [ - { - "tag": "head" - }, - { - "tag": "body", - "children": [ - { - "tag": "b", - "children": [ - { - "tag": "a", - "children": [ - { - "tag": "b" - } - ] - }, - { - "tag": "b", - "children": [ - { - "tag": "p", - "children": [ - { - "tag": "a" - } - ] - } - ] - } - ] - } - ] - } - ] - } - ], - "html": "

    ", - "noQuirksBodyHtml": "

    " - } - }, - { - "data": "

    ", - "errors": [ - "(1,3): expected-doctype-but-got-start-tag", - "(1,16): adoption-agency-1.3", - "(1,16): expected-closing-tag-but-got-eof" - ], - "document": { - "props": { - "tags": { - "html": true, - "head": true, - "body": true, - "a": true, - "b": true, - "p": true - } - }, - "tree": [ - { - "tag": "html", - "children": [ - { - "tag": "head" - }, - { - "tag": "body", - "children": [ - { - "tag": "a", - "children": [ - { - "tag": "b", - "children": [ - { - "tag": "b" - } - ] - } - ] - }, - { - "tag": "b", - "children": [ - { - "tag": "b", - "children": [ - { - "tag": "p", - "children": [ - { - "tag": "a" - } - ] - } - ] - } - ] - } - ] - } - ] - } - ], - "html": "

    ", - "noQuirksBodyHtml": "

    " - } - }, - { - "data": "

    123

    45", - "errors": [ - "(1,3): expected-doctype-but-got-start-tag", - "(1,30): unexpected-end-tag", - "(1,35): adoption-agency-1.3" - ], - "document": { - "props": { - "tags": { - "html": true, - "head": true, - "body": true, - "p": true, - "s": true, - "b": true - } - }, - "tree": [ - { - "tag": "html", - "children": [ - { - "tag": "head" - }, - { - "tag": "body", - "children": [ - { - "tag": "p", - "children": [ - { - "text": "1" - }, - { - "tag": "s", - "attrs": [ - { - "name": "id", - "value": "A" - } - ], - "children": [ - { - "text": "2" - }, - { - "tag": "b", - "attrs": [ - { - "name": "id", - "value": "B" - } - ], - "children": [ - { - "text": "3" - } - ] - } - ] - } - ] - }, - { - "tag": "s", - "attrs": [ - { - "name": "id", - "value": "A" - } - ], - "children": [ - { - "tag": "b", - "attrs": [ - { - "name": "id", - "value": "B" - } - ], - "children": [ - { - "text": "4" - } - ] - } - ] - }, - { - "tag": "b", - "attrs": [ - { - "name": "id", - "value": "B" - } - ], - "children": [ - { - "text": "5" - } - ] - } - ] - } - ] - } - ], - "html": "

    123

    45", - "noQuirksBodyHtml": "

    123

    45" - } - }, - { - "data": "13
    2
    ", - "errors": [ - "(1,7): expected-doctype-but-got-start-tag", - "(1,10): unexpected-start-tag-implies-table-voodoo", - "(1,11): unexpected-character-implies-table-voodoo", - "(1,15): unexpected-cell-in-table-body", - "(1,30): unexpected-implied-end-tag-in-table-view" - ], - "document": { - "props": { - "tags": { - "html": true, - "head": true, - "body": true, - "a": true, - "table": true, - "tbody": true, - "tr": true, - "td": true - } - }, - "tree": [ - { - "tag": "html", - "children": [ - { - "tag": "head" - }, - { - "tag": "body", - "children": [ - { - "tag": "a", - "children": [ - { - "text": "1" - } - ] - }, - { - "tag": "a", - "children": [ - { - "text": "3" - } - ] - }, - { - "tag": "table", - "children": [ - { - "tag": "tbody", - "children": [ - { - "tag": "tr", - "children": [ - { - "tag": "td", - "children": [ - { - "text": "2" - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ], - "html": "13
    2
    ", - "noQuirksBodyHtml": "13
    2
    " - } - }, - { - "data": "AC
    B
    ", - "errors": [ - "(1,7): expected-doctype-but-got-start-tag", - "(1,8): unexpected-character-implies-table-voodoo", - "(1,12): unexpected-cell-in-table-body", - "(1,22): unexpected-character-implies-table-voodoo" - ], - "document": { - "props": { - "tags": { - "html": true, - "head": true, - "body": true, - "table": true, - "tbody": true, - "tr": true, - "td": true - } - }, - "tree": [ - { - "tag": "html", - "children": [ - { - "tag": "head" - }, - { - "tag": "body", - "children": [ - { - "text": "AC" - }, - { - "tag": "table", - "children": [ - { - "tag": "tbody", - "children": [ - { - "tag": "tr", - "children": [ - { - "tag": "td", - "children": [ - { - "text": "B" - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ], - "html": "AC
    B
    ", - "noQuirksBodyHtml": "AC
    B
    " - } - }, - { - "data": "
    ", - "errors": [ - "(1,3): expected-doctype-but-got-start-tag", - "(1,23): unexpected-end-tag", - "(1,23): adoption-agency-1.3" - ], - "document": { - "props": { - "tags": { - "html": true, - "head": true, - "body": true, - "a": true, - "svg svg": true, - "svg tr": true, - "svg input": true - } - }, - "tree": [ - { - "tag": "html", - "children": [ - { - "tag": "head" - }, - { - "tag": "body", - "children": [ - { - "tag": "a", - "children": [ - { - "tag": "svg", - "ns": "http://www.w3.org/2000/svg", - "children": [ - { - "tag": "tr", - "ns": "http://www.w3.org/2000/svg", - "children": [ - { - "tag": "input", - "ns": "http://www.w3.org/2000/svg" - } - ] - } - ] - } - ] - } - ] - } - ] - } - ], - "html": "
    ", - "noQuirksBodyHtml": "
    " - } - }, - { - "data": "
    ", - "errors": [ - "(1,5): expected-doctype-but-got-start-tag", - "(1,65): adoption-agency-1.3", - "(1,65): adoption-agency-1.3", - "(1,65): adoption-agency-1.3", - "(1,65): adoption-agency-1.3", - "(1,65): adoption-agency-1.3", - "(1,65): adoption-agency-1.3", - "(1,65): adoption-agency-1.3", - "(1,65): adoption-agency-1.3", - "(1,65): expected-closing-tag-but-got-eof" - ], - "document": { - "props": { - "tags": { - "html": true, - "head": true, - "body": true, - "div": true, - "a": true, - "b": true - } - }, - "tree": [ - { - "tag": "html", - "children": [ - { - "tag": "head" - }, - { - "tag": "body", - "children": [ - { - "tag": "div", - "children": [ - { - "tag": "a", - "children": [ - { - "tag": "b" - } - ] - }, - { - "tag": "b", - "children": [ - { - "tag": "div", - "children": [ - { - "tag": "a" - }, - { - "tag": "div", - "children": [ - { - "tag": "a" - }, - { - "tag": "div", - "children": [ - { - "tag": "a" - }, - { - "tag": "div", - "children": [ - { - "tag": "a" - }, - { - "tag": "div", - "children": [ - { - "tag": "a" - }, - { - "tag": "div", - "children": [ - { - "tag": "a" - }, - { - "tag": "div", - "children": [ - { - "tag": "a" - }, - { - "tag": "div", - "children": [ - { - "tag": "a", - "children": [ - { - "tag": "div", - "children": [ - { - "tag": "div" - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ], - "html": "
    ", - "noQuirksBodyHtml": "
    " - } - }, - { - "data": "
    ", - "errors": [ - "(1,5): expected-doctype-but-got-start-tag", - "(1,32): adoption-agency-1.3", - "(1,32): expected-closing-tag-but-got-eof" - ], - "document": { - "props": { - "tags": { - "html": true, - "head": true, - "body": true, - "div": true, - "a": true, - "b": true, - "u": true, - "i": true, - "code": true - } - }, - "tree": [ - { - "tag": "html", - "children": [ - { - "tag": "head" - }, - { - "tag": "body", - "children": [ - { - "tag": "div", - "children": [ - { - "tag": "a", - "children": [ - { - "tag": "b", - "children": [ - { - "tag": "u", - "children": [ - { - "tag": "i", - "children": [ - { - "tag": "code" - } - ] - } - ] - } - ] - } - ] - }, - { - "tag": "u", - "children": [ - { - "tag": "i", - "children": [ - { - "tag": "code", - "children": [ - { - "tag": "div", - "children": [ - { - "tag": "a" - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ], - "html": "
    ", - "noQuirksBodyHtml": "
    " - } - }, - { - "data": "xy", - "errors": [ - "(1,3): expected-doctype-but-got-start-tag" - ], - "document": { - "props": { - "tags": { - "html": true, - "head": true, - "body": true, - "b": true - } - }, - "tree": [ - { - "tag": "html", - "children": [ - { - "tag": "head" - }, - { - "tag": "body", - "children": [ - { - "tag": "b", - "children": [ - { - "tag": "b", - "children": [ - { - "tag": "b", - "children": [ - { - "tag": "b", - "children": [ - { - "text": "x" - } - ] - } - ] - } - ] - } - ] - }, - { - "text": "y" - } - ] - } - ] - } - ], - "html": "xy", - "noQuirksBodyHtml": "xy" - } - }, - { - "data": "

    x", - "errors": [ - "(1,3): expected-doctype-but-got-start-tag", - "(1,18): unexpected-end-tag", - "(1,19): expected-closing-tag-but-got-eof" - ], - "document": { - "props": { - "tags": { - "html": true, - "head": true, - "body": true, - "p": true, - "b": true - } - }, - "tree": [ - { - "tag": "html", - "children": [ - { - "tag": "head" - }, - { - "tag": "body", - "children": [ - { - "tag": "p", - "children": [ - { - "tag": "b", - "children": [ - { - "tag": "b", - "children": [ - { - "tag": "b", - "children": [ - { - "tag": "b" - } - ] - } - ] - } - ] - } - ] - }, - { - "tag": "p", - "children": [ - { - "tag": "b", - "children": [ - { - "tag": "b", - "children": [ - { - "tag": "b", - "children": [ - { - "text": "x" - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ], - "html": "

    x

    ", - "noQuirksBodyHtml": "

    x

    " - } - }, - { - "data": "