From f6d65e533c62f6deb21342d4901ece24497b433e Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Thu, 4 Jun 2015 07:31:04 +0200 Subject: Update to MediaWiki 1.25.1 --- tests/phpunit/includes/ArrayUtilsTest.php | 311 ---- tests/phpunit/includes/ArticleTablesTest.php | 53 - tests/phpunit/includes/ArticleTest.php | 95 -- tests/phpunit/includes/BlockTest.php | 12 +- tests/phpunit/includes/EditPageTest.php | 7 +- tests/phpunit/includes/ExternalStoreTest.php | 87 -- tests/phpunit/includes/GitInfoTest.php | 2 +- .../includes/GlobalFunctions/GlobalTest.php | 78 +- .../includes/GlobalFunctions/wfAppendQueryTest.php | 67 + .../GlobalFunctions/wfEscapeShellArgTest.php | 43 + .../GlobalFunctions/wfThumbIsStandardTest.php | 104 ++ tests/phpunit/includes/HtmlFormatterTest.php | 14 + tests/phpunit/includes/HtmlTest.php | 28 +- tests/phpunit/includes/HttpTest.php | 321 +++- tests/phpunit/includes/ImagePage404Test.php | 53 - tests/phpunit/includes/ImagePageTest.php | 90 -- tests/phpunit/includes/ImportTest.php | 68 +- tests/phpunit/includes/LanguageConverterTest.php | 187 --- tests/phpunit/includes/LinkerTest.php | 56 +- tests/phpunit/includes/LinksUpdateTest.php | 266 ---- tests/phpunit/includes/LocalFileTest.php | 184 --- tests/phpunit/includes/MWFunctionTest.php | 33 - tests/phpunit/includes/MWTimestampTest.php | 26 + .../includes/MediaWikiVersionFetcherTest.php | 1 - tests/phpunit/includes/MessageTest.php | 407 +++-- tests/phpunit/includes/MovePageTest.php | 63 + tests/phpunit/includes/OutputPageTest.php | 45 +- tests/phpunit/includes/PasswordTest.php | 33 - tests/phpunit/includes/PrefixSearchTest.php | 306 ++++ tests/phpunit/includes/RequestContextTest.php | 96 -- tests/phpunit/includes/SampleTest.php | 2 +- tests/phpunit/includes/SanitizerTest.php | 29 +- tests/phpunit/includes/SpecialPageTest.php | 105 -- tests/phpunit/includes/StatusTest.php | 15 +- tests/phpunit/includes/TemplateParserTest.php | 63 + tests/phpunit/includes/TestUser.php | 126 +- tests/phpunit/includes/TestingAccessWrapper.php | 50 + .../phpunit/includes/TestingAccessWrapperTest.php | 34 + tests/phpunit/includes/TitleMethodsTest.php | 41 +- tests/phpunit/includes/TitlePermissionTest.php | 28 +- tests/phpunit/includes/TitleTest.php | 54 +- tests/phpunit/includes/UserTest.php | 91 +- tests/phpunit/includes/WikiPageTest.php | 1301 ---------------- .../includes/WikiPageTestContentHandlerUseDB.php | 61 - tests/phpunit/includes/XmlSelectTest.php | 2 +- tests/phpunit/includes/XmlTest.php | 12 +- tests/phpunit/includes/XmlTypeCheckTest.php | 49 - tests/phpunit/includes/actions/ActionTest.php | 12 +- .../includes/api/ApiContinuationManagerTest.php | 195 +++ .../phpunit/includes/api/ApiErrorFormatterTest.php | 351 +++++ tests/phpunit/includes/api/ApiLoginTest.php | 3 +- tests/phpunit/includes/api/ApiMainTest.php | 68 +- tests/phpunit/includes/api/ApiMessageTest.php | 103 ++ tests/phpunit/includes/api/ApiOptionsTest.php | 28 +- tests/phpunit/includes/api/ApiResultTest.php | 1563 ++++++++++++++++++++ tests/phpunit/includes/api/ApiTestCase.php | 24 +- tests/phpunit/includes/api/ApiTestCaseUpload.php | 22 +- tests/phpunit/includes/api/ApiUploadTest.php | 65 +- tests/phpunit/includes/api/MockApi.php | 3 - tests/phpunit/includes/api/MockApiQueryBase.php | 8 +- .../phpunit/includes/api/PrefixUniquenessTest.php | 2 +- .../includes/api/format/ApiFormatDbgTest.php | 55 + .../includes/api/format/ApiFormatDumpTest.php | 63 + .../includes/api/format/ApiFormatJsonTest.php | 106 +- .../includes/api/format/ApiFormatNoneTest.php | 38 +- .../includes/api/format/ApiFormatPhpTest.php | 139 +- .../includes/api/format/ApiFormatTestBase.php | 70 +- .../includes/api/format/ApiFormatTxtTest.php | 55 + .../includes/api/format/ApiFormatWddxTest.php | 74 +- .../includes/api/format/ApiFormatXmlTest.php | 119 ++ .../includes/api/query/ApiQueryBasicTest.php | 2 - .../includes/api/query/ApiQueryContinue2Test.php | 5 +- .../includes/api/query/ApiQueryContinueTest.php | 26 +- .../api/query/ApiQueryContinueTestBase.php | 5 +- tests/phpunit/includes/api/query/ApiQueryTest.php | 56 +- .../includes/api/query/ApiQueryTestBase.php | 22 +- tests/phpunit/includes/cache/GenderCacheTest.php | 8 +- .../includes/cache/LocalisationCacheTest.php | 54 +- .../phpunit/includes/cache/RedisBloomCacheTest.php | 71 - .../includes/changes/EnhancedChangesListTest.php | 13 +- .../includes/changes/OldChangesListTest.php | 1 - .../includes/changes/RCCacheEntryFactoryTest.php | 1 - .../phpunit/includes/changes/RecentChangeTest.php | 65 +- .../includes/changes/TestRecentChangesHelper.php | 1 - .../composer/ComposerVersionNormalizerTest.php | 1 - .../includes/config/GlobalVarConfigTest.php | 24 - tests/phpunit/includes/config/HashConfigTest.php | 2 +- .../includes/content/ContentHandlerTest.php | 15 +- tests/phpunit/includes/content/JsonContentTest.php | 142 +- tests/phpunit/includes/content/TextContentTest.php | 21 +- .../includes/context/RequestContextTest.php | 96 ++ .../phpunit/includes/db/DatabaseMysqlBaseTest.php | 1 - tests/phpunit/includes/db/DatabaseSQLTest.php | 80 + tests/phpunit/includes/db/DatabaseSqliteTest.php | 45 +- tests/phpunit/includes/db/LBFactoryTest.php | 1 - tests/phpunit/includes/db/ORMRowTest.php | 1 - tests/phpunit/includes/db/ORMTableTest.php | 19 +- tests/phpunit/includes/db/TestORMRowTest.php | 37 +- tests/phpunit/includes/debug/MWDebugTest.php | 5 +- .../includes/debug/logging/LegacyLoggerTest.php | 122 ++ .../phpunit/includes/deferred/LinksUpdateTest.php | 266 ++++ .../phpunit/includes/deferred/SearchUpdateTest.php | 81 + .../includes/diff/ArrayDiffFormatterTest.php | 1 - tests/phpunit/includes/diff/DiffOpTest.php | 5 - tests/phpunit/includes/diff/DiffTest.php | 1 - .../phpunit/includes/diff/DifferenceEngineTest.php | 1 - .../includes/exception/BadTitleErrorTest.php | 17 +- .../includes/exception/ErrorPageErrorTest.php | 24 +- .../includes/exception/MWExceptionHandlerTest.php | 2 +- .../includes/exception/ThrottledErrorTest.php | 17 +- .../includes/externalstore/ExternalStoreTest.php | 87 ++ .../includes/filebackend/FileBackendTest.php | 68 +- tests/phpunit/includes/filerepo/StoreBatchTest.php | 19 +- .../includes/filerepo/file/LocalFileTest.php | 183 +++ .../includes/installer/DatabaseUpdaterTest.php | 279 ++++ .../includes/installer/InstallDocFormatterTest.php | 15 + tests/phpunit/includes/jobqueue/JobQueueTest.php | 9 +- tests/phpunit/includes/jobqueue/JobTest.php | 67 + tests/phpunit/includes/json/FormatJsonTest.php | 116 +- tests/phpunit/includes/libs/ArrayUtilsTest.php | 311 ++++ tests/phpunit/includes/libs/CSSMinTest.php | 49 +- .../includes/libs/DeferredStringifierTest.php | 50 + .../includes/libs/GenericArrayObjectTest.php | 3 +- tests/phpunit/includes/libs/HashRingTest.php | 2 +- tests/phpunit/includes/libs/IEUrlExtensionTest.php | 2 +- tests/phpunit/includes/libs/IPSetTest.php | 2 +- .../includes/libs/JavaScriptMinifierTest.php | 11 +- tests/phpunit/includes/libs/MWMessagePackTest.php | 2 +- tests/phpunit/includes/libs/ObjectFactoryTest.php | 60 + .../phpunit/includes/libs/ProcessCacheLRUTest.php | 50 +- tests/phpunit/includes/libs/RunningStatTest.php | 2 +- tests/phpunit/includes/libs/StringUtilsTest.php | 149 ++ tests/phpunit/includes/libs/XhprofTest.php | 320 ++++ tests/phpunit/includes/libs/XmlTypeCheckTest.php | 49 + .../includes/libs/composer/ComposerJsonTest.php | 57 + .../includes/libs/composer/ComposerLockTest.php | 62 + .../phpunit/includes/logging/LogFormatterTest.php | 53 + tests/phpunit/includes/mail/MailAddressTest.php | 7 +- tests/phpunit/includes/media/BitmapScalingTest.php | 4 +- .../phpunit/includes/media/FormatMetadataTest.php | 32 + tests/phpunit/includes/media/MediaHandlerTest.php | 82 +- .../includes/media/SVGMetadataExtractorTest.php | 5 - tests/phpunit/includes/normal/CleanUpTest.php | 409 ----- .../phpunit/includes/objectcache/BagOStuffTest.php | 14 +- tests/phpunit/includes/page/ArticleTablesTest.php | 53 + tests/phpunit/includes/page/ArticleTest.php | 95 ++ tests/phpunit/includes/page/ImagePage404Test.php | 53 + tests/phpunit/includes/page/ImagePageTest.php | 90 ++ tests/phpunit/includes/page/WikiPageTest.php | 1301 ++++++++++++++++ .../page/WikiPageTestContentHandlerUseDB.php | 61 + tests/phpunit/includes/parser/NewParserTest.php | 38 +- tests/phpunit/includes/parser/ParserOutputTest.php | 5 + tests/phpunit/includes/parser/TagHooksTest.php | 2 +- tests/phpunit/includes/password/PasswordTest.php | 39 + .../phpunit/includes/password/PasswordTestCase.php | 7 +- .../registration/ExtensionProcessorTest.php | 374 +++++ .../registration/ExtensionRegistryTest.php | 195 +++ .../ResourceLoaderFileModuleTest.php | 247 ++++ .../ResourceLoaderImageModuleTest.php | 162 ++ .../resourceloader/ResourceLoaderImageTest.php | 122 ++ .../resourceloader/ResourceLoaderModuleTest.php | 61 +- .../ResourceLoaderStartUpModuleTest.php | 388 +++++ .../ResourceLoaderStartupModuleTest.php | 388 ----- .../includes/resourceloader/ResourceLoaderTest.php | 185 ++- .../ResourceLoaderWikiModuleTest.php | 84 +- .../resourceloader/templates/template.html | 1 + .../resourceloader/templates/template2.html | 1 + .../templates/template_awesome.handlebars | 1 + tests/phpunit/includes/search/SearchEngineTest.php | 67 +- tests/phpunit/includes/search/SearchUpdateTest.php | 81 - .../phpunit/includes/site/CachingSiteStoreTest.php | 161 ++ tests/phpunit/includes/site/DBSiteStoreTest.php | 133 ++ .../includes/site/FileBasedSiteLookupTest.php | 101 ++ tests/phpunit/includes/site/HashSiteStoreTest.php | 105 ++ tests/phpunit/includes/site/MediaWikiSiteTest.php | 1 - tests/phpunit/includes/site/SiteExporterTest.php | 147 ++ tests/phpunit/includes/site/SiteImporterTest.php | 200 +++ tests/phpunit/includes/site/SiteImporterTest.xml | 19 + tests/phpunit/includes/site/SiteListTest.php | 1 - tests/phpunit/includes/site/SiteSQLStoreTest.php | 106 +- tests/phpunit/includes/site/SiteTest.php | 1 - .../includes/site/SitesCacheFileBuilderTest.php | 135 ++ tests/phpunit/includes/site/TestSites.php | 3 +- tests/phpunit/includes/skins/SkinTemplateTest.php | 1 - .../specialpage/SpecialPageFactoryTest.php | 62 +- .../includes/specialpage/SpecialPageTest.php | 104 ++ .../includes/specialpage/SpecialPageTestHelper.php | 24 + .../includes/specials/SpecialBooksourcesTest.php | 36 + .../includes/specials/SpecialMIMESearchTest.php | 4 +- tests/phpunit/includes/title/ForeignTitleTest.php | 103 ++ .../title/MediaWikiPageLinkRendererTest.php | 1 - .../includes/title/MediaWikiTitleCodecTest.php | 11 +- .../title/NaiveForeignTitleFactoryTest.php | 91 ++ .../includes/title/NaiveImportTitleFactoryTest.php | 89 ++ .../NamespaceAwareForeignTitleFactoryTest.php | 89 ++ .../title/NamespaceImportTitleFactoryTest.php | 77 + .../title/SubpageImportTitleFactoryTest.php | 86 ++ tests/phpunit/includes/title/TitleValueTest.php | 1 - tests/phpunit/includes/upload/UploadBaseTest.php | 41 +- .../phpunit/includes/upload/UploadFromUrlTest.php | 5 +- tests/phpunit/includes/utils/CdbTest.php | 90 -- tests/phpunit/includes/utils/IPTest.php | 2 +- tests/phpunit/includes/utils/MWCryptHKDFTest.php | 6 + tests/phpunit/includes/utils/MWFunctionTest.php | 34 + tests/phpunit/includes/utils/StringUtilsTest.php | 149 -- tests/phpunit/includes/utils/UIDGeneratorTest.php | 6 +- .../includes/utils/ZipDirectoryReaderTest.php | 2 +- 207 files changed, 13247 insertions(+), 5433 deletions(-) delete mode 100644 tests/phpunit/includes/ArrayUtilsTest.php delete mode 100644 tests/phpunit/includes/ArticleTablesTest.php delete mode 100644 tests/phpunit/includes/ArticleTest.php delete mode 100644 tests/phpunit/includes/ExternalStoreTest.php create mode 100644 tests/phpunit/includes/GlobalFunctions/wfAppendQueryTest.php create mode 100644 tests/phpunit/includes/GlobalFunctions/wfEscapeShellArgTest.php create mode 100644 tests/phpunit/includes/GlobalFunctions/wfThumbIsStandardTest.php delete mode 100644 tests/phpunit/includes/ImagePage404Test.php delete mode 100644 tests/phpunit/includes/ImagePageTest.php delete mode 100644 tests/phpunit/includes/LanguageConverterTest.php delete mode 100644 tests/phpunit/includes/LinksUpdateTest.php delete mode 100644 tests/phpunit/includes/LocalFileTest.php delete mode 100644 tests/phpunit/includes/MWFunctionTest.php create mode 100644 tests/phpunit/includes/MovePageTest.php delete mode 100644 tests/phpunit/includes/PasswordTest.php create mode 100644 tests/phpunit/includes/PrefixSearchTest.php delete mode 100644 tests/phpunit/includes/RequestContextTest.php delete mode 100644 tests/phpunit/includes/SpecialPageTest.php create mode 100644 tests/phpunit/includes/TemplateParserTest.php create mode 100644 tests/phpunit/includes/TestingAccessWrapper.php create mode 100644 tests/phpunit/includes/TestingAccessWrapperTest.php delete mode 100644 tests/phpunit/includes/WikiPageTest.php delete mode 100644 tests/phpunit/includes/WikiPageTestContentHandlerUseDB.php delete mode 100644 tests/phpunit/includes/XmlTypeCheckTest.php create mode 100644 tests/phpunit/includes/api/ApiContinuationManagerTest.php create mode 100644 tests/phpunit/includes/api/ApiErrorFormatterTest.php create mode 100644 tests/phpunit/includes/api/ApiMessageTest.php create mode 100644 tests/phpunit/includes/api/ApiResultTest.php create mode 100644 tests/phpunit/includes/api/format/ApiFormatDbgTest.php create mode 100644 tests/phpunit/includes/api/format/ApiFormatDumpTest.php create mode 100644 tests/phpunit/includes/api/format/ApiFormatTxtTest.php create mode 100644 tests/phpunit/includes/api/format/ApiFormatXmlTest.php delete mode 100644 tests/phpunit/includes/cache/RedisBloomCacheTest.php create mode 100644 tests/phpunit/includes/context/RequestContextTest.php create mode 100644 tests/phpunit/includes/debug/logging/LegacyLoggerTest.php create mode 100644 tests/phpunit/includes/deferred/LinksUpdateTest.php create mode 100644 tests/phpunit/includes/deferred/SearchUpdateTest.php create mode 100644 tests/phpunit/includes/externalstore/ExternalStoreTest.php create mode 100644 tests/phpunit/includes/filerepo/file/LocalFileTest.php create mode 100644 tests/phpunit/includes/installer/DatabaseUpdaterTest.php create mode 100644 tests/phpunit/includes/jobqueue/JobTest.php create mode 100644 tests/phpunit/includes/libs/ArrayUtilsTest.php create mode 100644 tests/phpunit/includes/libs/DeferredStringifierTest.php create mode 100644 tests/phpunit/includes/libs/ObjectFactoryTest.php create mode 100644 tests/phpunit/includes/libs/StringUtilsTest.php create mode 100644 tests/phpunit/includes/libs/XhprofTest.php create mode 100644 tests/phpunit/includes/libs/XmlTypeCheckTest.php create mode 100644 tests/phpunit/includes/libs/composer/ComposerJsonTest.php create mode 100644 tests/phpunit/includes/libs/composer/ComposerLockTest.php delete mode 100644 tests/phpunit/includes/normal/CleanUpTest.php create mode 100644 tests/phpunit/includes/page/ArticleTablesTest.php create mode 100644 tests/phpunit/includes/page/ArticleTest.php create mode 100644 tests/phpunit/includes/page/ImagePage404Test.php create mode 100644 tests/phpunit/includes/page/ImagePageTest.php create mode 100644 tests/phpunit/includes/page/WikiPageTest.php create mode 100644 tests/phpunit/includes/page/WikiPageTestContentHandlerUseDB.php create mode 100644 tests/phpunit/includes/password/PasswordTest.php create mode 100644 tests/phpunit/includes/registration/ExtensionProcessorTest.php create mode 100644 tests/phpunit/includes/registration/ExtensionRegistryTest.php create mode 100644 tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php create mode 100644 tests/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php create mode 100644 tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php create mode 100644 tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php delete mode 100644 tests/phpunit/includes/resourceloader/ResourceLoaderStartupModuleTest.php create mode 100644 tests/phpunit/includes/resourceloader/templates/template.html create mode 100644 tests/phpunit/includes/resourceloader/templates/template2.html create mode 100644 tests/phpunit/includes/resourceloader/templates/template_awesome.handlebars delete mode 100644 tests/phpunit/includes/search/SearchUpdateTest.php create mode 100644 tests/phpunit/includes/site/CachingSiteStoreTest.php create mode 100644 tests/phpunit/includes/site/DBSiteStoreTest.php create mode 100644 tests/phpunit/includes/site/FileBasedSiteLookupTest.php create mode 100644 tests/phpunit/includes/site/HashSiteStoreTest.php create mode 100644 tests/phpunit/includes/site/SiteExporterTest.php create mode 100644 tests/phpunit/includes/site/SiteImporterTest.php create mode 100644 tests/phpunit/includes/site/SiteImporterTest.xml create mode 100644 tests/phpunit/includes/site/SitesCacheFileBuilderTest.php create mode 100644 tests/phpunit/includes/specialpage/SpecialPageTest.php create mode 100644 tests/phpunit/includes/specialpage/SpecialPageTestHelper.php create mode 100644 tests/phpunit/includes/specials/SpecialBooksourcesTest.php create mode 100644 tests/phpunit/includes/title/ForeignTitleTest.php create mode 100644 tests/phpunit/includes/title/NaiveForeignTitleFactoryTest.php create mode 100644 tests/phpunit/includes/title/NaiveImportTitleFactoryTest.php create mode 100644 tests/phpunit/includes/title/NamespaceAwareForeignTitleFactoryTest.php create mode 100644 tests/phpunit/includes/title/NamespaceImportTitleFactoryTest.php create mode 100644 tests/phpunit/includes/title/SubpageImportTitleFactoryTest.php delete mode 100644 tests/phpunit/includes/utils/CdbTest.php create mode 100644 tests/phpunit/includes/utils/MWFunctionTest.php delete mode 100644 tests/phpunit/includes/utils/StringUtilsTest.php (limited to 'tests/phpunit/includes') diff --git a/tests/phpunit/includes/ArrayUtilsTest.php b/tests/phpunit/includes/ArrayUtilsTest.php deleted file mode 100644 index 7bdb1ca4..00000000 --- a/tests/phpunit/includes/ArrayUtilsTest.php +++ /dev/null @@ -1,311 +0,0 @@ -assertSame( - ArrayUtils::findLowerBound( - $valueCallback, $valueCount, $comparisonCallback, $target - ), $expected - ); - } - - function provideFindLowerBound() { - $self = $this; - $indexValueCallback = function ( $size ) use ( $self ) { - return function ( $val ) use ( $self, $size ) { - $self->assertTrue( $val >= 0 ); - $self->assertTrue( $val < $size ); - return $val; - }; - }; - $comparisonCallback = function ( $a, $b ) { - return $a - $b; - }; - - return array( - array( - $indexValueCallback( 0 ), - 0, - $comparisonCallback, - 1, - false, - ), - array( - $indexValueCallback( 1 ), - 1, - $comparisonCallback, - -1, - false, - ), - array( - $indexValueCallback( 1 ), - 1, - $comparisonCallback, - 0, - 0, - ), - array( - $indexValueCallback( 1 ), - 1, - $comparisonCallback, - 1, - 0, - ), - array( - $indexValueCallback( 2 ), - 2, - $comparisonCallback, - -1, - false, - ), - array( - $indexValueCallback( 2 ), - 2, - $comparisonCallback, - 0, - 0, - ), - array( - $indexValueCallback( 2 ), - 2, - $comparisonCallback, - 0.5, - 0, - ), - array( - $indexValueCallback( 2 ), - 2, - $comparisonCallback, - 1, - 1, - ), - array( - $indexValueCallback( 2 ), - 2, - $comparisonCallback, - 1.5, - 1, - ), - array( - $indexValueCallback( 3 ), - 3, - $comparisonCallback, - 1, - 1, - ), - array( - $indexValueCallback( 3 ), - 3, - $comparisonCallback, - 1.5, - 1, - ), - array( - $indexValueCallback( 3 ), - 3, - $comparisonCallback, - 2, - 2, - ), - array( - $indexValueCallback( 3 ), - 3, - $comparisonCallback, - 3, - 2, - ), - ); - } - - /** - * @covers ArrayUtils::arrayDiffAssocRecursive - * @dataProvider provideArrayDiffAssocRecursive - */ - function testArrayDiffAssocRecursive( $expected ) { - $args = func_get_args(); - array_shift( $args ); - $this->assertEquals( call_user_func_array( - 'ArrayUtils::arrayDiffAssocRecursive', $args - ), $expected ); - } - - function provideArrayDiffAssocRecursive() { - return array( - array( - array(), - array(), - array(), - ), - array( - array(), - array(), - array(), - array(), - ), - array( - array( 1 ), - array( 1 ), - array(), - ), - array( - array( 1 ), - array( 1 ), - array(), - array(), - ), - array( - array(), - array(), - array( 1 ), - ), - array( - array(), - array(), - array( 1 ), - array( 2 ), - ), - array( - array( '' => 1 ), - array( '' => 1 ), - array(), - ), - array( - array(), - array(), - array( '' => 1 ), - ), - array( - array( 1 ), - array( 1 ), - array( 2 ), - ), - array( - array(), - array( 1 ), - array( 2 ), - array( 1 ), - ), - array( - array(), - array( 1 ), - array( 1, 2 ), - ), - array( - array( 1 => 1 ), - array( 1 => 1 ), - array( 1 ), - ), - array( - array(), - array( 1 => 1 ), - array( 1 ), - array( 1 => 1), - ), - array( - array(), - array( 1 => 1 ), - array( 1, 1, 1 ), - ), - array( - array(), - array( array() ), - array(), - ), - array( - array(), - array( array( array() ) ), - array(), - ), - array( - array( 1, array( 1 ) ), - array( 1, array( 1 ) ), - array(), - ), - array( - array( 1 ), - array( 1, array( 1 ) ), - array( 2, array( 1 ) ), - ), - array( - array(), - array( 1, array( 1 ) ), - array( 2, array( 1 ) ), - array( 1, array( 2 ) ), - ), - array( - array( 1 ), - array( 1, array() ), - array( 2 ), - ), - array( - array(), - array( 1, array() ), - array( 2 ), - array( 1 ), - ), - array( - array( 1, array( 1 => 2 ) ), - array( 1, array( 1, 2 ) ), - array( 2, array( 1 ) ), - ), - array( - array( 1 ), - array( 1, array( 1, 2 ) ), - array( 2, array( 1 ) ), - array( 2, array( 1 => 2 ) ), - ), - array( - array( 1 => array( 1, 2 ) ), - array( 1, array( 1, 2 ) ), - array( 1, array( 2 ) ), - ), - array( - array( 1 => array( array( 2, 3 ), 2 ) ), - array( 1, array( array( 2, 3 ), 2 ) ), - array( 1, array( 2 ) ), - ), - array( - array( 1 => array( array( 2 ), 2 ) ), - array( 1, array( array( 2, 3 ), 2 ) ), - array( 1, array( array( 1 => 3 ) ) ), - ), - array( - array( 1 => array( 1 => 2 ) ), - array( 1, array( array( 2, 3 ), 2 ) ), - array( 1, array( array( 1 => 3, 0 => 2 ) ) ), - ), - array( - array( 1 => array( 1 => 2 ) ), - array( 1, array( array( 2, 3 ), 2 ) ), - array( 1, array( array( 1 => 3 ) ) ), - array( 1 => array( array( 2 ) ) ), - ), - array( - array(), - array( 1, array( array( 2, 3 ), 2 ) ), - array( 1 => array( 1 => 2, 0 => array( 1 => 3, 0 => 2 ) ), 0 => 1 ), - ), - array( - array(), - array( 1, array( array( 2, 3 ), 2 ) ), - array( 1 => array( 1 => 2 ) ), - array( 1 => array( array( 1 => 3 ) ) ), - array( 1 => array( array( 2 ) ) ), - array( 1 ), - ), - ); - } -} diff --git a/tests/phpunit/includes/ArticleTablesTest.php b/tests/phpunit/includes/ArticleTablesTest.php deleted file mode 100644 index 9f2b7a05..00000000 --- a/tests/phpunit/includes/ArticleTablesTest.php +++ /dev/null @@ -1,53 +0,0 @@ -mRights = array( 'createpage', 'edit', 'purge' ); - $this->setMwGlobals( 'wgLanguageCode', 'es' ); - $this->setMwGlobals( 'wgContLang', Language::factory( 'es' ) ); - $this->setMwGlobals( 'wgLang', Language::factory( 'fr' ) ); - - $page->doEditContent( - new WikitextContent( '{{:{{int:history}}}}' ), - 'Test code for bug 14404', - 0, - false, - $user - ); - $templates1 = $title->getTemplateLinksFrom(); - - $this->setMwGlobals( 'wgLang', Language::factory( 'de' ) ); - $page = WikiPage::factory( $title ); // In order to force the re-rendering of the same wikitext - - // We need an edit, a purge is not enough to regenerate the tables - $page->doEditContent( - new WikitextContent( '{{:{{int:history}}}}' ), - 'Test code for bug 14404', - EDIT_UPDATE, - false, - $user - ); - $templates2 = $title->getTemplateLinksFrom(); - - /** - * @var Title[] $templates1 - * @var Title[] $templates2 - */ - $this->assertEquals( $templates1, $templates2 ); - $this->assertEquals( $templates1[0]->getFullText(), 'Historial' ); - } -} diff --git a/tests/phpunit/includes/ArticleTest.php b/tests/phpunit/includes/ArticleTest.php deleted file mode 100644 index ae069eaf..00000000 --- a/tests/phpunit/includes/ArticleTest.php +++ /dev/null @@ -1,95 +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" ); - } - - /** - * @depends testImplementsSetMagic - * @covers Article::__call - */ - public function testImplementsCallMagic() { - $this->article->mLatest = 33; - $this->article->mDataLoaded = true; - $this->assertEquals( 33, $this->article->getLatest(), "Article __call 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" ); - } - - /** - * Checks for the existence of the backwards compatibility static functions - * (forwarders to WikiPage class) - * - * @covers Article::selectFields - * @covers Article::onArticleCreate - * @covers Article::onArticleDelete - * @covers Article::onArticleEdit - * @covers Article::getAutosummary - */ - public function testStaticFunctions() { - $this->hideDeprecated( 'Article::selectFields' ); - $this->hideDeprecated( 'Article::getAutosummary' ); - $this->hideDeprecated( 'WikiPage::getAutosummary' ); - $this->hideDeprecated( 'CategoryPage::getAutosummary' ); // Inherited from Article - - $this->assertEquals( WikiPage::selectFields(), Article::selectFields(), - "Article static functions" ); - $this->assertEquals( true, is_callable( "Article::onArticleCreate" ), - "Article static functions" ); - $this->assertEquals( true, is_callable( "Article::onArticleDelete" ), - "Article static functions" ); - $this->assertEquals( true, is_callable( "ImagePage::onArticleEdit" ), - "Article static functions" ); - $this->assertTrue( is_string( CategoryPage::getAutosummary( '', '', 0 ) ), - "Article static functions" ); - } -} diff --git a/tests/phpunit/includes/BlockTest.php b/tests/phpunit/includes/BlockTest.php index b248d24e..19741621 100644 --- a/tests/phpunit/includes/BlockTest.php +++ b/tests/phpunit/includes/BlockTest.php @@ -133,6 +133,7 @@ class BlockTest extends MediaWikiLangTestCase { $username = 'BlockedUserToCreateAccountWith'; $u = User::newFromName( $username ); $u->setPassword( 'NotRandomPass' ); + $u->setId( 14146 ); $u->addToDatabase(); unset( $u ); @@ -200,6 +201,12 @@ class BlockTest extends MediaWikiLangTestCase { $oldBlock->delete(); } + // Local perspective (blockee on current wiki)... + $user = User::newFromName( 'UserOnForeignWiki' ); + $user->addToDatabase(); + // Set user ID to match the test value + $this->db->update( 'user', array( 'user_id' => 14146 ), array( 'user_id' => $user->getId() ) ); + // Foreign perspective (blockee not on current wiki)... $block = new Block( /* $address */ 'UserOnForeignWiki', @@ -221,11 +228,6 @@ class BlockTest extends MediaWikiLangTestCase { $res = $block->insert( $this->db ); $this->assertTrue( (bool)$res['id'], 'Block succeeded' ); - // Local perspective (blockee on current wiki)... - $user = User::newFromName( 'UserOnForeignWiki' ); - $user->addToDatabase(); - // Set user ID to match the test value - $this->db->update( 'user', array( 'user_id' => 14146 ), array( 'user_id' => $user->getId() ) ); $user = null; // clear $block = Block::newFromID( $res['id'] ); diff --git a/tests/phpunit/includes/EditPageTest.php b/tests/phpunit/includes/EditPageTest.php index 702fce4c..15778e40 100644 --- a/tests/phpunit/includes/EditPageTest.php +++ b/tests/phpunit/includes/EditPageTest.php @@ -217,7 +217,8 @@ class EditPageTest extends MediaWikiLangTestCase { EditPage::AS_SUCCESS_NEW_ARTICLE, '' ), - array( 'expected registered MediaWiki: page whose default content is empty not being created if empty', + array( 'expected registered MediaWiki: page whose default content is empty' + . ' not being created if empty', 'MediaWiki:Ipb-default-expiry', 'UTSysop', '', @@ -246,7 +247,9 @@ class EditPageTest extends MediaWikiLangTestCase { * @dataProvider provideCreatePages * @covers EditPage */ - public function testCreatePage( $desc, $pageTitle, $user, $editText, $expectedCode, $expectedText, $ignoreBlank = false ) { + public function testCreatePage( + $desc, $pageTitle, $user, $editText, $expectedCode, $expectedText, $ignoreBlank = false + ) { $edit = array( 'wpTextbox1' => $editText ); if ( $ignoreBlank ) { $edit['wpIgnoreBlankArticle'] = 1; diff --git a/tests/phpunit/includes/ExternalStoreTest.php b/tests/phpunit/includes/ExternalStoreTest.php deleted file mode 100644 index 07c2957c..00000000 --- a/tests/phpunit/includes/ExternalStoreTest.php +++ /dev/null @@ -1,87 +0,0 @@ -setMwGlobals( 'wgExternalStores', false ); - - $this->assertFalse( - ExternalStore::fetchFromURL( 'FOO://cluster1/200' ), - 'Deny if wgExternalStores is not set to a non-empty array' - ); - - $this->setMwGlobals( 'wgExternalStores', array( 'FOO' ) ); - - $this->assertEquals( - ExternalStore::fetchFromURL( 'FOO://cluster1/200' ), - 'Hello', - 'Allow FOO://cluster1/200' - ); - $this->assertEquals( - ExternalStore::fetchFromURL( 'FOO://cluster1/300/0' ), - 'Hello', - 'Allow FOO://cluster1/300/0' - ); - # Assertions for r68900 - $this->assertFalse( - ExternalStore::fetchFromURL( 'ftp.example.org' ), - 'Deny domain ftp.example.org' - ); - $this->assertFalse( - ExternalStore::fetchFromURL( '/example.txt' ), - 'Deny path /example.txt' - ); - $this->assertFalse( - ExternalStore::fetchFromURL( 'http://' ), - 'Deny protocol http://' - ); - } -} - -class ExternalStoreFOO { - - protected $data = array( - 'cluster1' => array( - '200' => 'Hello', - '300' => array( - 'Hello', 'World', - ), - ), - ); - - /** - * Fetch data from given URL - * @param string $url An url of the form FOO://cluster/id or FOO://cluster/id/itemid. - * @return mixed - */ - function fetchFromURL( $url ) { - // Based on ExternalStoreDB - $path = explode( '/', $url ); - $cluster = $path[2]; - $id = $path[3]; - if ( isset( $path[4] ) ) { - $itemID = $path[4]; - } else { - $itemID = false; - } - - if ( !isset( $this->data[$cluster][$id] ) ) { - return null; - } - - if ( $itemID !== false - && is_array( $this->data[$cluster][$id] ) - && isset( $this->data[$cluster][$id][$itemID] ) - ) { - return $this->data[$cluster][$id][$itemID]; - } - - return $this->data[$cluster][$id]; - } -} diff --git a/tests/phpunit/includes/GitInfoTest.php b/tests/phpunit/includes/GitInfoTest.php index e22f5050..c3539d0e 100644 --- a/tests/phpunit/includes/GitInfoTest.php +++ b/tests/phpunit/includes/GitInfoTest.php @@ -10,7 +10,7 @@ class GitInfoTest extends MediaWikiTestCase { } public function testValidJsonData() { - $dir = $GLOBALS['IP'] . '/testValidJsonData'; + $dir = $GLOBALS['IP'] . DIRECTORY_SEPARATOR . 'testValidJsonData'; $fixture = new GitInfo( $dir ); $this->assertTrue( $fixture->cacheIsComplete() ); diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php index 3acc48e2..1e30273e 100644 --- a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php +++ b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php @@ -7,7 +7,7 @@ class GlobalTest extends MediaWikiTestCase { protected function setUp() { parent::setUp(); - $readOnlyFile = tempnam( wfTempDir(), "mwtest_readonly" ); + $readOnlyFile = $this->getNewTempFile(); unlink( $readOnlyFile ); $this->setMwGlobals( array( @@ -22,16 +22,6 @@ class GlobalTest extends MediaWikiTestCase { ) ); } - protected function tearDown() { - global $wgReadOnlyFile; - - if ( file_exists( $wgReadOnlyFile ) ) { - unlink( $wgReadOnlyFile ); - } - - parent::tearDown(); - } - /** * @dataProvider provideForWfArrayDiff2 * @covers ::wfArrayDiff2 @@ -312,46 +302,42 @@ class GlobalTest extends MediaWikiTestCase { * @covers ::wfDebugMem */ public function testDebugFunctionTest() { + $debugLogFile = $this->getNewTempFile(); - global $wgDebugLogFile, $wgDebugTimestamps; - - $old_log_file = $wgDebugLogFile; - $wgDebugLogFile = tempnam( wfTempDir(), 'mw-' ); - # @todo FIXME: $wgDebugTimestamps should be tested - $old_wgDebugTimestamps = $wgDebugTimestamps; - $wgDebugTimestamps = false; + $this->setMwGlobals( array( + 'wgDebugLogFile' => $debugLogFile, + # @todo FIXME: $wgDebugTimestamps should be tested + 'wgDebugTimestamps' => false + ) ); wfDebug( "This is a normal string" ); - $this->assertEquals( "This is a normal string", file_get_contents( $wgDebugLogFile ) ); - unlink( $wgDebugLogFile ); + $this->assertEquals( "This is a normal string\n", file_get_contents( $debugLogFile ) ); + unlink( $debugLogFile ); wfDebug( "This is nöt an ASCII string" ); - $this->assertEquals( "This is nöt an ASCII string", file_get_contents( $wgDebugLogFile ) ); - unlink( $wgDebugLogFile ); + $this->assertEquals( "This is nöt an ASCII string\n", file_get_contents( $debugLogFile ) ); + unlink( $debugLogFile ); wfDebug( "\00305This has böth UTF and control chars\003" ); $this->assertEquals( - " 05This has böth UTF and control chars ", - file_get_contents( $wgDebugLogFile ) + " 05This has böth UTF and control chars \n", + file_get_contents( $debugLogFile ) ); - unlink( $wgDebugLogFile ); + unlink( $debugLogFile ); wfDebugMem(); $this->assertGreaterThan( 1000, - preg_replace( '/\D/', '', file_get_contents( $wgDebugLogFile ) ) + preg_replace( '/\D/', '', file_get_contents( $debugLogFile ) ) ); - unlink( $wgDebugLogFile ); + unlink( $debugLogFile ); wfDebugMem( true ); $this->assertGreaterThan( 1000000, - preg_replace( '/\D/', '', file_get_contents( $wgDebugLogFile ) ) + preg_replace( '/\D/', '', file_get_contents( $debugLogFile ) ) ); - unlink( $wgDebugLogFile ); - - $wgDebugLogFile = $old_log_file; - $wgDebugTimestamps = $old_wgDebugTimestamps; + unlink( $debugLogFile ); } /** @@ -388,24 +374,6 @@ class GlobalTest extends MediaWikiTestCase { } } - /** - * @covers ::swap - */ - public function testSwapVarsTest() { - $this->hideDeprecated( 'swap' ); - - $var1 = 1; - $var2 = 2; - - $this->assertEquals( $var1, 1, 'var1 is set originally' ); - $this->assertEquals( $var2, 2, 'var1 is set originally' ); - - swap( $var1, $var2 ); - - $this->assertEquals( $var1, 2, 'var1 is swapped' ); - $this->assertEquals( $var2, 1, 'var2 is swapped' ); - } - /** * @covers ::wfPercent */ @@ -705,21 +673,21 @@ class GlobalTest extends MediaWikiTestCase { } /** - * @dataProvider provideWfShellMaintenanceCmdList - * @covers ::wfShellMaintenanceCmd + * @dataProvider provideWfShellWikiCmdList + * @covers ::wfShellWikiCmd */ - public function testWfShellMaintenanceCmd( $script, $parameters, $options, + public function testWfShellWikiCmd( $script, $parameters, $options, $expected, $description ) { if ( wfIsWindows() ) { // Approximation that's good enough for our purposes just now $expected = str_replace( "'", '"', $expected ); } - $actual = wfShellMaintenanceCmd( $script, $parameters, $options ); + $actual = wfShellWikiCmd( $script, $parameters, $options ); $this->assertEquals( $expected, $actual, $description ); } - public static function provideWfShellMaintenanceCmdList() { + public static function provideWfShellWikiCmdList() { global $wgPhpCli; return array( diff --git a/tests/phpunit/includes/GlobalFunctions/wfAppendQueryTest.php b/tests/phpunit/includes/GlobalFunctions/wfAppendQueryTest.php new file mode 100644 index 00000000..54e1f896 --- /dev/null +++ b/tests/phpunit/includes/GlobalFunctions/wfAppendQueryTest.php @@ -0,0 +1,67 @@ +assertEquals( $expected, wfAppendQuery( $url, $query ), $message ); + } + + public static function provideAppendQuery() { + return array( + array( + 'http://www.example.org/index.php', + '', + 'http://www.example.org/index.php', + 'No query' + ), + array( + 'http://www.example.org/index.php', + array( 'foo' => 'bar' ), + 'http://www.example.org/index.php?foo=bar', + 'Set query array' + ), + array( + 'http://www.example.org/index.php?foz=baz', + 'foo=bar', + 'http://www.example.org/index.php?foz=baz&foo=bar', + 'Set query string' + ), + array( + 'http://www.example.org/index.php?foo=bar', + '', + 'http://www.example.org/index.php?foo=bar', + 'Empty string with query' + ), + array( + 'http://www.example.org/index.php?foo=bar', + array( 'baz' => 'quux' ), + 'http://www.example.org/index.php?foo=bar&baz=quux', + 'Add query array' + ), + array( + 'http://www.example.org/index.php?foo=bar', + 'baz=quux', + 'http://www.example.org/index.php?foo=bar&baz=quux', + 'Add query string' + ), + array( + 'http://www.example.org/index.php?foo=bar', + array( 'baz' => 'quux', 'foo' => 'baz' ), + 'http://www.example.org/index.php?foo=bar&baz=quux&foo=baz', + 'Modify query array' + ), + 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' + ) + ); + } +} diff --git a/tests/phpunit/includes/GlobalFunctions/wfEscapeShellArgTest.php b/tests/phpunit/includes/GlobalFunctions/wfEscapeShellArgTest.php new file mode 100644 index 00000000..cb334d2f --- /dev/null +++ b/tests/phpunit/includes/GlobalFunctions/wfEscapeShellArgTest.php @@ -0,0 +1,43 @@ +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( array( 'foo', 'bar', 'baz' ) ); + + $this->assertEquals( $expected, $actual ); + } +} diff --git a/tests/phpunit/includes/GlobalFunctions/wfThumbIsStandardTest.php b/tests/phpunit/includes/GlobalFunctions/wfThumbIsStandardTest.php new file mode 100644 index 00000000..448250a6 --- /dev/null +++ b/tests/phpunit/includes/GlobalFunctions/wfThumbIsStandardTest.php @@ -0,0 +1,104 @@ +setMwGlobals( array( + 'wgThumbLimits' => array( + 100, + 401 + ), + 'wgImageLimits' => array( + array( 300, 225 ), + array( 800, 600 ), + ), + 'wgMediaHandlers' => array( + 'unknown/unknown' => 'MockBitmapHandler', + ), + ) ); + } + + public static function provideThumbParams() { + return array( + // Thumb limits + array( + 'Standard thumb width', + true, + array( 'width' => 100 ), + ), + array( + 'Standard thumb width', + true, + array( 'width' => 401 ), + ), + // wfThumbIsStandard should match Linker::processResponsiveImages + // in its rounding behaviour. + array( + 'Standard thumb width (HiDPI 1.5x) - incorrect rounding', + false, + array( 'width' => 601 ), + ), + array( + 'Standard thumb width (HiDPI 1.5x)', + true, + array( 'width' => 602 ), + ), + array( + 'Standard thumb width (HiDPI 2x)', + true, + array( 'width' => 802 ), + ), + array( + 'Non-standard thumb width', + false, + array( 'width' => 300 ), + ), + // Image limits + // Note: Image limits are measured as pairs. Individual values + // may be non-standard based on the aspect ratio. + array( + 'Standard image width/height pair', + true, + array( 'width' => 250, 'height' => 225 ), + ), + array( + 'Standard image width/height pair', + true, + array( 'width' => 667, 'height' => 600 ), + ), + array( + 'Standard image width where image does not fit aspect ratio', + false, + array( 'width' => 300 ), + ), + array( + 'Implicit width from image width/height pair aspect ratio fit', + true, + // 2000x1800 fit inside 300x225 makes w=250 + array( 'width' => 250 ), + ), + array( + 'Height-only is always non-standard', + false, + array( 'height' => 225 ), + ), + ); + } + + /** + * @dataProvider provideThumbParams + */ + public function testIsStandard( $message, $expected, $params ) { + $this->assertSame( + $expected, + wfThumbIsStandard( new FakeDimensionFile( array( 2000, 1800 ) ), $params ), + $message + ); + } +} diff --git a/tests/phpunit/includes/HtmlFormatterTest.php b/tests/phpunit/includes/HtmlFormatterTest.php index 9dbfa452..1c3e8539 100644 --- a/tests/phpunit/includes/HtmlFormatterTest.php +++ b/tests/phpunit/includes/HtmlFormatterTest.php @@ -4,6 +4,20 @@ * @group HtmlFormatter */ class HtmlFormatterTest extends MediaWikiTestCase { + + /** + * Use TidySupport to check whether we should use $wgTidyInternal. + * + * The Tidy extension in HHVM does not support error text return, so it is + * nominally usable, but does not pass tests which require error text from + * Tidy. + */ + protected function setUp() { + parent::setUp(); + $tidySupport = new TidySupport(); + $this->setMwGlobals( 'wgTidyInternal', $tidySupport->isInternal() ); + } + /** * @dataProvider getHtmlData * diff --git a/tests/phpunit/includes/HtmlTest.php b/tests/phpunit/includes/HtmlTest.php index a8829cd8..c5797c4f 100644 --- a/tests/phpunit/includes/HtmlTest.php +++ b/tests/phpunit/includes/HtmlTest.php @@ -637,7 +637,7 @@ class HtmlTest extends MediaWikiTestCase { . 'Depending on compatibility mode IE might use "button", instead.', ); - # specific handling $cases[] = array( '', 'select', array( 'size' => '4', 'multiple' => true ), ); @@ -715,7 +715,7 @@ class HtmlTest extends MediaWikiTestCase { 'Input wrapper with type and value.' ); $this->assertEquals( - '', + '', Html::input( 'testname' ), 'Input wrapper with all default values.' ); @@ -764,6 +764,30 @@ class HtmlTest extends MediaWikiTestCase { 'Label wrapper' ); } + + public static function provideSrcSetImages() { + return array( + array( array(), '', 'when there are no images, return empty string' ), + array( + array( '1x' => '1x.png', '1.5x' => '1_5x.png', '2x' => '2x.png' ), + '1x.png 1x, 1_5x.png 1.5x, 2x.png 2x', + 'pixel depth keys may include a trailing "x"' + ), + array( + array( '1' => '1x.png', '1.5' => '1_5x.png', '2' => '2x.png' ), + '1x.png 1x, 1_5x.png 1.5x, 2x.png 2x', + 'pixel depth keys may omit a trailing "x"' + ), + ); + } + + /** + * @dataProvider provideSrcSetImages + * @covers Html::srcSet + */ + public function testSrcSet( $images, $expected, $message ) { + $this->assertEquals( Html::srcSet( $images ), $expected, $message ); + } } class HtmlTestValue { diff --git a/tests/phpunit/includes/HttpTest.php b/tests/phpunit/includes/HttpTest.php index 9b53381e..8a0dff78 100644 --- a/tests/phpunit/includes/HttpTest.php +++ b/tests/phpunit/includes/HttpTest.php @@ -1,6 +1,7 @@ setRespHeaders( 'location', array( @@ -171,6 +174,310 @@ class HttpTest extends MediaWikiTestCase { $h->getFinalUrl( "Relative file path Location: should keep the latest host and scheme!" ) ); } + + /** + * Constant values are from PHP 5.3.28 using cURL 7.24.0 + * @see http://php.net/manual/en/curl.constants.php + * + * All constant values are present so that developers don’t need to remember + * to add them if added at a later date. The commented out constants were + * not found anywhere in the MediaWiki core code. + * + * Commented out constants that were not available in: + * HipHop VM 3.3.0 (rel) + * Compiler: heads/master-0-g08810d920dfff59e0774cf2d651f92f13a637175 + * Repo schema: 3214fc2c684a4520485f715ee45f33f2182324b1 + * Extension API: 20140829 + * + * Commented out constants that were removed in PHP 5.6.0 + * + * @covers CurlHttpRequest::execute + */ + public function provideCurlConstants() { + return array( + array( 'CURLAUTH_ANY' ), + array( 'CURLAUTH_ANYSAFE' ), + array( 'CURLAUTH_BASIC' ), + array( 'CURLAUTH_DIGEST' ), + array( 'CURLAUTH_GSSNEGOTIATE' ), + array( 'CURLAUTH_NTLM' ), + // array( 'CURLCLOSEPOLICY_CALLBACK' ), // removed in PHP 5.6.0 + // array( 'CURLCLOSEPOLICY_LEAST_RECENTLY_USED' ), // removed in PHP 5.6.0 + // array( 'CURLCLOSEPOLICY_LEAST_TRAFFIC' ), // removed in PHP 5.6.0 + // array( 'CURLCLOSEPOLICY_OLDEST' ), // removed in PHP 5.6.0 + // array( 'CURLCLOSEPOLICY_SLOWEST' ), // removed in PHP 5.6.0 + array( 'CURLE_ABORTED_BY_CALLBACK' ), + array( 'CURLE_BAD_CALLING_ORDER' ), + array( 'CURLE_BAD_CONTENT_ENCODING' ), + array( 'CURLE_BAD_FUNCTION_ARGUMENT' ), + array( 'CURLE_BAD_PASSWORD_ENTERED' ), + array( 'CURLE_COULDNT_CONNECT' ), + array( 'CURLE_COULDNT_RESOLVE_HOST' ), + array( 'CURLE_COULDNT_RESOLVE_PROXY' ), + array( 'CURLE_FAILED_INIT' ), + array( 'CURLE_FILESIZE_EXCEEDED' ), + array( 'CURLE_FILE_COULDNT_READ_FILE' ), + array( 'CURLE_FTP_ACCESS_DENIED' ), + array( 'CURLE_FTP_BAD_DOWNLOAD_RESUME' ), + array( 'CURLE_FTP_CANT_GET_HOST' ), + array( 'CURLE_FTP_CANT_RECONNECT' ), + array( 'CURLE_FTP_COULDNT_GET_SIZE' ), + array( 'CURLE_FTP_COULDNT_RETR_FILE' ), + array( 'CURLE_FTP_COULDNT_SET_ASCII' ), + array( 'CURLE_FTP_COULDNT_SET_BINARY' ), + array( 'CURLE_FTP_COULDNT_STOR_FILE' ), + array( 'CURLE_FTP_COULDNT_USE_REST' ), + array( 'CURLE_FTP_PORT_FAILED' ), + array( 'CURLE_FTP_QUOTE_ERROR' ), + array( 'CURLE_FTP_SSL_FAILED' ), + array( 'CURLE_FTP_USER_PASSWORD_INCORRECT' ), + array( 'CURLE_FTP_WEIRD_227_FORMAT' ), + array( 'CURLE_FTP_WEIRD_PASS_REPLY' ), + array( 'CURLE_FTP_WEIRD_PASV_REPLY' ), + array( 'CURLE_FTP_WEIRD_SERVER_REPLY' ), + array( 'CURLE_FTP_WEIRD_USER_REPLY' ), + array( 'CURLE_FTP_WRITE_ERROR' ), + array( 'CURLE_FUNCTION_NOT_FOUND' ), + array( 'CURLE_GOT_NOTHING' ), + array( 'CURLE_HTTP_NOT_FOUND' ), + array( 'CURLE_HTTP_PORT_FAILED' ), + array( 'CURLE_HTTP_POST_ERROR' ), + array( 'CURLE_HTTP_RANGE_ERROR' ), + array( 'CURLE_LDAP_CANNOT_BIND' ), + array( 'CURLE_LDAP_INVALID_URL' ), + array( 'CURLE_LDAP_SEARCH_FAILED' ), + array( 'CURLE_LIBRARY_NOT_FOUND' ), + array( 'CURLE_MALFORMAT_USER' ), + array( 'CURLE_OBSOLETE' ), + array( 'CURLE_OK' ), + array( 'CURLE_OPERATION_TIMEOUTED' ), + array( 'CURLE_OUT_OF_MEMORY' ), + array( 'CURLE_PARTIAL_FILE' ), + array( 'CURLE_READ_ERROR' ), + array( 'CURLE_RECV_ERROR' ), + array( 'CURLE_SEND_ERROR' ), + array( 'CURLE_SHARE_IN_USE' ), + // array( 'CURLE_SSH' ), // not present in HHVM 3.3.0-dev + array( 'CURLE_SSL_CACERT' ), + array( 'CURLE_SSL_CERTPROBLEM' ), + array( 'CURLE_SSL_CIPHER' ), + array( 'CURLE_SSL_CONNECT_ERROR' ), + array( 'CURLE_SSL_ENGINE_NOTFOUND' ), + array( 'CURLE_SSL_ENGINE_SETFAILED' ), + array( 'CURLE_SSL_PEER_CERTIFICATE' ), + array( 'CURLE_TELNET_OPTION_SYNTAX' ), + array( 'CURLE_TOO_MANY_REDIRECTS' ), + array( 'CURLE_UNKNOWN_TELNET_OPTION' ), + array( 'CURLE_UNSUPPORTED_PROTOCOL' ), + array( 'CURLE_URL_MALFORMAT' ), + array( 'CURLE_URL_MALFORMAT_USER' ), + array( 'CURLE_WRITE_ERROR' ), + array( 'CURLFTPAUTH_DEFAULT' ), + array( 'CURLFTPAUTH_SSL' ), + array( 'CURLFTPAUTH_TLS' ), + // array( 'CURLFTPMETHOD_MULTICWD' ), // not present in HHVM 3.3.0-dev + // array( 'CURLFTPMETHOD_NOCWD' ), // not present in HHVM 3.3.0-dev + // array( 'CURLFTPMETHOD_SINGLECWD' ), // not present in HHVM 3.3.0-dev + array( 'CURLFTPSSL_ALL' ), + array( 'CURLFTPSSL_CONTROL' ), + array( 'CURLFTPSSL_NONE' ), + array( 'CURLFTPSSL_TRY' ), + // array( 'CURLINFO_CERTINFO' ), // not present in HHVM 3.3.0-dev + array( 'CURLINFO_CONNECT_TIME' ), + array( 'CURLINFO_CONTENT_LENGTH_DOWNLOAD' ), + array( 'CURLINFO_CONTENT_LENGTH_UPLOAD' ), + array( 'CURLINFO_CONTENT_TYPE' ), + array( 'CURLINFO_EFFECTIVE_URL' ), + array( 'CURLINFO_FILETIME' ), + array( 'CURLINFO_HEADER_OUT' ), + array( 'CURLINFO_HEADER_SIZE' ), + array( 'CURLINFO_HTTP_CODE' ), + array( 'CURLINFO_NAMELOOKUP_TIME' ), + array( 'CURLINFO_PRETRANSFER_TIME' ), + array( 'CURLINFO_PRIVATE' ), + array( 'CURLINFO_REDIRECT_COUNT' ), + array( 'CURLINFO_REDIRECT_TIME' ), + // array( 'CURLINFO_REDIRECT_URL' ), // not present in HHVM 3.3.0-dev + array( 'CURLINFO_REQUEST_SIZE' ), + array( 'CURLINFO_SIZE_DOWNLOAD' ), + array( 'CURLINFO_SIZE_UPLOAD' ), + array( 'CURLINFO_SPEED_DOWNLOAD' ), + array( 'CURLINFO_SPEED_UPLOAD' ), + array( 'CURLINFO_SSL_VERIFYRESULT' ), + array( 'CURLINFO_STARTTRANSFER_TIME' ), + array( 'CURLINFO_TOTAL_TIME' ), + array( 'CURLMSG_DONE' ), + array( 'CURLM_BAD_EASY_HANDLE' ), + array( 'CURLM_BAD_HANDLE' ), + array( 'CURLM_CALL_MULTI_PERFORM' ), + array( 'CURLM_INTERNAL_ERROR' ), + array( 'CURLM_OK' ), + array( 'CURLM_OUT_OF_MEMORY' ), + array( 'CURLOPT_AUTOREFERER' ), + array( 'CURLOPT_BINARYTRANSFER' ), + array( 'CURLOPT_BUFFERSIZE' ), + array( 'CURLOPT_CAINFO' ), + array( 'CURLOPT_CAPATH' ), + // array( 'CURLOPT_CERTINFO' ), // not present in HHVM 3.3.0-dev + // array( 'CURLOPT_CLOSEPOLICY' ), // removed in PHP 5.6.0 + array( 'CURLOPT_CONNECTTIMEOUT' ), + array( 'CURLOPT_CONNECTTIMEOUT_MS' ), + array( 'CURLOPT_COOKIE' ), + array( 'CURLOPT_COOKIEFILE' ), + array( 'CURLOPT_COOKIEJAR' ), + array( 'CURLOPT_COOKIESESSION' ), + array( 'CURLOPT_CRLF' ), + array( 'CURLOPT_CUSTOMREQUEST' ), + array( 'CURLOPT_DNS_CACHE_TIMEOUT' ), + array( 'CURLOPT_DNS_USE_GLOBAL_CACHE' ), + array( 'CURLOPT_EGDSOCKET' ), + array( 'CURLOPT_ENCODING' ), + array( 'CURLOPT_FAILONERROR' ), + array( 'CURLOPT_FILE' ), + array( 'CURLOPT_FILETIME' ), + array( 'CURLOPT_FOLLOWLOCATION' ), + array( 'CURLOPT_FORBID_REUSE' ), + array( 'CURLOPT_FRESH_CONNECT' ), + array( 'CURLOPT_FTPAPPEND' ), + array( 'CURLOPT_FTPLISTONLY' ), + array( 'CURLOPT_FTPPORT' ), + array( 'CURLOPT_FTPSSLAUTH' ), + array( 'CURLOPT_FTP_CREATE_MISSING_DIRS' ), + // array( 'CURLOPT_FTP_FILEMETHOD' ), // not present in HHVM 3.3.0-dev + // array( 'CURLOPT_FTP_SKIP_PASV_IP' ), // not present in HHVM 3.3.0-dev + array( 'CURLOPT_FTP_SSL' ), + array( 'CURLOPT_FTP_USE_EPRT' ), + array( 'CURLOPT_FTP_USE_EPSV' ), + array( 'CURLOPT_HEADER' ), + array( 'CURLOPT_HEADERFUNCTION' ), + array( 'CURLOPT_HTTP200ALIASES' ), + array( 'CURLOPT_HTTPAUTH' ), + array( 'CURLOPT_HTTPGET' ), + array( 'CURLOPT_HTTPHEADER' ), + array( 'CURLOPT_HTTPPROXYTUNNEL' ), + array( 'CURLOPT_HTTP_VERSION' ), + array( 'CURLOPT_INFILE' ), + array( 'CURLOPT_INFILESIZE' ), + array( 'CURLOPT_INTERFACE' ), + array( 'CURLOPT_IPRESOLVE' ), + // array( 'CURLOPT_KEYPASSWD' ), // not present in HHVM 3.3.0-dev + array( 'CURLOPT_KRB4LEVEL' ), + array( 'CURLOPT_LOW_SPEED_LIMIT' ), + array( 'CURLOPT_LOW_SPEED_TIME' ), + array( 'CURLOPT_MAXCONNECTS' ), + array( 'CURLOPT_MAXREDIRS' ), + // array( 'CURLOPT_MAX_RECV_SPEED_LARGE' ), // not present in HHVM 3.3.0-dev + // array( 'CURLOPT_MAX_SEND_SPEED_LARGE' ), // not present in HHVM 3.3.0-dev + array( 'CURLOPT_NETRC' ), + array( 'CURLOPT_NOBODY' ), + array( 'CURLOPT_NOPROGRESS' ), + array( 'CURLOPT_NOSIGNAL' ), + array( 'CURLOPT_PORT' ), + array( 'CURLOPT_POST' ), + array( 'CURLOPT_POSTFIELDS' ), + array( 'CURLOPT_POSTQUOTE' ), + array( 'CURLOPT_POSTREDIR' ), + array( 'CURLOPT_PRIVATE' ), + array( 'CURLOPT_PROGRESSFUNCTION' ), + // array( 'CURLOPT_PROTOCOLS' ), // not present in HHVM 3.3.0-dev + array( 'CURLOPT_PROXY' ), + array( 'CURLOPT_PROXYAUTH' ), + array( 'CURLOPT_PROXYPORT' ), + array( 'CURLOPT_PROXYTYPE' ), + array( 'CURLOPT_PROXYUSERPWD' ), + array( 'CURLOPT_PUT' ), + array( 'CURLOPT_QUOTE' ), + array( 'CURLOPT_RANDOM_FILE' ), + array( 'CURLOPT_RANGE' ), + array( 'CURLOPT_READDATA' ), + array( 'CURLOPT_READFUNCTION' ), + // array( 'CURLOPT_REDIR_PROTOCOLS' ), // not present in HHVM 3.3.0-dev + array( 'CURLOPT_REFERER' ), + array( 'CURLOPT_RESUME_FROM' ), + array( 'CURLOPT_RETURNTRANSFER' ), + // array( 'CURLOPT_SSH_AUTH_TYPES' ), // not present in HHVM 3.3.0-dev + // array( 'CURLOPT_SSH_HOST_PUBLIC_KEY_MD5' ), // not present in HHVM 3.3.0-dev + // array( 'CURLOPT_SSH_PRIVATE_KEYFILE' ), // not present in HHVM 3.3.0-dev + // array( 'CURLOPT_SSH_PUBLIC_KEYFILE' ), // not present in HHVM 3.3.0-dev + array( 'CURLOPT_SSLCERT' ), + array( 'CURLOPT_SSLCERTPASSWD' ), + array( 'CURLOPT_SSLCERTTYPE' ), + array( 'CURLOPT_SSLENGINE' ), + array( 'CURLOPT_SSLENGINE_DEFAULT' ), + array( 'CURLOPT_SSLKEY' ), + array( 'CURLOPT_SSLKEYPASSWD' ), + array( 'CURLOPT_SSLKEYTYPE' ), + array( 'CURLOPT_SSLVERSION' ), + array( 'CURLOPT_SSL_CIPHER_LIST' ), + array( 'CURLOPT_SSL_VERIFYHOST' ), + array( 'CURLOPT_SSL_VERIFYPEER' ), + array( 'CURLOPT_STDERR' ), + array( 'CURLOPT_TCP_NODELAY' ), + array( 'CURLOPT_TIMECONDITION' ), + array( 'CURLOPT_TIMEOUT' ), + array( 'CURLOPT_TIMEOUT_MS' ), + array( 'CURLOPT_TIMEVALUE' ), + array( 'CURLOPT_TRANSFERTEXT' ), + array( 'CURLOPT_UNRESTRICTED_AUTH' ), + array( 'CURLOPT_UPLOAD' ), + array( 'CURLOPT_URL' ), + array( 'CURLOPT_USERAGENT' ), + array( 'CURLOPT_USERPWD' ), + array( 'CURLOPT_VERBOSE' ), + array( 'CURLOPT_WRITEFUNCTION' ), + array( 'CURLOPT_WRITEHEADER' ), + // array( 'CURLPROTO_ALL' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_DICT' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_FILE' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_FTP' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_FTPS' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_HTTP' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_HTTPS' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_LDAP' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_LDAPS' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_SCP' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_SFTP' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_TELNET' ), // not present in HHVM 3.3.0-dev + // array( 'CURLPROTO_TFTP' ), // not present in HHVM 3.3.0-dev + array( 'CURLPROXY_HTTP' ), + // array( 'CURLPROXY_SOCKS4' ), // not present in HHVM 3.3.0-dev + array( 'CURLPROXY_SOCKS5' ), + // array( 'CURLSSH_AUTH_DEFAULT' ), // not present in HHVM 3.3.0-dev + // array( 'CURLSSH_AUTH_HOST' ), // not present in HHVM 3.3.0-dev + // array( 'CURLSSH_AUTH_KEYBOARD' ), // not present in HHVM 3.3.0-dev + // array( 'CURLSSH_AUTH_NONE' ), // not present in HHVM 3.3.0-dev + // array( 'CURLSSH_AUTH_PASSWORD' ), // not present in HHVM 3.3.0-dev + // array( 'CURLSSH_AUTH_PUBLICKEY' ), // not present in HHVM 3.3.0-dev + array( 'CURLVERSION_NOW' ), + array( 'CURL_HTTP_VERSION_1_0' ), + array( 'CURL_HTTP_VERSION_1_1' ), + array( 'CURL_HTTP_VERSION_NONE' ), + array( 'CURL_IPRESOLVE_V4' ), + array( 'CURL_IPRESOLVE_V6' ), + array( 'CURL_IPRESOLVE_WHATEVER' ), + array( 'CURL_NETRC_IGNORED' ), + array( 'CURL_NETRC_OPTIONAL' ), + array( 'CURL_NETRC_REQUIRED' ), + array( 'CURL_TIMECOND_IFMODSINCE' ), + array( 'CURL_TIMECOND_IFUNMODSINCE' ), + array( 'CURL_TIMECOND_LASTMOD' ), + array( 'CURL_VERSION_IPV6' ), + array( 'CURL_VERSION_KERBEROS4' ), + array( 'CURL_VERSION_LIBZ' ), + array( 'CURL_VERSION_SSL' ), + ); + } + + /** + * Added this test based on an issue experienced with HHVM 3.3.0-dev + * where it did not define a cURL constant. + * + * @bug 70570 + * @dataProvider provideCurlConstants + */ + public function testCurlConstants( $value ) { + $this->assertTrue( defined( $value ), $value . ' not defined' ); + } } /** @@ -179,7 +486,7 @@ class HttpTest extends MediaWikiTestCase { class MWHttpRequestTester extends MWHttpRequest { // function derived from the MWHttpRequest factory function but // returns appropriate tester class here - public static function factory( $url, $options = null ) { + public static function factory( $url, $options = null, $caller = __METHOD__ ) { if ( !Http::$httpEngine ) { Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php'; } elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) { @@ -189,7 +496,7 @@ class MWHttpRequestTester extends MWHttpRequest { switch ( Http::$httpEngine ) { case 'curl': - return new CurlHttpRequestTester( $url, $options ); + return new CurlHttpRequestTester( $url, $options, $caller ); case 'php': if ( !wfIniGetBool( 'allow_url_fopen' ) ) { throw new MWException( __METHOD__ . @@ -197,7 +504,7 @@ class MWHttpRequestTester extends MWHttpRequest { . 'If possible, curl should be used instead. See http://php.net/curl.' ); } - return new PhpHttpRequestTester( $url, $options ); + return new PhpHttpRequestTester( $url, $options, $caller ); default: } } diff --git a/tests/phpunit/includes/ImagePage404Test.php b/tests/phpunit/includes/ImagePage404Test.php deleted file mode 100644 index 197a2b32..00000000 --- a/tests/phpunit/includes/ImagePage404Test.php +++ /dev/null @@ -1,53 +0,0 @@ - true ); - } - - function setUp() { - $this->setMwGlobals( 'wgImageLimits', array( - array( 320, 240 ), - array( 640, 480 ), - array( 800, 600 ), - array( 1024, 768 ), - array( 1280, 1024 ) - ) ); - parent::setUp(); - } - - function getImagePage( $filename ) { - $title = Title::makeTitleSafe( NS_FILE, $filename ); - $file = $this->dataFile( $filename ); - $iPage = new ImagePage( $title ); - $iPage->setFile( $file ); - return $iPage; - } - - /** - * @dataProvider providerGetThumbSizes - * @param string $filename - * @param int $expectedNumberThumbs How many thumbnails to show - */ - function testGetThumbSizes( $filename, $expectedNumberThumbs ) { - $iPage = $this->getImagePage( $filename ); - $reflection = new ReflectionClass( $iPage ); - $reflMethod = $reflection->getMethod( 'getThumbSizes' ); - $reflMethod->setAccessible( true ); - - $actual = $reflMethod->invoke( $iPage, 545, 700 ); - $this->assertEquals( count( $actual ), $expectedNumberThumbs ); - } - - function providerGetThumbSizes() { - return array( - array( 'animated.gif', 6 ), - array( 'Toll_Texas_1.svg', 6 ), - array( '80x60-Greyscale.xcf', 6 ), - array( 'jpeg-comment-binary.jpg', 6 ), - ); - } -} diff --git a/tests/phpunit/includes/ImagePageTest.php b/tests/phpunit/includes/ImagePageTest.php deleted file mode 100644 index 3c255b5f..00000000 --- a/tests/phpunit/includes/ImagePageTest.php +++ /dev/null @@ -1,90 +0,0 @@ -setMwGlobals( 'wgImageLimits', array( - array( 320, 240 ), - array( 640, 480 ), - array( 800, 600 ), - array( 1024, 768 ), - array( 1280, 1024 ) - ) ); - parent::setUp(); - } - - function getImagePage( $filename ) { - $title = Title::makeTitleSafe( NS_FILE, $filename ); - $file = $this->dataFile( $filename ); - $iPage = new ImagePage( $title ); - $iPage->setFile( $file ); - return $iPage; - } - - /** - * @dataProvider providerGetDisplayWidthHeight - * @param array $dim Array [maxWidth, maxHeight, width, height] - * @param array $expected Array [width, height] The width and height we expect to display at - */ - function testGetDisplayWidthHeight( $dim, $expected ) { - $iPage = $this->getImagePage( 'animated.gif' ); - $reflection = new ReflectionClass( $iPage ); - $reflMethod = $reflection->getMethod( 'getDisplayWidthHeight' ); - $reflMethod->setAccessible( true ); - - $actual = $reflMethod->invoke( $iPage, $dim[0], $dim[1], $dim[2], $dim[3] ); - $this->assertEquals( $actual, $expected ); - } - - function providerGetDisplayWidthHeight() { - return array( - array( - array( 1024.0, 768.0, 600.0, 600.0 ), - array( 600.0, 600.0 ) - ), - array( - array( 1024.0, 768.0, 1600.0, 600.0 ), - array( 1024.0, 384.0 ) - ), - array( - array( 1024.0, 768.0, 1024.0, 768.0 ), - array( 1024.0, 768.0 ) - ), - array( - array( 1024.0, 768.0, 800.0, 1000.0 ), - array( 614.0, 768.0 ) - ), - array( - array( 1024.0, 768.0, 0, 1000 ), - array( 0, 0 ) - ), - array( - array( 1024.0, 768.0, 2000, 0 ), - array( 0, 0 ) - ), - ); - } - - /** - * @dataProvider providerGetThumbSizes - * @param string $filename - * @param int $expectedNumberThumbs How many thumbnails to show - */ - function testGetThumbSizes( $filename, $expectedNumberThumbs ) { - $iPage = $this->getImagePage( $filename ); - $reflection = new ReflectionClass( $iPage ); - $reflMethod = $reflection->getMethod( 'getThumbSizes' ); - $reflMethod->setAccessible( true ); - - $actual = $reflMethod->invoke( $iPage, 545, 700 ); - $this->assertEquals( count( $actual ), $expectedNumberThumbs ); - } - - function providerGetThumbSizes() { - return array( - array( 'animated.gif', 2 ), - array( 'Toll_Texas_1.svg', 1 ), - array( '80x60-Greyscale.xcf', 1 ), - array( 'jpeg-comment-binary.jpg', 2 ), - ); - } -} diff --git a/tests/phpunit/includes/ImportTest.php b/tests/phpunit/includes/ImportTest.php index 2fce6bfb..ea753e81 100644 --- a/tests/phpunit/includes/ImportTest.php +++ b/tests/phpunit/includes/ImportTest.php @@ -28,13 +28,14 @@ class ImportTest extends MediaWikiLangTestCase { $source = $this->getInputStreamSource( $xml ); $redirect = null; - $callback = function ( $title, $origTitle, $revCount, $sRevCount, $pageInfo ) use ( &$redirect ) { + $callback = function ( Title $title, ForeignTitle $foreignTitle, $revCount, + $sRevCount, $pageInfo ) use ( &$redirect ) { if ( array_key_exists( 'redirect', $pageInfo ) ) { $redirect = $pageInfo['redirect']; } }; - $importer = new WikiImporter( $source ); + $importer = new WikiImporter( $source, ConfigFactory::getDefaultInstance()->makeConfig( 'main' ) ); $importer->setPageOutCallback( $callback ); $importer->doImport(); @@ -45,7 +46,7 @@ class ImportTest extends MediaWikiLangTestCase { return array( array( <<< EOF - + Test 0 @@ -59,10 +60,10 @@ class ImportTest extends MediaWikiLangTestCase { 10 Admin moved page [[Test]] to [[Test22]] - #REDIRECT [[Test22]] - tq456o9x3abm7r9ozi6km8yrbbc56o6 wikitext text/x-wiki + #REDIRECT [[Test22]] + tq456o9x3abm7r9ozi6km8yrbbc56o6 @@ -72,7 +73,7 @@ EOF ), array( <<< EOF - + Test 0 @@ -98,4 +99,59 @@ EOF ); } + /** + * @covers WikiImporter::handleSiteInfo + * @dataProvider getSiteInfoXML + * @param string $xml + * @param array|null $namespaces + */ + public function testSiteInfoContainsNamespaces( $xml, $namespaces ) { + $source = $this->getInputStreamSource( $xml ); + + $importNamespaces = null; + $callback = function ( array $siteinfo, $innerImporter ) use ( &$importNamespaces ) { + $importNamespaces = $siteinfo['_namespaces']; + }; + + $importer = new WikiImporter( $source, ConfigFactory::getDefaultInstance()->makeConfig( 'main' ) ); + $importer->setSiteInfoCallback( $callback ); + $importer->doImport(); + + $this->assertEquals( $importNamespaces, $namespaces ); + } + + public function getSiteInfoXML() { + return array( + array( + <<< EOF + + + + Media + Special + + Talk + User + User talk + Portal + Portal talk + + + +EOF + , + array( + '-2' => 'Media', + '-1' => 'Special', + '0' => '', + '1' => 'Talk', + '2' => 'User', + '3' => 'User talk', + '100' => 'Portal', + '101' => 'Portal talk', + ) + ), + ); + } + } diff --git a/tests/phpunit/includes/LanguageConverterTest.php b/tests/phpunit/includes/LanguageConverterTest.php deleted file mode 100644 index d4ccca99..00000000 --- a/tests/phpunit/includes/LanguageConverterTest.php +++ /dev/null @@ -1,187 +0,0 @@ -setMwGlobals( array( - 'wgContLang' => Language::factory( 'tg' ), - 'wgLanguageCode' => 'tg', - 'wgDefaultLanguageVariant' => false, - 'wgMemc' => new EmptyBagOStuff, - 'wgRequest' => new FauxRequest( array() ), - 'wgUser' => new User, - ) ); - - $this->lang = new LanguageToTest(); - $this->lc = new TestConverter( - $this->lang, 'tg', - array( 'tg', 'tg-latn' ) - ); - } - - protected function tearDown() { - unset( $this->lc ); - unset( $this->lang ); - - parent::tearDown(); - } - - /** - * @covers LanguageConverter::getPreferredVariant - */ - public function testGetPreferredVariantDefaults() { - $this->assertEquals( 'tg', $this->lc->getPreferredVariant() ); - } - - /** - * @covers LanguageConverter::getPreferredVariant - * @covers LanguageConverter::getHeaderVariant - */ - public function testGetPreferredVariantHeaders() { - global $wgRequest; - $wgRequest->setHeader( 'Accept-Language', 'tg-latn' ); - - $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() ); - } - - /** - * @covers LanguageConverter::getPreferredVariant - * @covers LanguageConverter::getHeaderVariant - */ - public function testGetPreferredVariantHeaderWeight() { - global $wgRequest; - $wgRequest->setHeader( 'Accept-Language', 'tg;q=1' ); - - $this->assertEquals( 'tg', $this->lc->getPreferredVariant() ); - } - - /** - * @covers LanguageConverter::getPreferredVariant - * @covers LanguageConverter::getHeaderVariant - */ - public function testGetPreferredVariantHeaderWeight2() { - global $wgRequest; - $wgRequest->setHeader( 'Accept-Language', 'tg-latn;q=1' ); - - $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() ); - } - - /** - * @covers LanguageConverter::getPreferredVariant - * @covers LanguageConverter::getHeaderVariant - */ - public function testGetPreferredVariantHeaderMulti() { - global $wgRequest; - $wgRequest->setHeader( 'Accept-Language', 'en, tg-latn;q=1' ); - - $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() ); - } - - /** - * @covers LanguageConverter::getPreferredVariant - */ - public function testGetPreferredVariantUserOption() { - global $wgUser; - - $wgUser = new User; - $wgUser->load(); // from 'defaults' - $wgUser->mId = 1; - $wgUser->mDataLoaded = true; - $wgUser->mOptionsLoaded = true; - $wgUser->setOption( 'variant', 'tg-latn' ); - - $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() ); - } - - /** - * @covers LanguageConverter::getPreferredVariant - * @covers LanguageConverter::getUserVariant - */ - public function testGetPreferredVariantUserOptionForForeignLanguage() { - global $wgContLang, $wgUser; - - $wgContLang = Language::factory( 'en' ); - $wgUser = new User; - $wgUser->load(); // from 'defaults' - $wgUser->mId = 1; - $wgUser->mDataLoaded = true; - $wgUser->mOptionsLoaded = true; - $wgUser->setOption( 'variant-tg', 'tg-latn' ); - - $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() ); - } - - /** - * @covers LanguageConverter::getPreferredVariant - * @covers LanguageConverter::getUserVariant - * @covers LanguageConverter::getURLVariant - */ - public function testGetPreferredVariantHeaderUserVsUrl() { - global $wgContLang, $wgRequest, $wgUser; - - $wgContLang = Language::factory( 'tg-latn' ); - $wgRequest->setVal( 'variant', 'tg' ); - $wgUser = User::newFromId( "admin" ); - $wgUser->setId( 1 ); - $wgUser->mFrom = 'defaults'; - $wgUser->mOptionsLoaded = true; - // The user's data is ignored because the variant is set in the URL. - $wgUser->setOption( 'variant', 'tg-latn' ); - $this->assertEquals( 'tg', $this->lc->getPreferredVariant() ); - } - - /** - * @covers LanguageConverter::getPreferredVariant - */ - public function testGetPreferredVariantDefaultLanguageVariant() { - global $wgDefaultLanguageVariant; - - $wgDefaultLanguageVariant = 'tg-latn'; - $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() ); - } - - /** - * @covers LanguageConverter::getPreferredVariant - * @covers LanguageConverter::getURLVariant - */ - public function testGetPreferredVariantDefaultLanguageVsUrlVariant() { - global $wgDefaultLanguageVariant, $wgRequest, $wgContLang; - - $wgContLang = Language::factory( 'tg-latn' ); - $wgDefaultLanguageVariant = 'tg'; - $wgRequest->setVal( 'variant', null ); - $this->assertEquals( 'tg', $this->lc->getPreferredVariant() ); - } -} - -/** - * Test converter (from Tajiki to latin orthography) - */ -class TestConverter extends LanguageConverter { - private $table = array( - 'б' => 'b', - 'в' => 'v', - 'г' => 'g', - ); - - function loadDefaultTables() { - $this->mTables = array( - 'tg-latn' => new ReplacementArray( $this->table ), - 'tg' => new ReplacementArray() - ); - } -} - -class LanguageToTest extends Language { - function __construct() { - parent::__construct(); - $variants = array( 'tg', 'tg-latn' ); - $this->mConverter = new TestConverter( $this, 'tg', $variants ); - } -} diff --git a/tests/phpunit/includes/LinkerTest.php b/tests/phpunit/includes/LinkerTest.php index 7b84107e..823c9330 100644 --- a/tests/phpunit/includes/LinkerTest.php +++ b/tests/phpunit/includes/LinkerTest.php @@ -149,9 +149,13 @@ class LinkerTest extends MediaWikiLangTestCase { "pre /* autocomment */ post", ), array( - '/* autocomment */ multiple? autocomment2: ', + 'autocomment: multiple? autocomment2: ', "/* autocomment */ multiple? /* autocomment2 */ ", ), + array( + 'autocomment containing /*: T70361', + "/* autocomment containing /* */ T70361" + ), array( 'autocomment', "/* autocomment */", @@ -189,4 +193,54 @@ class LinkerTest extends MediaWikiLangTestCase { ), ); } + + /** + * @covers Linker::formatLinksInComment + * @dataProvider provideCasesForFormatLinksInComment + */ + public function testFormatLinksInComment( $expected, $input, $wiki ) { + + $conf = new SiteConfiguration(); + $conf->settings = array( + 'wgServer' => array( + 'enwiki' => '//en.example.org' + ), + 'wgArticlePath' => array( + 'enwiki' => '/w/$1', + ), + ); + $conf->suffixes = array( 'wiki' ); + $this->setMwGlobals( array( + 'wgScript' => '/wiki/index.php', + 'wgArticlePath' => '/wiki/$1', + 'wgWellFormedXml' => true, + 'wgCapitalLinks' => true, + 'wgConf' => $conf, + ) ); + + $this->assertEquals( + $expected, + Linker::formatLinksInComment( $input, Title::newFromText( 'Special:BlankPage' ), false, $wiki ) + ); + } + + public static function provideCasesForFormatLinksInComment() { + return array( + array( + 'foo bar Special:BlankPage', + 'foo bar [[Special:BlankPage]]', + null, + ), + array( + 'Foo\'bar', + "[[Foo'bar]]", + 'enwiki', + ), + array( + 'foo bar Special:BlankPage', + 'foo bar [[Special:BlankPage]]', + 'enwiki', + ), + ); + } } diff --git a/tests/phpunit/includes/LinksUpdateTest.php b/tests/phpunit/includes/LinksUpdateTest.php deleted file mode 100644 index 02f6b2ab..00000000 --- a/tests/phpunit/includes/LinksUpdateTest.php +++ /dev/null @@ -1,266 +0,0 @@ -tablesUsed = array_merge( $this->tablesUsed, - array( - 'interwiki', - 'page_props', - 'pagelinks', - 'categorylinks', - 'langlinks', - 'externallinks', - 'imagelinks', - 'templatelinks', - 'iwlinks' - ) - ); - } - - protected function setUp() { - parent::setUp(); - $dbw = wfGetDB( DB_MASTER ); - $dbw->replace( - 'interwiki', - array( 'iw_prefix' ), - array( - 'iw_prefix' => 'linksupdatetest', - 'iw_url' => 'http://testing.com/wiki/$1', - 'iw_api' => 'http://testing.com/w/api.php', - 'iw_local' => 0, - 'iw_trans' => 0, - 'iw_wikiid' => 'linksupdatetest', - ) - ); - } - - protected function makeTitleAndParserOutput( $name, $id ) { - $t = Title::newFromText( $name ); - $t->mArticleID = $id; # XXX: this is fugly - - $po = new ParserOutput(); - $po->setTitleText( $t->getPrefixedText() ); - - return array( $t, $po ); - } - - /** - * @covers ParserOutput::addLink - */ - public function testUpdate_pagelinks() { - /** @var ParserOutput $po */ - list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); - - $po->addLink( Title::newFromText( "Foo" ) ); - $po->addLink( Title::newFromText( "Special:Foo" ) ); // special namespace should be ignored - $po->addLink( Title::newFromText( "linksupdatetest:Foo" ) ); // interwiki link should be ignored - $po->addLink( Title::newFromText( "#Foo" ) ); // hash link should be ignored - - $update = $this->assertLinksUpdate( - $t, - $po, - 'pagelinks', - 'pl_namespace, - pl_title', - 'pl_from = 111', - array( array( NS_MAIN, 'Foo' ) ) - ); - $this->assertArrayEquals( array( - Title::makeTitle( NS_MAIN, 'Foo' ), // newFromText doesn't yield the same internal state.... - ), $update->getAddedLinks() ); - - $po = new ParserOutput(); - $po->setTitleText( $t->getPrefixedText() ); - - $po->addLink( Title::newFromText( "Bar" ) ); - $po->addLink( Title::newFromText( "Talk:Bar" ) ); - - $update = $this->assertLinksUpdate( - $t, - $po, - 'pagelinks', - 'pl_namespace, - pl_title', - 'pl_from = 111', - array( - array( NS_MAIN, 'Bar' ), - array( NS_TALK, 'Bar' ), - ) - ); - $this->assertArrayEquals( array( - Title::makeTitle( NS_MAIN, 'Bar' ), - Title::makeTitle( NS_TALK, 'Bar' ), - ), $update->getAddedLinks() ); - $this->assertArrayEquals( array( - Title::makeTitle( NS_MAIN, 'Foo' ), - ), $update->getRemovedLinks() ); - } - - /** - * @covers ParserOutput::addExternalLink - */ - public function testUpdate_externallinks() { - /** @var ParserOutput $po */ - list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); - - $po->addExternalLink( "http://testing.com/wiki/Foo" ); - - $this->assertLinksUpdate( $t, $po, 'externallinks', 'el_to, el_index', 'el_from = 111', array( - array( 'http://testing.com/wiki/Foo', 'http://com.testing./wiki/Foo' ), - ) ); - } - - /** - * @covers ParserOutput::addCategory - */ - public function testUpdate_categorylinks() { - /** @var ParserOutput $po */ - $this->setMwGlobals( 'wgCategoryCollation', 'uppercase' ); - - list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); - - $po->addCategory( "Foo", "FOO" ); - - $this->assertLinksUpdate( $t, $po, 'categorylinks', 'cl_to, cl_sortkey', 'cl_from = 111', array( - array( 'Foo', "FOO\nTESTING" ), - ) ); - } - - /** - * @covers ParserOutput::addInterwikiLink - */ - public function testUpdate_iwlinks() { - /** @var ParserOutput $po */ - list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); - - $target = Title::makeTitleSafe( NS_MAIN, "Foo", '', 'linksupdatetest' ); - $po->addInterwikiLink( $target ); - - $this->assertLinksUpdate( $t, $po, 'iwlinks', 'iwl_prefix, iwl_title', 'iwl_from = 111', array( - array( 'linksupdatetest', 'Foo' ), - ) ); - } - - /** - * @covers ParserOutput::addTemplate - */ - public function testUpdate_templatelinks() { - /** @var ParserOutput $po */ - list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); - - $po->addTemplate( Title::newFromText( "Template:Foo" ), 23, 42 ); - - $this->assertLinksUpdate( - $t, - $po, - 'templatelinks', - 'tl_namespace, - tl_title', - 'tl_from = 111', - array( array( NS_TEMPLATE, 'Foo' ) ) - ); - } - - /** - * @covers ParserOutput::addImage - */ - public function testUpdate_imagelinks() { - /** @var ParserOutput $po */ - list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); - - $po->addImage( "Foo.png" ); - - $this->assertLinksUpdate( $t, $po, 'imagelinks', 'il_to', 'il_from = 111', array( - array( 'Foo.png' ), - ) ); - } - - /** - * @covers ParserOutput::addLanguageLink - */ - public function testUpdate_langlinks() { - $this->setMwGlobals( array( - 'wgCapitalLinks' => true, - ) ); - - /** @var ParserOutput $po */ - list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); - - $po->addLanguageLink( Title::newFromText( "en:Foo" )->getFullText() ); - - $this->assertLinksUpdate( $t, $po, 'langlinks', 'll_lang, ll_title', 'll_from = 111', array( - array( 'En', 'Foo' ), - ) ); - } - - /** - * @covers ParserOutput::setProperty - */ - public function testUpdate_page_props() { - global $wgPagePropsHaveSortkey; - - /** @var ParserOutput $po */ - list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); - - $fields = array( 'pp_propname', 'pp_value' ); - $expected = array(); - - $po->setProperty( "bool", true ); - $expected[] = array( "bool", true ); - - $po->setProperty( "float", 4.0 + 1.0 / 4.0 ); - $expected[] = array( "float", 4.0 + 1.0 / 4.0 ); - - $po->setProperty( "int", -7 ); - $expected[] = array( "int", -7 ); - - $po->setProperty( "string", "33 bar" ); - $expected[] = array( "string", "33 bar" ); - - // compute expected sortkey values - if ( $wgPagePropsHaveSortkey ) { - $fields[] = 'pp_sortkey'; - - foreach ( $expected as &$row ) { - $value = $row[1]; - - if ( is_int( $value ) || is_float( $value ) || is_bool( $value ) ) { - $row[] = floatval( $value ); - } else { - $row[] = null; - } - } - } - - $this->assertLinksUpdate( $t, $po, 'page_props', $fields, 'pp_page = 111', $expected ); - } - - public function testUpdate_page_props_without_sortkey() { - $this->setMwGlobals( 'wgPagePropsHaveSortkey', false ); - - $this->testUpdate_page_props(); - } - - // @todo test recursive, too! - - protected function assertLinksUpdate( Title $title, ParserOutput $parserOutput, - $table, $fields, $condition, array $expectedRows - ) { - $update = new LinksUpdate( $title, $parserOutput ); - - //NOTE: make sure LinksUpdate does not generate warnings when called inside a transaction. - $update->beginTransaction(); - $update->doUpdate(); - $update->commitTransaction(); - - $this->assertSelect( $table, $fields, $condition, $expectedRows ); - return $update; - } -} diff --git a/tests/phpunit/includes/LocalFileTest.php b/tests/phpunit/includes/LocalFileTest.php deleted file mode 100644 index 5c5052e4..00000000 --- a/tests/phpunit/includes/LocalFileTest.php +++ /dev/null @@ -1,184 +0,0 @@ -setMwGlobals( 'wgCapitalLinks', true ); - - $info = array( - 'name' => 'test', - 'directory' => '/testdir', - 'url' => '/testurl', - 'hashLevels' => 2, - 'transformVia404' => false, - 'backend' => new FSFileBackend( array( - 'name' => 'local-backend', - 'wikiId' => wfWikiId(), - 'containerPaths' => array( - 'cont1' => "/testdir/local-backend/tempimages/cont1", - 'cont2' => "/testdir/local-backend/tempimages/cont2" - ) - ) ) - ); - $this->repo_hl0 = new LocalRepo( array( 'hashLevels' => 0 ) + $info ); - $this->repo_hl2 = new LocalRepo( array( 'hashLevels' => 2 ) + $info ); - $this->repo_lc = new LocalRepo( array( 'initialCapital' => false ) + $info ); - $this->file_hl0 = $this->repo_hl0->newFile( 'test!' ); - $this->file_hl2 = $this->repo_hl2->newFile( 'test!' ); - $this->file_lc = $this->repo_lc->newFile( 'test!' ); - } - - /** - * @covers File::getHashPath - */ - public function testGetHashPath() { - $this->assertEquals( '', $this->file_hl0->getHashPath() ); - $this->assertEquals( 'a/a2/', $this->file_hl2->getHashPath() ); - $this->assertEquals( 'c/c4/', $this->file_lc->getHashPath() ); - } - - /** - * @covers File::getRel - */ - public function testGetRel() { - $this->assertEquals( 'Test!', $this->file_hl0->getRel() ); - $this->assertEquals( 'a/a2/Test!', $this->file_hl2->getRel() ); - $this->assertEquals( 'c/c4/test!', $this->file_lc->getRel() ); - } - - /** - * @covers File::getUrlRel - */ - public function testGetUrlRel() { - $this->assertEquals( 'Test%21', $this->file_hl0->getUrlRel() ); - $this->assertEquals( 'a/a2/Test%21', $this->file_hl2->getUrlRel() ); - $this->assertEquals( 'c/c4/test%21', $this->file_lc->getUrlRel() ); - } - - /** - * @covers File::getArchivePath - */ - public function testGetArchivePath() { - $this->assertEquals( - 'mwstore://local-backend/test-public/archive', - $this->file_hl0->getArchivePath() - ); - $this->assertEquals( - 'mwstore://local-backend/test-public/archive/a/a2', - $this->file_hl2->getArchivePath() - ); - $this->assertEquals( - 'mwstore://local-backend/test-public/archive/!', - $this->file_hl0->getArchivePath( '!' ) - ); - $this->assertEquals( - 'mwstore://local-backend/test-public/archive/a/a2/!', - $this->file_hl2->getArchivePath( '!' ) - ); - } - - /** - * @covers File::getThumbPath - */ - public function testGetThumbPath() { - $this->assertEquals( - 'mwstore://local-backend/test-thumb/Test!', - $this->file_hl0->getThumbPath() - ); - $this->assertEquals( - 'mwstore://local-backend/test-thumb/a/a2/Test!', - $this->file_hl2->getThumbPath() - ); - $this->assertEquals( - 'mwstore://local-backend/test-thumb/Test!/x', - $this->file_hl0->getThumbPath( 'x' ) - ); - $this->assertEquals( - 'mwstore://local-backend/test-thumb/a/a2/Test!/x', - $this->file_hl2->getThumbPath( 'x' ) - ); - } - - /** - * @covers File::getArchiveUrl - */ - public function testGetArchiveUrl() { - $this->assertEquals( '/testurl/archive', $this->file_hl0->getArchiveUrl() ); - $this->assertEquals( '/testurl/archive/a/a2', $this->file_hl2->getArchiveUrl() ); - $this->assertEquals( '/testurl/archive/%21', $this->file_hl0->getArchiveUrl( '!' ) ); - $this->assertEquals( '/testurl/archive/a/a2/%21', $this->file_hl2->getArchiveUrl( '!' ) ); - } - - /** - * @covers File::getThumbUrl - */ - public function testGetThumbUrl() { - $this->assertEquals( '/testurl/thumb/Test%21', $this->file_hl0->getThumbUrl() ); - $this->assertEquals( '/testurl/thumb/a/a2/Test%21', $this->file_hl2->getThumbUrl() ); - $this->assertEquals( '/testurl/thumb/Test%21/x', $this->file_hl0->getThumbUrl( 'x' ) ); - $this->assertEquals( '/testurl/thumb/a/a2/Test%21/x', $this->file_hl2->getThumbUrl( 'x' ) ); - } - - /** - * @covers File::getArchiveVirtualUrl - */ - public function testGetArchiveVirtualUrl() { - $this->assertEquals( 'mwrepo://test/public/archive', $this->file_hl0->getArchiveVirtualUrl() ); - $this->assertEquals( - 'mwrepo://test/public/archive/a/a2', - $this->file_hl2->getArchiveVirtualUrl() - ); - $this->assertEquals( - 'mwrepo://test/public/archive/%21', - $this->file_hl0->getArchiveVirtualUrl( '!' ) - ); - $this->assertEquals( - 'mwrepo://test/public/archive/a/a2/%21', - $this->file_hl2->getArchiveVirtualUrl( '!' ) - ); - } - - /** - * @covers File::getThumbVirtualUrl - */ - public function testGetThumbVirtualUrl() { - $this->assertEquals( 'mwrepo://test/thumb/Test%21', $this->file_hl0->getThumbVirtualUrl() ); - $this->assertEquals( 'mwrepo://test/thumb/a/a2/Test%21', $this->file_hl2->getThumbVirtualUrl() ); - $this->assertEquals( - 'mwrepo://test/thumb/Test%21/%21', - $this->file_hl0->getThumbVirtualUrl( '!' ) - ); - $this->assertEquals( - 'mwrepo://test/thumb/a/a2/Test%21/%21', - $this->file_hl2->getThumbVirtualUrl( '!' ) - ); - } - - /** - * @covers File::getUrl - */ - public function testGetUrl() { - $this->assertEquals( '/testurl/Test%21', $this->file_hl0->getUrl() ); - $this->assertEquals( '/testurl/a/a2/Test%21', $this->file_hl2->getUrl() ); - } - - /** - * @covers ::wfLocalFile - */ - public function testWfLocalFile() { - $file = wfLocalFile( "File:Some_file_that_probably_doesn't exist.png" ); - $this->assertThat( - $file, - $this->isInstanceOf( 'LocalFile' ), - 'wfLocalFile() returns LocalFile for valid Titles' - ); - } -} diff --git a/tests/phpunit/includes/MWFunctionTest.php b/tests/phpunit/includes/MWFunctionTest.php deleted file mode 100644 index f2a720e8..00000000 --- a/tests/phpunit/includes/MWFunctionTest.php +++ /dev/null @@ -1,33 +0,0 @@ -assertEquals( - MWFunction::newObj( 'MWBlankClass', $args )->args, - $newObject->args - ); - } -} - -class MWBlankClass { - - public $args = array(); - - function __construct( $arg1, $arg2, $arg3, $arg4 ) { - $this->args = array( $arg1, $arg2, $arg3, $arg4 ); - } -} - -class ExampleObject { -} diff --git a/tests/phpunit/includes/MWTimestampTest.php b/tests/phpunit/includes/MWTimestampTest.php index dcb98563..36562545 100644 --- a/tests/phpunit/includes/MWTimestampTest.php +++ b/tests/phpunit/includes/MWTimestampTest.php @@ -8,6 +8,9 @@ class MWTimestampTest extends MediaWikiLangTestCase { protected function setUp() { parent::setUp(); + // Avoid 'GetHumanTimestamp' hook and others + $this->setMwGlobals( 'wgHooks', array() ); + RequestContext::getMain()->setLanguage( Language::factory( 'en' ) ); } @@ -78,6 +81,17 @@ class MWTimestampTest extends MediaWikiLangTestCase { new MWTimestamp( "This is not a timestamp." ); } + /** + * Test an out of range timestamp + * @dataProvider provideOutOfRangeTimestamps + * @expectedException TimestampException + * @covers MWTimestamp + */ + public function testOutOfRangeTimestamps( $format, $input ) { + $timestamp = new MWTimestamp( $input ); + $timestamp->getTimestamp( $format ); + } + /** * Test requesting an invalid output format. * @expectedException TimestampException @@ -110,6 +124,18 @@ class MWTimestampTest extends MediaWikiLangTestCase { ); } + /** + * Returns a list of out of range timestamps in the format: + * array( type, timestamp_of_type ) + */ + public static function provideOutOfRangeTimestamps() { + return array( + // Various formats + array( TS_MW, '-62167219201' ), // -0001-12-31T23:59:59Z + array( TS_MW, '253402300800' ), // 10000-01-01T00:00:00Z + ); + } + /** * @dataProvider provideHumanTimestampTests * @covers MWTimestamp::getHumanTimestamp diff --git a/tests/phpunit/includes/MediaWikiVersionFetcherTest.php b/tests/phpunit/includes/MediaWikiVersionFetcherTest.php index e548f817..fa59ef29 100644 --- a/tests/phpunit/includes/MediaWikiVersionFetcherTest.php +++ b/tests/phpunit/includes/MediaWikiVersionFetcherTest.php @@ -8,7 +8,6 @@ * * @group ComposerHooks * - * @licence GNU GPL v2+ * @author Jeroen De Dauw < jeroendedauw@gmail.com > */ class MediaWikiVersionFetcherTest extends PHPUnit_Framework_TestCase { diff --git a/tests/phpunit/includes/MessageTest.php b/tests/phpunit/includes/MessageTest.php index f3d2a84a..99ec2e42 100644 --- a/tests/phpunit/includes/MessageTest.php +++ b/tests/phpunit/includes/MessageTest.php @@ -16,22 +16,11 @@ class MessageTest extends MediaWikiLangTestCase { * @dataProvider provideConstructor */ public function testConstructor( $expectedLang, $key, $params, $language ) { - $reflection = new ReflectionClass( 'Message' ); - - $keyProperty = $reflection->getProperty( 'key' ); - $keyProperty->setAccessible( true ); - - $paramsProperty = $reflection->getProperty( 'parameters' ); - $paramsProperty->setAccessible( true ); - - $langProperty = $reflection->getProperty( 'language' ); - $langProperty->setAccessible( true ); - $message = new Message( $key, $params, $language ); - $this->assertEquals( $key, $keyProperty->getValue( $message ) ); - $this->assertEquals( $params, $paramsProperty->getValue( $message ) ); - $this->assertEquals( $expectedLang, $langProperty->getValue( $message ) ); + $this->assertEquals( $key, $message->getKey() ); + $this->assertEquals( $params, $message->getParams() ); + $this->assertEquals( $expectedLang, $message->getLanguage() ); } public static function provideConstructor() { @@ -45,21 +34,62 @@ class MessageTest extends MediaWikiLangTestCase { ); } - public static function provideTestParams() { + public static function provideConstructorParams() { return array( - array( array() ), - array( array( 'foo' ), 'foo' ), - array( array( 'foo', 'bar' ), 'foo', 'bar' ), - array( array( 'baz' ), array( 'baz' ) ), - array( array( 'baz', 'foo' ), array( 'baz', 'foo' ) ), - array( array( 'baz', 'foo' ), array( 'baz', 'foo' ), 'hhh' ), - array( array( 'baz', 'foo' ), array( 'baz', 'foo' ), 'hhh', array( 'ahahahahha' ) ), - array( array( 'baz', 'foo' ), array( 'baz', 'foo' ), array( 'ahahahahha' ) ), - array( array( 'baz' ), array( 'baz' ), array( 'ahahahahha' ) ), + array( + array(), + array(), + ), + array( + array( 'foo' ), + array( 'foo' ), + ), + array( + array( 'foo', 'bar' ), + array( 'foo', 'bar' ), + ), + array( + array( 'baz' ), + array( array( 'baz' ) ), + ), + array( + array( 'baz', 'foo' ), + array( array( 'baz', 'foo' ) ), + ), + array( + array( 'baz', 'foo' ), + array( array( 'baz', 'foo' ), 'hhh' ), + ), + array( + array( 'baz', 'foo' ), + array( array( 'baz', 'foo' ), 'hhh', array( 'ahahahahha' ) ), + ), + array( + array( 'baz', 'foo' ), + array( array( 'baz', 'foo' ), array( 'ahahahahha' ) ), + ), + array( + array( 'baz' ), + array( array( 'baz' ), array( 'ahahahahha' ) ), + ), ); } - public function getLanguageProvider() { + /** + * @covers Message::__construct + * @covers Message::getParams + * @dataProvider provideConstructorParams + */ + public function testConstructorParams( $expected, $args ) { + $msg = new Message( 'imasomething' ); + + $returned = call_user_func_array( array( $msg, 'params' ), $args ); + + $this->assertSame( $msg, $returned ); + $this->assertEquals( $expected, $msg->getParams() ); + } + + public static function provideConstructorLanguage() { return array( array( 'foo', array( 'bar' ), 'en' ), array( 'foo', array( 'bar' ), 'de' ) @@ -67,27 +97,98 @@ class MessageTest extends MediaWikiLangTestCase { } /** + * @covers Message::__construct * @covers Message::getLanguage - * @dataProvider getLanguageProvider + * @dataProvider provideConstructorLanguage */ - public function testGetLanguageCode( $key, $params, $languageCode ) { + public function testConstructorLanguage( $key, $params, $languageCode ) { $language = Language::factory( $languageCode ); $message = new Message( $key, $params, $language ); $this->assertEquals( $language, $message->getLanguage() ); } + public static function provideKeys() { + return array( + 'string' => array( + 'key' => 'mainpage', + 'expected' => array( 'mainpage' ), + ), + 'single' => array( + 'key' => array( 'mainpage' ), + 'expected' => array( 'mainpage' ), + ), + 'multi' => array( + 'key' => array( 'mainpage-foo', 'mainpage-bar', 'mainpage' ), + 'expected' => array( 'mainpage-foo', 'mainpage-bar', 'mainpage' ), + ), + 'empty' => array( + 'key' => array(), + 'expected' => null, + 'exception' => 'InvalidArgumentException', + ), + 'null' => array( + 'key' => null, + 'expected' => null, + 'exception' => 'InvalidArgumentException', + ), + 'bad type' => array( + 'key' => 123, + 'expected' => null, + 'exception' => 'InvalidArgumentException', + ), + ); + } + /** - * @covers Message::params - * @dataProvider provideTestParams + * @covers Message::__construct + * @covers Message::getKey + * @covers Message::isMultiKey + * @covers Message::getKeysToTry + * @dataProvider provideKeys */ - public function testParams( $expected ) { - $msg = new Message( 'imasomething' ); + public function testKeys( $key, $expected, $exception = null ) { + if ( $exception ) { + $this->setExpectedException( $exception ); + } + + $msg = new Message( $key ); + $this->assertContains( $msg->getKey(), $expected ); + $this->assertEquals( $expected, $msg->getKeysToTry() ); + $this->assertEquals( count( $expected ) > 1, $msg->isMultiKey() ); + } - $returned = call_user_func_array( array( $msg, 'params' ), array_slice( func_get_args(), 1 ) ); + /** + * @covers ::wfMessage + */ + public function testWfMessage() { + $this->assertInstanceOf( 'Message', wfMessage( 'mainpage' ) ); + $this->assertInstanceOf( 'Message', wfMessage( 'i-dont-exist-evar' ) ); + } - $this->assertSame( $msg, $returned ); - $this->assertEquals( $expected, $msg->getParams() ); + /** + * @covers Message::newFromKey + */ + public function testNewFromKey() { + $this->assertInstanceOf( 'Message', Message::newFromKey( 'mainpage' ) ); + $this->assertInstanceOf( 'Message', Message::newFromKey( 'i-dont-exist-evar' ) ); + } + + /** + * @covers ::wfMessage + * @covers Message::__construct + */ + public function testWfMessageParams() { + $this->assertEquals( 'Return to $1.', wfMessage( 'returnto' )->text() ); + $this->assertEquals( 'Return to $1.', wfMessage( 'returnto', array() )->text() ); + $this->assertEquals( + 'You have foo (bar).', + wfMessage( 'youhavenewmessages', 'foo', 'bar' )->text() + ); + $this->assertEquals( + 'You have foo (bar).', + wfMessage( 'youhavenewmessages', array( 'foo', 'bar' ) )->text() + ); } /** @@ -104,14 +205,42 @@ class MessageTest extends MediaWikiLangTestCase { /** * @covers Message::__construct + * @covers Message::text + * @covers Message::plain + * @covers Message::escaped + * @covers Message::toString */ - public function testKey() { - $this->assertInstanceOf( 'Message', wfMessage( 'mainpage' ) ); - $this->assertInstanceOf( 'Message', wfMessage( 'i-dont-exist-evar' ) ); + public function testToStringKey() { $this->assertEquals( 'Main Page', wfMessage( 'mainpage' )->text() ); - $this->assertEquals( '<i-dont-exist-evar>', wfMessage( 'i-dont-exist-evar' )->text() ); + $this->assertEquals( '', wfMessage( 'i-dont-exist-evar' )->text() ); + $this->assertEquals( 'exist-evar>', wfMessage( 'iexist-evar' )->text() ); $this->assertEquals( '', wfMessage( 'i-dont-exist-evar' )->plain() ); + $this->assertEquals( 'exist-evar>', wfMessage( 'iexist-evar' )->plain() ); $this->assertEquals( '<i-dont-exist-evar>', wfMessage( 'i-dont-exist-evar' )->escaped() ); + $this->assertEquals( + '<i<dont>exist-evar>', + wfMessage( 'iexist-evar' )->escaped() + ); + } + + public static function provideToString() { + return array( + array( 'mainpage', 'Main Page' ), + array( 'i-dont-exist-evar', '' ), + array( 'i-dont-exist-evar', '<i-dont-exist-evar>', 'escaped' ), + ); + } + + /** + * @covers Message::toString + * @covers Message::__toString + * @dataProvider provideToString + */ + public function testToString( $key, $expect, $format = 'plain' ) { + $msg = new Message( $key ); + $msg->$format(); + $this->assertEquals( $expect, $msg->toString() ); + $this->assertEquals( $expect, $msg->__toString() ); } /** @@ -132,26 +261,10 @@ class MessageTest extends MediaWikiLangTestCase { } /** - * @covers Message::__construct - */ - public function testMessageParams() { - $this->assertEquals( 'Return to $1.', wfMessage( 'returnto' )->text() ); - $this->assertEquals( 'Return to $1.', wfMessage( 'returnto', array() )->text() ); - $this->assertEquals( - 'You have foo (bar).', - wfMessage( 'youhavenewmessages', 'foo', 'bar' )->text() - ); - $this->assertEquals( - 'You have foo (bar).', - wfMessage( 'youhavenewmessages', array( 'foo', 'bar' ) )->text() - ); - } - - /** - * @covers Message::__construct + * @covers Message::rawParam * @covers Message::rawParams */ - public function testMessageParamSubstitution() { + public function testRawParams() { $this->assertEquals( '(Заглавная страница)', wfMessage( 'parentheses', 'Заглавная страница' )->plain() @@ -171,10 +284,21 @@ class MessageTest extends MediaWikiLangTestCase { } /** - * @covers Message::__construct + * @covers RawMessage::__construct + * @covers RawMessage::fetchMessage + */ + public function testRawMessage() { + $msg = new RawMessage( 'example &' ); + $this->assertEquals( 'example &', $msg->plain() ); + $this->assertEquals( 'example &', $msg->escaped() ); + } + + /** * @covers Message::params + * @covers Message::toString + * @covers Message::replaceParameters */ - public function testDeliciouslyManyParams() { + public function testReplaceManyParams() { $msg = new RawMessage( '$1$2$3$4$5$6$7$8$9$10$11$12' ); // One less than above has placeholders $params = array( 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k' ); @@ -183,12 +307,20 @@ class MessageTest extends MediaWikiLangTestCase { $msg->params( $params )->plain(), 'Params > 9 are replaced correctly' ); + + $msg = new RawMessage( 'Params$*' ); + $params = array( 'ab', 'bc', 'cd' ); + $this->assertEquals( + 'Params: ab, bc, cd', + $msg->params( $params )->text() + ); } /** + * @covers Message::numParam * @covers Message::numParams */ - public function testMessageNumParams() { + public function testNumParams() { $lang = Language::factory( 'en' ); $msg = new RawMessage( '$1' ); @@ -200,9 +332,10 @@ class MessageTest extends MediaWikiLangTestCase { } /** + * @covers Message::durationParam * @covers Message::durationParams */ - public function testMessageDurationParams() { + public function testDurationParams() { $lang = Language::factory( 'en' ); $msg = new RawMessage( '$1' ); @@ -216,9 +349,10 @@ class MessageTest extends MediaWikiLangTestCase { /** * FIXME: This should not need database, but Language#formatExpiry does (bug 55912) * @group Database + * @covers Message::expiryParam * @covers Message::expiryParams */ - public function testMessageExpiryParams() { + public function testExpiryParams() { $lang = Language::factory( 'en' ); $msg = new RawMessage( '$1' ); @@ -230,9 +364,10 @@ class MessageTest extends MediaWikiLangTestCase { } /** + * @covers Message::timeperiodParam * @covers Message::timeperiodParams */ - public function testMessageTimeperiodParams() { + public function testTimeperiodParams() { $lang = Language::factory( 'en' ); $msg = new RawMessage( '$1' ); @@ -244,9 +379,10 @@ class MessageTest extends MediaWikiLangTestCase { } /** + * @covers Message::sizeParam * @covers Message::sizeParams */ - public function testMessageSizeParams() { + public function testSizeParams() { $lang = Language::factory( 'en' ); $msg = new RawMessage( '$1' ); @@ -258,9 +394,10 @@ class MessageTest extends MediaWikiLangTestCase { } /** + * @covers Message::bitrateParam * @covers Message::bitrateParams */ - public function testMessageBitrateParams() { + public function testBitrateParams() { $lang = Language::factory( 'en' ); $msg = new RawMessage( '$1' ); @@ -271,6 +408,100 @@ class MessageTest extends MediaWikiLangTestCase { ); } + public static function providePlaintextParams() { + return array( + array( + 'one $2
foo
[[Bar]] {{Baz}} <', + 'plain', + ), + + array( + // expect + 'one $2
foo
[[Bar]] {{Baz}} <', + // format + 'text', + ), + array( + 'one $2 <div>foo</div> [[Bar]] {{Baz}} &lt;', + 'escaped', + ), + + array( + 'one $2 <div>foo</div> [[Bar]] {{Baz}} &lt;', + 'parse', + ), + + array( + "

one $2 <div>foo</div> [[Bar]] {{Baz}} &lt;\n

", + 'parseAsBlock', + ), + ); + } + + /** + * @covers Message::plaintextParam + * @covers Message::plaintextParams + * @covers Message::formatPlaintext + * @covers Message::toString + * @covers Message::parse + * @covers Message::parseAsBlock + * @dataProvider providePlaintextParams + */ + public function testPlaintextParams( $expect, $format ) { + $lang = Language::factory( 'en' ); + + $msg = new RawMessage( '$1 $2' ); + $params = array( + 'one $2', + '
foo
[[Bar]] {{Baz}} <', + ); + $this->assertEquals( + $expect, + $msg->inLanguage( $lang )->plaintextParams( $params )->$format(), + "Fail formatting for $format" + ); + } + + public static function provideParser() { + return array( + array( + "''&'' ", + 'plain', + ), + + array( + "''&'' ", + 'text', + ), + array( + '& <x>', + 'parse', + ), + + array( + "

& <x>\n

", + 'parseAsBlock', + ), + ); + } + + /** + * @covers Message::text + * @covers Message::parse + * @covers Message::parseAsBlock + * @covers Message::toString + * @covers Message::transformText + * @covers Message::parseText + * @dataProvider provideParser + */ + public function testParser( $expect, $format ) { + $msg = new RawMessage( "''&'' " ); + $this->assertEquals( + $expect, + $msg->inLanguage( 'en' )->$format() + ); + } + /** * @covers Message::inContentLanguage */ @@ -317,52 +548,4 @@ class MessageTest extends MediaWikiLangTestCase { public function testInLanguageThrows() { wfMessage( 'foo' )->inLanguage( 123 ); } - - public function keyProvider() { - return array( - 'string' => array( - 'key' => 'mainpage', - 'expected' => array( 'mainpage' ), - ), - 'single' => array( - 'key' => array( 'mainpage' ), - 'expected' => array( 'mainpage' ), - ), - 'multi' => array( - 'key' => array( 'mainpage-foo', 'mainpage-bar', 'mainpage' ), - 'expected' => array( 'mainpage-foo', 'mainpage-bar', 'mainpage' ), - ), - 'empty' => array( - 'key' => array(), - 'expected' => null, - 'exception' => 'InvalidArgumentException', - ), - 'null' => array( - 'key' => null, - 'expected' => null, - 'exception' => 'InvalidArgumentException', - ), - 'bad type' => array( - 'key' => 17, - 'expected' => null, - 'exception' => 'InvalidArgumentException', - ), - ); - } - - /** - * @dataProvider keyProvider() - * - * @covers Message::getKey - */ - public function testGetKey( $key, $expected, $exception = null ) { - if ( $exception ) { - $this->setExpectedException( $exception ); - } - - $msg = new Message( $key ); - $this->assertEquals( $expected, $msg->getKeysToTry() ); - $this->assertEquals( count( $expected ) > 1, $msg->isMultiKey() ); - $this->assertContains( $msg->getKey(), $expected ); - } } diff --git a/tests/phpunit/includes/MovePageTest.php b/tests/phpunit/includes/MovePageTest.php new file mode 100644 index 00000000..9501e452 --- /dev/null +++ b/tests/phpunit/includes/MovePageTest.php @@ -0,0 +1,63 @@ +setMwGlobals( 'wgContentHandlerUseDB', false ); + $mp = new MovePage( + Title::newFromText( $old ), + Title::newFromText( $new ) + ); + $status = $mp->isValidMove(); + if ( $error === true ) { + $this->assertTrue( $status->isGood() ); + } else { + $this->assertTrue( $status->hasMessage( $error ) ); + } + } + + /** + * This should be kept in sync with TitleTest::provideTestIsValidMoveOperation + */ + public static function provideIsValidMove() { + return array( + // for MovePage::isValidMove + array( 'Test', 'Test', 'selfmove' ), + array( 'Special:FooBar', 'Test', 'immobile-source-namespace' ), + array( 'Test', 'Special:FooBar', 'immobile-target-namespace' ), + array( 'MediaWiki:Common.js', 'Help:Some wikitext page', 'bad-target-model' ), + array( 'Page', 'File:Test.jpg', 'nonfile-cannot-move-to-file' ), + // for MovePage::isValidFileMove + array( 'File:Test.jpg', 'Page', 'imagenocrossnamespace' ), + ); + } + + /** + * Integration test to catch regressions like T74870. Taken and modified + * from SemanticMediaWiki + */ + public function testTitleMoveCompleteIntegrationTest() { + $oldTitle = Title::newFromText( 'Help:Some title' ); + WikiPage::factory( $oldTitle )->doEditContent( new WikitextContent( 'foo' ), 'bar' ); + $newTitle = Title::newFromText( 'Help:Some other title' ); + $this->assertNull( + WikiPage::factory( $newTitle )->getRevision() + ); + + $this->assertTrue( $oldTitle->moveTo( $newTitle, false, 'test1', true ) ); + $this->assertNotNull( + WikiPage::factory( $oldTitle )->getRevision() + ); + $this->assertNotNull( + WikiPage::factory( $newTitle)->getRevision() + ); + } +} diff --git a/tests/phpunit/includes/OutputPageTest.php b/tests/phpunit/includes/OutputPageTest.php index d7e8cd31..6c6d95ee 100644 --- a/tests/phpunit/includes/OutputPageTest.php +++ b/tests/phpunit/includes/OutputPageTest.php @@ -172,18 +172,18 @@ mw.test.baz({token:123});mw.loader.state({"test.quux":"ready"}); array( array( 'test.quux', ResourceLoaderModule::TYPE_COMBINED ), ' ' ), - // Load module script with with ESI + // Load module script with ESI array( array( 'test.foo', ResourceLoaderModule::TYPE_SCRIPTS, true ), ' ' ), - // Load module styles with with ESI + // Load module styles with ESI array( array( 'test.foo', ResourceLoaderModule::TYPE_STYLES, true ), ' @@ -203,9 +203,13 @@ mw.loader.implement("test.quux",function($,jQuery){mw.test.baz({token:123});},{" // Load two modules in separate groups array( array( array( 'test.group.foo', 'test.group.bar' ), ResourceLoaderModule::TYPE_COMBINED ), - ' - -', + ' + +' ), ); } @@ -213,6 +217,11 @@ mw.loader.implement("test.quux",function($,jQuery){mw.test.baz({token:123});},{" /** * @dataProvider provideMakeResourceLoaderLink * @covers OutputPage::makeResourceLoaderLink + * @covers ResourceLoader::makeLoaderImplementScript + * @covers ResourceLoader::makeModuleResponse + * @covers ResourceLoader::makeInlineScript + * @covers ResourceLoader::makeLoaderStateScript + * @covers ResourceLoader::createLoaderURL */ public function testMakeResourceLoaderLink( $args, $expectedHtml ) { $this->setMwGlobals( array( @@ -230,6 +239,7 @@ mw.loader.implement("test.quux",function($,jQuery){mw.test.baz({token:123});},{" $ctx->setLanguage( 'en' ); $out = new OutputPage( $ctx ); $rl = $out->getResourceLoader(); + $rl->setMessageBlobStore( new NullMessageBlobStore() ); $rl->register( array( 'test.foo' => new ResourceLoaderTestModule( array( 'script' => 'mw.test.foo( { a: true } );', @@ -271,3 +281,26 @@ mw.loader.implement("test.quux",function($,jQuery){mw.test.baz({token:123});},{" $this->assertEquals( $expectedHtml, $actualHtml ); } } + +/** + * MessageBlobStore that doesn't do anything + */ +class NullMessageBlobStore extends MessageBlobStore { + public function get ( ResourceLoader $resourceLoader, $modules, $lang ) { + return array(); + } + + public function insertMessageBlob ( $name, ResourceLoaderModule $module, $lang ) { + return false; + } + + public function updateModule ( $name, ResourceLoaderModule $module, $lang ) { + return; + } + + public function updateMessage ( $key ) { + } + public function clear() { + } +} + diff --git a/tests/phpunit/includes/PasswordTest.php b/tests/phpunit/includes/PasswordTest.php deleted file mode 100644 index ceb794b5..00000000 --- a/tests/phpunit/includes/PasswordTest.php +++ /dev/null @@ -1,33 +0,0 @@ -newFromCiphertext( null ); - $invalid2 = User::getPasswordFactory()->newFromCiphertext( null ); - - $this->assertFalse( $invalid1->equals( $invalid2 ) ); - } -} diff --git a/tests/phpunit/includes/PrefixSearchTest.php b/tests/phpunit/includes/PrefixSearchTest.php new file mode 100644 index 00000000..d63541b7 --- /dev/null +++ b/tests/phpunit/includes/PrefixSearchTest.php @@ -0,0 +1,306 @@ +insertPage( 'Sandbox' ); + $this->insertPage( 'Bar' ); + $this->insertPage( 'Example' ); + $this->insertPage( 'Example Bar' ); + $this->insertPage( 'Example Foo' ); + $this->insertPage( 'Example Foo/Bar' ); + $this->insertPage( 'Example/Baz' ); + $this->insertPage( 'Redirect test', '#REDIRECT [[Redirect Test]]' ); + $this->insertPage( 'Redirect Test' ); + $this->insertPage( 'Redirect Test Worse Result' ); + $this->insertPage( 'Redirect test2', '#REDIRECT [[Redirect Test2]]' ); + $this->insertPage( 'Redirect TEST2', '#REDIRECT [[Redirect Test2]]' ); + $this->insertPage( 'Redirect Test2' ); + $this->insertPage( 'Redirect Test2 Worse Result' ); + + $this->insertPage( 'Talk:Sandbox' ); + $this->insertPage( 'Talk:Example' ); + + $this->insertPage( 'User:Example' ); + } + + protected function setUp() { + parent::setUp(); + + if ( !$this->isWikitextNS( NS_MAIN ) ) { + $this->markTestSkipped( 'Main namespace does not support wikitext.' ); + } + + // Avoid special pages from extensions interferring with the tests + $this->setMwGlobals( 'wgSpecialPages', array() ); + } + + protected function searchProvision( Array $results = null ) { + if ( $results === null ) { + $this->setMwGlobals( 'wgHooks', array() ); + } else { + $this->setMwGlobals( 'wgHooks', array( + 'PrefixSearchBackend' => array( + function ( $namespaces, $search, $limit, &$srchres ) use ( $results ) { + $srchres = $results; + return false; + } + ), + ) ); + } + } + + public static function provideSearch() { + return array( + array( array( + 'Empty string', + 'query' => '', + 'results' => array(), + ) ), + array( array( + 'Main namespace with title prefix', + 'query' => 'Ex', + 'results' => array( + 'Example', + 'Example/Baz', + 'Example Bar', + ), + // Third result when testing offset + 'offsetresult' => array( + 'Example Foo', + ), + ) ), + array( array( + 'Talk namespace prefix', + 'query' => 'Talk:', + 'results' => array( + 'Talk:Example', + 'Talk:Sandbox', + ), + ) ), + array( array( + 'User namespace prefix', + 'query' => 'User:', + 'results' => array( + 'User:Example', + ), + ) ), + array( array( + 'Special namespace prefix', + 'query' => 'Special:', + 'results' => array( + 'Special:ActiveUsers', + 'Special:AllMessages', + 'Special:AllMyFiles', + ), + // Third result when testing offset + 'offsetresult' => array( + 'Special:AllMyUploads', + ), + ) ), + array( array( + 'Special namespace with prefix', + 'query' => 'Special:Un', + 'results' => array( + 'Special:Unblock', + 'Special:UncategorizedCategories', + 'Special:UncategorizedFiles', + ), + // Third result when testing offset + 'offsetresult' => array( + 'Special:UncategorizedImages', + ), + ) ), + array( array( + 'Special page name', + 'query' => 'Special:EditWatchlist', + 'results' => array( + 'Special:EditWatchlist', + ), + ) ), + array( array( + 'Special page subpages', + 'query' => 'Special:EditWatchlist/', + 'results' => array( + 'Special:EditWatchlist/clear', + 'Special:EditWatchlist/raw', + ), + ) ), + array( array( + 'Special page subpages with prefix', + 'query' => 'Special:EditWatchlist/cl', + 'results' => array( + 'Special:EditWatchlist/clear', + ), + ) ), + ); + } + + /** + * @dataProvider provideSearch + * @covers PrefixSearch::search + * @covers PrefixSearch::searchBackend + */ + public function testSearch( Array $case ) { + $this->searchProvision( null ); + $searcher = new StringPrefixSearch; + $results = $searcher->search( $case['query'], 3 ); + $this->assertEquals( + $case['results'], + $results, + $case[0] + ); + } + + /** + * @dataProvider provideSearch + * @covers PrefixSearch::search + * @covers PrefixSearch::searchBackend + */ + public function testSearchWithOffset( Array $case ) { + $this->searchProvision( null ); + $searcher = new StringPrefixSearch; + $results = $searcher->search( $case['query'], 3, array(), 1 ); + + // We don't expect the first result when offsetting + array_shift( $case['results'] ); + // And sometimes we expect a different last result + $expected = isset( $case['offsetresult'] ) ? + array_merge( $case['results'], $case['offsetresult'] ) : + $case['results']; + + $this->assertEquals( + $expected, + $results, + $case[0] + ); + } + + public static function provideSearchBackend() { + return array( + array( array( + 'Simple case', + 'provision' => array( + 'Bar', + 'Barcelona', + 'Barbara', + ), + 'query' => 'Bar', + 'results' => array( + 'Bar', + 'Barcelona', + 'Barbara', + ), + ) ), + array( array( + 'Exact match not on top (bug 70958)', + 'provision' => array( + 'Barcelona', + 'Bar', + 'Barbara', + ), + 'query' => 'Bar', + 'results' => array( + 'Bar', + 'Barcelona', + 'Barbara', + ), + ) ), + array( array( + 'Exact match missing (bug 70958)', + 'provision' => array( + 'Barcelona', + 'Barbara', + 'Bart', + ), + 'query' => 'Bar', + 'results' => array( + 'Bar', + 'Barcelona', + 'Barbara', + ), + ) ), + array( array( + 'Exact match missing and not existing', + 'provision' => array( + 'Exile', + 'Exist', + 'External', + ), + 'query' => 'Ex', + 'results' => array( + 'Exile', + 'Exist', + 'External', + ), + ) ), + array( array( + "Exact match shouldn't override already found match if " . + "exact is redirect and found isn't", + 'provision' => array( + // Target of the exact match is low in the list + 'Redirect Test Worse Result', + 'Redirect Test', + ), + 'query' => 'redirect test', + 'results' => array( + // Redirect target is pulled up and exact match isn't added + 'Redirect Test', + 'Redirect Test Worse Result', + ), + ) ), + array( array( + "Exact match shouldn't override already found match if " . + "both exact match and found match are redirect", + 'provision' => array( + // Another redirect to the same target as the exact match + // is low in the list + 'Redirect Test2 Worse Result', + 'Redirect test2', + ), + 'query' => 'redirect TEST2', + 'results' => array( + // Found redirect is pulled to the top and exact match isn't + // added + 'Redirect test2', + 'Redirect Test2 Worse Result', + ), + ) ), + array( array( + "Exact match should override any already found matches that " . + "are redirects to it", + 'provision' => array( + // Another redirect to the same target as the exact match + // is low in the list + 'Redirect Test Worse Result', + 'Redirect test', + ), + 'query' => 'Redirect Test', + 'results' => array( + // Found redirect is pulled to the top and exact match isn't + // added + 'Redirect Test', + 'Redirect Test Worse Result', + ), + ) ), + ); + } + + /** + * @dataProvider provideSearchBackend + * @covers PrefixSearch::searchBackend + */ + public function testSearchBackend( Array $case ) { + $this->searchProvision( $case['provision'] ); + $searcher = new StringPrefixSearch; + $results = $searcher->search( $case['query'], 3 ); + $this->assertEquals( + $case['results'], + $results, + $case[0] + ); + } +} diff --git a/tests/phpunit/includes/RequestContextTest.php b/tests/phpunit/includes/RequestContextTest.php deleted file mode 100644 index cae0e52e..00000000 --- a/tests/phpunit/includes/RequestContextTest.php +++ /dev/null @@ -1,96 +0,0 @@ -setTitle( $curTitle ); - $this->assertTrue( $curTitle->equals( $context->getWikiPage()->getTitle() ), - "When a title is first set WikiPage should be created on-demand for that title." ); - - $curTitle = Title::newFromText( "B" ); - $context->setWikiPage( WikiPage::factory( $curTitle ) ); - $this->assertTrue( $curTitle->equals( $context->getTitle() ), - "Title must be updated when a new WikiPage is provided." ); - - $curTitle = Title::newFromText( "C" ); - $context->setTitle( $curTitle ); - $this->assertTrue( - $curTitle->equals( $context->getWikiPage()->getTitle() ), - "When a title is updated the WikiPage should be purged " - . "and recreated on-demand with the new title." - ); - } - - /** - * @covers RequestContext::importScopedSession - */ - public function testImportScopedSession() { - $context = RequestContext::getMain(); - - $oInfo = $context->exportSession(); - $this->assertEquals( '127.0.0.1', $oInfo['ip'], "Correct initial IP address." ); - $this->assertEquals( 0, $oInfo['userId'], "Correct initial user ID." ); - - $user = User::newFromName( 'UnitTestContextUser' ); - $user->addToDatabase(); - - $sinfo = array( - 'sessionId' => 'd612ee607c87e749ef14da4983a702cd', - 'userId' => $user->getId(), - 'ip' => '192.0.2.0', - 'headers' => array( - 'USER-AGENT' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:18.0) Gecko/20100101 Firefox/18.0' - ) - ); - // importScopedSession() sets these variables - $this->setMwGlobals( array( - 'wgUser' => new User, - 'wgRequest' => new FauxRequest, - ) ); - $sc = RequestContext::importScopedSession( $sinfo ); // load new context - - $info = $context->exportSession(); - $this->assertEquals( $sinfo['ip'], $info['ip'], "Correct IP address." ); - $this->assertEquals( $sinfo['headers'], $info['headers'], "Correct headers." ); - $this->assertEquals( $sinfo['sessionId'], $info['sessionId'], "Correct session ID." ); - $this->assertEquals( $sinfo['userId'], $info['userId'], "Correct user ID." ); - $this->assertEquals( - $sinfo['ip'], - $context->getRequest()->getIP(), - "Correct context IP address." - ); - $this->assertEquals( - $sinfo['headers'], - $context->getRequest()->getAllHeaders(), - "Correct context headers." - ); - $this->assertEquals( $sinfo['sessionId'], session_id(), "Correct context session ID." ); - $this->assertEquals( true, $context->getUser()->isLoggedIn(), "Correct context user." ); - $this->assertEquals( $sinfo['userId'], $context->getUser()->getId(), "Correct context user ID." ); - $this->assertEquals( - 'UnitTestContextUser', - $context->getUser()->getName(), - "Correct context user name." - ); - - unset( $sc ); // restore previous context - - $info = $context->exportSession(); - $this->assertEquals( $oInfo['ip'], $info['ip'], "Correct initial IP address." ); - $this->assertEquals( $oInfo['headers'], $info['headers'], "Correct initial headers." ); - $this->assertEquals( $oInfo['sessionId'], $info['sessionId'], "Correct initial session ID." ); - $this->assertEquals( $oInfo['userId'], $info['userId'], "Correct initial user ID." ); - } -} diff --git a/tests/phpunit/includes/SampleTest.php b/tests/phpunit/includes/SampleTest.php index 25858110..c5944d16 100644 --- a/tests/phpunit/includes/SampleTest.php +++ b/tests/phpunit/includes/SampleTest.php @@ -97,7 +97,7 @@ class TestSample extends MediaWikiLangTestCase { // @codingStandardsIgnoreStart Ignore long line warning /** - * @expectedException MWException object + * @expectedException InvalidArgumentException * See http://phpunit.de/manual/3.7/en/appendixes.annotations.html#appendixes.annotations.expectedException */ // @codingStandardsIgnoreEnd diff --git a/tests/phpunit/includes/SanitizerTest.php b/tests/phpunit/includes/SanitizerTest.php index 50c1e509..c615c460 100644 --- a/tests/phpunit/includes/SanitizerTest.php +++ b/tests/phpunit/includes/SanitizerTest.php @@ -6,12 +6,6 @@ */ class SanitizerTest extends MediaWikiTestCase { - protected function setUp() { - parent::setUp(); - - AutoLoader::loadClass( 'Sanitizer' ); - } - /** * @covers Sanitizer::decodeCharReferences */ @@ -85,7 +79,7 @@ class SanitizerTest extends MediaWikiTestCase { */ public function testInvalidNumberedEntities() { $this->assertEquals( - UTF8_REPLACEMENT, + UtfNormal\Constants::UTF8_REPLACEMENT, Sanitizer::decodeCharReferences( "�" ), 'Invalid numbered entity' ); @@ -346,4 +340,25 @@ class SanitizerTest extends MediaWikiTestCase { $message ); } + + /** + * @dataProvider provideEscapeHtmlAllowEntities + * @covers Sanitizer::escapeHtmlAllowEntities + */ + public function testEscapeHtmlAllowEntities( $expected, $html ) { + $this->assertEquals( + $expected, + Sanitizer::escapeHtmlAllowEntities( $html ) + ); + } + + public static function provideEscapeHtmlAllowEntities() { + return array( + array( 'foo', 'foo' ), + array( 'a¡b', 'a¡b' ), + array( 'foo'bar', "foo'bar" ), + array( '<script>foo</script>', '' ), + ); + } + } diff --git a/tests/phpunit/includes/SpecialPageTest.php b/tests/phpunit/includes/SpecialPageTest.php deleted file mode 100644 index 245cdffd..00000000 --- a/tests/phpunit/includes/SpecialPageTest.php +++ /dev/null @@ -1,105 +0,0 @@ - - */ -class SpecialPageTest extends MediaWikiTestCase { - - protected function setUp() { - parent::setUp(); - - $this->setMwGlobals( array( - 'wgScript' => '/index.php', - 'wgContLang' => Language::factory( 'en' ) - ) ); - } - - /** - * @dataProvider getTitleForProvider - */ - public function testGetTitleFor( $expectedName, $name ) { - $title = SpecialPage::getTitleFor( $name ); - $expected = Title::makeTitle( NS_SPECIAL, $expectedName ); - $this->assertEquals( $expected, $title ); - } - - public function getTitleForProvider() { - return array( - array( 'UserLogin', 'Userlogin' ) - ); - } - - /** - * @expectedException PHPUnit_Framework_Error_Notice - */ - public function testInvalidGetTitleFor() { - $title = SpecialPage::getTitleFor( 'cat' ); - $expected = Title::makeTitle( NS_SPECIAL, 'Cat' ); - $this->assertEquals( $expected, $title ); - } - - /** - * @expectedException PHPUnit_Framework_Error_Notice - * @dataProvider getTitleForWithWarningProvider - */ - public function testGetTitleForWithWarning( $expected, $name ) { - $title = SpecialPage::getTitleFor( $name ); - $this->assertEquals( $expected, $title ); - } - - public function getTitleForWithWarningProvider() { - return array( - array( Title::makeTitle( NS_SPECIAL, 'UserLogin' ), 'UserLogin' ) - ); - } - - /** - * @dataProvider requireLoginAnonProvider - */ - public function testRequireLoginAnon( $expected, $reason, $title ) { - $specialPage = new SpecialPage( 'Watchlist', 'viewmywatchlist' ); - - $user = User::newFromId( 0 ); - $specialPage->getContext()->setUser( $user ); - $specialPage->getContext()->setLanguage( Language::factory( 'en' ) ); - - $this->setExpectedException( 'UserNotLoggedIn', $expected ); - - // $specialPage->requireLogin( [ $reason [, $title ] ] ) - call_user_func_array( - array( $specialPage, 'requireLogin' ), - array_filter( array( $reason, $title ) ) - ); - } - - public function requireLoginAnonProvider() { - $lang = 'en'; - - $expected1 = wfMessage( 'exception-nologin-text' )->inLanguage( $lang )->text(); - $expected2 = wfMessage( 'about' )->inLanguage( $lang )->text(); - - return array( - array( $expected1, null, null ), - array( $expected2, 'about', null ), - array( $expected2, 'about', 'about' ), - ); - } - - public function testRequireLoginNotAnon() { - $specialPage = new SpecialPage( 'Watchlist', 'viewmywatchlist' ); - - $user = User::newFromName( "UTSysop" ); - $specialPage->getContext()->setUser( $user ); - - $specialPage->requireLogin(); - - // no exception thrown, logged in use can access special page - $this->assertTrue( true ); - } - -} diff --git a/tests/phpunit/includes/StatusTest.php b/tests/phpunit/includes/StatusTest.php index 628c59b6..c013f4fc 100644 --- a/tests/phpunit/includes/StatusTest.php +++ b/tests/phpunit/includes/StatusTest.php @@ -56,6 +56,17 @@ class StatusTest extends MediaWikiLangTestCase { $this->assertEquals( $message, $status->getMessage()->getKey() ); } + /** + * + */ + public function testOkAndErrors() { + $status = Status::newGood( 'foo' ); + $this->assertTrue( $status->ok ); + $status = Status::newFatal( 'foo', 1, 2 ); + $this->assertFalse( $status->ok ); + $this->assertArrayEquals( array( array( 'type' => 'error', 'message' => 'foo', 'params' => array( 1, 2 ) ) ), $status->errors ); + } + /** * @dataProvider provideSetResult * @covers Status::setResult @@ -109,7 +120,9 @@ class StatusTest extends MediaWikiLangTestCase { public function testIsGood( $ok, $errors, $expected ) { $status = new Status(); $status->ok = $ok; - $status->errors = $errors; + foreach ( $errors as $error ) { + $status->warning( $error ); + } $this->assertEquals( $expected, $status->isGood() ); } diff --git a/tests/phpunit/includes/TemplateParserTest.php b/tests/phpunit/includes/TemplateParserTest.php new file mode 100644 index 00000000..81854ff3 --- /dev/null +++ b/tests/phpunit/includes/TemplateParserTest.php @@ -0,0 +1,63 @@ +setMwGlobals( array( + 'wgSecretKey' => 'foo', + 'wgMemc' => new EmptyBagOStuff(), + ) ); + + $this->templateDir = dirname( __DIR__ ) . '/data/templates/'; + } + + /** + * @dataProvider provideProcessTemplate + * @covers TemplateParser::processTemplate + * @covers TemplateParser::getTemplate + * @covers TemplateParser::getTemplateFilename + */ + public function testProcessTemplate( $name, $args, $result, $exception = false ) { + if ( $exception ) { + $this->setExpectedException( $exception ); + } + $tp = new TemplateParser( $this->templateDir ); + $this->assertEquals( $result, $tp->processTemplate( $name, $args ) ); + } + + public static function provideProcessTemplate() { + return array( + array( + 'foobar', + array(), + "hello world!\n" + ), + array( + 'foobar_args', + array( + 'planet' => 'world', + ), + "hello world!\n", + ), + array( + '../foobar', + array(), + false, + 'UnexpectedValueException' + ), + array( + 'nonexistenttemplate', + array(), + false, + 'RuntimeException', + ) + ); + } +} diff --git a/tests/phpunit/includes/TestUser.php b/tests/phpunit/includes/TestUser.php index 610a6acd..754568d0 100644 --- a/tests/phpunit/includes/TestUser.php +++ b/tests/phpunit/includes/TestUser.php @@ -5,24 +5,41 @@ * like password if we log in via the API. */ class TestUser { + /** + * @deprecated Since 1.25. Use TestUser::getUser()->getName() + * @private + * @var string + */ public $username; + + /** + * @deprecated Since 1.25. Use TestUser::getPassword() + * @private + * @var string + */ public $password; - public $email; - public $groups; + + /** + * @deprecated Since 1.25. Use TestUser::getUser() + * @private + * @var User + */ public $user; + private function assertNotReal() { + global $wgDBprefix; + if ( $wgDBprefix !== MediaWikiTestCase::DB_PREFIX && $wgDBprefix !== MediaWikiTestCase::ORA_DB_PREFIX ) { + throw new MWException( "Can't create user on real database" ); + } + } + public function __construct( $username, $realname = 'Real Name', $email = 'sample@example.com', $groups = array() ) { - $this->username = $username; - $this->realname = $realname; - $this->email = $email; - $this->groups = $groups; + $this->assertNotReal(); - // don't allow user to hardcode or select passwords -- people sometimes run tests - // on live wikis. Sometimes we create sysop users in these tests. A sysop user with - // a known password would be a Bad Thing. - $this->password = User::randomPassword(); + $this->username = $username; + $this->password = 'TestUser'; $this->user = User::newFromName( $this->username ); $this->user->load(); @@ -31,32 +48,99 @@ class TestUser { // But for now, we just need to create or update the user with the desired properties. // we particularly need the new password, since we just generated it randomly. // In core MediaWiki, there is no functionality to delete users, so this is the best we can do. - if ( !$this->user->getID() ) { + if ( !$this->user->isLoggedIn() ) { // create the user $this->user = User::createNew( $this->username, array( - "email" => $this->email, - "real_name" => $this->realname + "email" => $email, + "real_name" => $realname ) ); + if ( !$this->user ) { - throw new Exception( "error creating user" ); + throw new MWException( "Error creating TestUser " . $username ); } } - // update the user to use the new random password and other details - $this->user->setPassword( $this->password ); - $this->user->setEmail( $this->email ); - $this->user->setRealName( $this->realname ); + // Update the user to use the password and other details + $change = $this->setPassword( $this->password ) || + $this->setEmail( $email ) || + $this->setRealName( $realname ); // Adjust groups by adding any missing ones and removing any extras $currentGroups = $this->user->getGroups(); - foreach ( array_diff( $this->groups, $currentGroups ) as $group ) { + foreach ( array_diff( $groups, $currentGroups ) as $group ) { $this->user->addGroup( $group ); } - foreach ( array_diff( $currentGroups, $this->groups ) as $group ) { + foreach ( array_diff( $currentGroups, $groups ) as $group ) { $this->user->removeGroup( $group ); } - $this->user->saveSettings(); + if ( $change ) { + $this->user->saveSettings(); + } + } + + /** + * @param string $realname + * @return bool + */ + private function setRealName( $realname ) { + if ( $this->user->getRealName() !== $realname ) { + $this->user->setRealName( $realname ); + return true; + } + + return false; + } + + /** + * @param string $email + * @return bool + */ + private function setEmail( $email ) { + if ( $this->user->getEmail() !== $email ) { + $this->user->setEmail( $email ); + return true; + } + + return false; + } + + /** + * @param string $password + * @return bool + */ + private function setPassword( $password ) { + $passwordFactory = $this->user->getPasswordFactory(); + $oldDefaultType = $passwordFactory->getDefaultType(); + + // A is unsalted MD5 (thus fast) ... we don't care about security here, this is test only + $passwordFactory->setDefaultType( 'A' ); + $newPassword = $passwordFactory->newFromPlaintext( $password, $this->user->getPassword() ); + + $change = false; + if ( !$this->user->getPassword()->equals( $newPassword ) ) { + // Password changed + $this->user->setPassword( $password ); + $change = true; + } + + $passwordFactory->setDefaultType( $oldDefaultType ); + + return $change; + } + + /** + * @return User + */ + public function getUser() { + return $this->user; + } + + /** + * @return string + */ + public function getPassword() { + return $this->password; } } diff --git a/tests/phpunit/includes/TestingAccessWrapper.php b/tests/phpunit/includes/TestingAccessWrapper.php new file mode 100644 index 00000000..84c0f9b5 --- /dev/null +++ b/tests/phpunit/includes/TestingAccessWrapper.php @@ -0,0 +1,50 @@ +getTitleFormatter(); + * + * TODO: + * - Provide access to static methods and properties. + * - Organize other helper classes in tests/testHelpers.inc into a directory. + */ +class TestingAccessWrapper { + public $object; + + /** + * Return the same object, without access restrictions. + */ + public static function newFromObject( $object ) { + $wrapper = new TestingAccessWrapper(); + $wrapper->object = $object; + return $wrapper; + } + + public function __call( $method, $args ) { + $classReflection = new ReflectionClass( $this->object ); + $methodReflection = $classReflection->getMethod( $method ); + $methodReflection->setAccessible( true ); + return $methodReflection->invokeArgs( $this->object, $args ); + } + + public function __set( $name, $value ) { + $classReflection = new ReflectionClass( $this->object ); + $propertyReflection = $classReflection->getProperty( $name ); + $propertyReflection->setAccessible( true ); + $propertyReflection->setValue( $this->object, $value ); + } + + public function __get( $name ) { + $classReflection = new ReflectionClass( $this->object ); + $propertyReflection = $classReflection->getProperty( $name ); + $propertyReflection->setAccessible( true ); + return $propertyReflection->getValue( $this->object ); + } +} diff --git a/tests/phpunit/includes/TestingAccessWrapperTest.php b/tests/phpunit/includes/TestingAccessWrapperTest.php new file mode 100644 index 00000000..7e5b91a1 --- /dev/null +++ b/tests/phpunit/includes/TestingAccessWrapperTest.php @@ -0,0 +1,34 @@ +raw = new WellProtectedClass(); + $this->wrapped = TestingAccessWrapper::newFromObject( $this->raw ); + } + + function testGetProperty() { + $this->assertSame( 1, $this->wrapped->property ); + } + + function testSetProperty() { + $this->wrapped->property = 10; + $this->assertSame( 10, $this->wrapped->property ); + $this->assertSame( 10, $this->raw->getProperty() ); + } + + function testCallMethod() { + $this->wrapped->incrementPropertyValue(); + $this->assertSame( 2, $this->wrapped->property ); + $this->assertSame( 2, $this->raw->getProperty() ); + } + + function testCallMethodTwoArgs() { + $this->assertSame( 'two', $this->wrapped->whatSecondArg( 'one', 'two' ) ); + } +} diff --git a/tests/phpunit/includes/TitleMethodsTest.php b/tests/phpunit/includes/TitleMethodsTest.php index 5904facd..e186438b 100644 --- a/tests/phpunit/includes/TitleMethodsTest.php +++ b/tests/phpunit/includes/TitleMethodsTest.php @@ -7,7 +7,7 @@ * @note We don't make assumptions about the main namespace. * But we do expect the Help namespace to contain Wikitext. */ -class TitleMethodsTest extends MediaWikiTestCase { +class TitleMethodsTest extends MediaWikiLangTestCase { protected function setUp() { global $wgContLang; @@ -297,4 +297,43 @@ class TitleMethodsTest extends MediaWikiTestCase { $title = Title::newFromText( $title ); $this->assertEquals( $expectedBool, $title->isWikitextPage() ); } + + public static function provideGetOtherPage() { + return array( + array( 'Main Page', 'Talk:Main Page' ), + array( 'Talk:Main Page', 'Main Page' ), + array( 'Help:Main Page', 'Help talk:Main Page' ), + array( 'Help talk:Main Page', 'Help:Main Page' ), + array( 'Special:FooBar', null ), + ); + } + + /** + * @dataProvider provideGetOtherpage + * @covers Title::getOtherPage + * + * @param string $text + * @param string|null $expected + */ + public function testGetOtherPage( $text, $expected ) { + if ( $expected === null ) { + $this->setExpectedException( 'MWException' ); + } + + $title = Title::newFromText( $text ); + $this->assertEquals( $expected, $title->getOtherPage()->getPrefixedText() ); + } + + public function testClearCaches() { + $linkCache = LinkCache::singleton(); + + $title1 = Title::newFromText( 'Foo' ); + $linkCache->addGoodLinkObj( 23, $title1 ); + + Title::clearCaches(); + + $title2 = Title::newFromText( 'Foo' ); + $this->assertNotSame( $title1, $title2, 'title cache should be empty' ); + $this->assertEquals( 0, $linkCache->getGoodLinkID( 'Foo' ), 'link cache should be empty' ); + } } diff --git a/tests/phpunit/includes/TitlePermissionTest.php b/tests/phpunit/includes/TitlePermissionTest.php index d2400b3f..022c7d53 100644 --- a/tests/phpunit/includes/TitlePermissionTest.php +++ b/tests/phpunit/includes/TitlePermissionTest.php @@ -326,6 +326,10 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->setUserPerm( null ); $this->assertEquals( $check[$action][0], $this->title->getUserPermissionsErrors( $action, $this->user, true ) ); + $this->assertEquals( $check[$action][0], + $this->title->getUserPermissionsErrors( $action, $this->user, 'full' ) ); + $this->assertEquals( $check[$action][0], + $this->title->getUserPermissionsErrors( $action, $this->user, 'secure' ) ); global $wgGroupPermissions; $old = $wgGroupPermissions; @@ -333,11 +337,19 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->assertEquals( $check[$action][1], $this->title->getUserPermissionsErrors( $action, $this->user, true ) ); + $this->assertEquals( $check[$action][1], + $this->title->getUserPermissionsErrors( $action, $this->user, 'full' ) ); + $this->assertEquals( $check[$action][1], + $this->title->getUserPermissionsErrors( $action, $this->user, 'secure' ) ); $wgGroupPermissions = $old; $this->setUserPerm( $action ); $this->assertEquals( $check[$action][2], $this->title->getUserPermissionsErrors( $action, $this->user, true ) ); + $this->assertEquals( $check[$action][2], + $this->title->getUserPermissionsErrors( $action, $this->user, 'full' ) ); + $this->assertEquals( $check[$action][2], + $this->title->getUserPermissionsErrors( $action, $this->user, 'secure' ) ); $this->setUserPerm( $action ); $this->assertEquals( $check[$action][3], @@ -403,7 +415,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->setTitle( NS_USER ); $this->setUserPerm( '' ); - $this->assertEquals( array( array( 'badaccess-group0' ), array( 'namespaceprotected', 'User', 'bogus' ) ), + $this->assertEquals( array( array( 'badaccess-group0' ), + array( 'namespaceprotected', 'User', 'bogus' ) ), $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); $this->setTitle( NS_MEDIAWIKI ); @@ -630,7 +643,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->assertEquals( false, $this->title->userCan( 'bogus', $this->user ) ); - $this->assertEquals( array( array( "cascadeprotected", 2, "* [[:Bogus]]\n* [[:UnBogus]]\n", 'bogus' ), + $this->assertEquals( array( + array( "cascadeprotected", 2, "* [[:Bogus]]\n* [[:UnBogus]]\n", 'bogus' ), array( "cascadeprotected", 2, "* [[:Bogus]]\n* [[:UnBogus]]\n", 'bogus' ), array( "cascadeprotected", 2, "* [[:Bogus]]\n* [[:UnBogus]]\n", 'bogus' ) ), $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); @@ -648,10 +662,10 @@ class TitlePermissionTest extends MediaWikiLangTestCase { public function testActionPermissions() { $this->setUserPerm( array( "createpage" ) ); $this->setTitle( NS_MAIN, "test page" ); - $this->title->mTitleProtection['pt_create_perm'] = ''; - $this->title->mTitleProtection['pt_user'] = $this->user->getID(); - $this->title->mTitleProtection['pt_expiry'] = wfGetDB( DB_SLAVE )->getInfinity(); - $this->title->mTitleProtection['pt_reason'] = 'test'; + $this->title->mTitleProtection['permission'] = ''; + $this->title->mTitleProtection['user'] = $this->user->getID(); + $this->title->mTitleProtection['expiry'] = wfGetDB( DB_SLAVE )->getInfinity(); + $this->title->mTitleProtection['reason'] = 'test'; $this->title->mCascadeRestriction = false; $this->assertEquals( array( array( 'titleprotected', 'Useruser', 'test' ) ), @@ -659,7 +673,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->assertEquals( false, $this->title->userCan( 'create', $this->user ) ); - $this->title->mTitleProtection['pt_create_perm'] = 'sysop'; + $this->title->mTitleProtection['permission'] = 'editprotected'; $this->setUserPerm( array( 'createpage', 'protect' ) ); $this->assertEquals( array( array( 'titleprotected', 'Useruser', 'test' ) ), $this->title->getUserPermissionsErrors( 'create', $this->user ) ); diff --git a/tests/phpunit/includes/TitleTest.php b/tests/phpunit/includes/TitleTest.php index fb58381f..d55f958b 100644 --- a/tests/phpunit/includes/TitleTest.php +++ b/tests/phpunit/includes/TitleTest.php @@ -14,6 +14,7 @@ class TitleTest extends MediaWikiTestCase { 'wgLang' => Language::factory( 'en' ), 'wgAllowUserJs' => false, 'wgDefaultLanguageVariant' => false, + 'wgMetaNamespace' => 'Project', ) ); } @@ -324,36 +325,19 @@ class TitleTest extends MediaWikiTestCase { $whitelistRegexp = array( $whitelistRegexp ); } + $this->setMwGlobals( array( + // So User::isEveryoneAllowed( 'read' ) === false + 'wgGroupPermissions' => array( '*' => array( 'read' => false ) ), + 'wgWhitelistRead' => array( 'some random non sense title' ), + 'wgWhitelistReadRegexp' => $whitelistRegexp, + ) ); + $title = Title::newFromDBkey( $source ); - global $wgGroupPermissions; - $oldPermissions = $wgGroupPermissions; - // Disallow all so we can ensure our regex works - $wgGroupPermissions = array(); - $wgGroupPermissions['*']['read'] = false; - - global $wgWhitelistRead; - $oldWhitelist = $wgWhitelistRead; - // Undo any LocalSettings explicite whitelists so they won't cause a - // failing test to succeed. Set it to some random non sense just - // to make sure we properly test Title::checkReadPermissions() - $wgWhitelistRead = array( 'some random non sense title' ); - - global $wgWhitelistReadRegexp; - $oldWhitelistRegexp = $wgWhitelistReadRegexp; - $wgWhitelistReadRegexp = $whitelistRegexp; - - // Just use $wgUser which in test is a user object for '127.0.0.1' - global $wgUser; - // Invalidate user rights cache to take in account $wgGroupPermissions - // change above. - $wgUser->clearInstanceCache(); - $errors = $title->userCan( $action, $wgUser ); - - // Restore globals - $wgGroupPermissions = $oldPermissions; - $wgWhitelistRead = $oldWhitelist; - $wgWhitelistReadRegexp = $oldWhitelistRegexp; + // New anonymous user with no rights + $user = new User; + $user->mRights = array(); + $errors = $title->userCan( $action, $user ); if ( is_bool( $expected ) ) { # Forge the assertion message depending on the assertion expectation @@ -428,14 +412,14 @@ class TitleTest extends MediaWikiTestCase { public function testGetPageViewLanguage( $expected, $titleText, $contLang, $lang, $variant, $msg = '' ) { - global $wgLanguageCode, $wgContLang, $wgLang, $wgDefaultLanguageVariant, $wgAllowUserJs; - // Setup environnement for this test - $wgLanguageCode = $contLang; - $wgContLang = Language::factory( $contLang ); - $wgLang = Language::factory( $lang ); - $wgDefaultLanguageVariant = $variant; - $wgAllowUserJs = true; + $this->setMwGlobals( array( + 'wgLanguageCode' => $contLang, + 'wgContLang' => Language::factory( $contLang ), + 'wgLang' => Language::factory( $lang ), + 'wgDefaultLanguageVariant' => $variant, + 'wgAllowUserJs' => true, + ) ); $title = Title::newFromText( $titleText ); $this->assertInstanceOf( 'Title', $title, diff --git a/tests/phpunit/includes/UserTest.php b/tests/phpunit/includes/UserTest.php index af95a721..b74a7ead 100644 --- a/tests/phpunit/includes/UserTest.php +++ b/tests/phpunit/includes/UserTest.php @@ -214,8 +214,10 @@ class UserTest extends MediaWikiTestCase { */ public function testEditCount() { $user = User::newFromName( 'UnitTestUser' ); - $user->loadDefaults(); - $user->addToDatabase(); + + if ( !$user->getId() ) { + $user->addToDatabase(); + } // let the user have a few (3) edits $page = WikiPage::factory( Title::newFromText( 'Help:UserTest_EditCount' ) ); @@ -248,14 +250,17 @@ class UserTest extends MediaWikiTestCase { */ public function testOptions() { $user = User::newFromName( 'UnitTestUser' ); - $user->addToDatabase(); - $user->setOption( 'someoption', 'test' ); + if ( !$user->getId() ) { + $user->addToDatabase(); + } + + $user->setOption( 'userjs-someoption', 'test' ); $user->setOption( 'cols', 200 ); $user->saveSettings(); $user = User::newFromName( 'UnitTestUser' ); - $this->assertEquals( 'test', $user->getOption( 'someoption' ) ); + $this->assertEquals( 'test', $user->getOption( 'userjs-someoption' ) ); $this->assertEquals( 200, $user->getOption( 'cols' ) ); } @@ -266,9 +271,9 @@ class UserTest extends MediaWikiTestCase { */ public function testAnonOptions() { global $wgDefaultUserOptions; - $this->user->setOption( 'someoption', 'test' ); + $this->user->setOption( 'userjs-someoption', 'test' ); $this->assertEquals( $wgDefaultUserOptions['cols'], $this->user->getOption( 'cols' ) ); - $this->assertEquals( 'test', $this->user->getOption( 'someoption' ) ); + $this->assertEquals( 'test', $this->user->getOption( 'userjs-someoption' ) ); } /** @@ -276,12 +281,10 @@ class UserTest extends MediaWikiTestCase { * @covers User::getPasswordExpired() */ public function testPasswordExpire() { - global $wgPasswordExpireGrace; - $wgTemp = $wgPasswordExpireGrace; - $wgPasswordExpireGrace = 3600 * 24 * 7; // 7 days + $this->setMwGlobals( 'wgPasswordExpireGrace', 3600 * 24 * 7 ); // 7 days $user = User::newFromName( 'UnitTestUser' ); - $user->loadDefaults(); + $user->loadDefaults( 'UnitTestUser' ); $this->assertEquals( false, $user->getPasswordExpired() ); $ts = time() - ( 3600 * 24 * 1 ); // 1 day ago @@ -291,8 +294,6 @@ class UserTest extends MediaWikiTestCase { $ts = time() - ( 3600 * 24 * 10 ); // 10 days ago $user->expirePassword( $ts ); $this->assertEquals( 'hard', $user->getPasswordExpired() ); - - $wgPasswordExpireGrace = $wgTemp; } /** @@ -343,8 +344,8 @@ class UserTest extends MediaWikiTestCase { public function testGetCanonicalName( $name, $expectedArray, $msg ) { foreach ( $expectedArray as $validate => $expected ) { $this->assertEquals( - User::getCanonicalName( $name, $validate === 'false' ? false : $validate ), $expected, + User::getCanonicalName( $name, $validate === 'false' ? false : $validate ), $msg . ' (' . $validate . ')' ); } @@ -352,8 +353,8 @@ class UserTest extends MediaWikiTestCase { public static function provideGetCanonicalName() { return array( - array( ' trailing space ', array( 'creatable' => 'Trailing space' ), 'Trailing spaces' ), - // @todo FIXME: Maybe the createable name should be 'Talk:Username' or false to reject? + array( ' Trailing space ', array( 'creatable' => 'Trailing space' ), 'Trailing spaces' ), + // @todo FIXME: Maybe the creatable name should be 'Talk:Username' or false to reject? array( 'Talk:Username', array( 'creatable' => 'Username', 'usable' => 'Username', 'valid' => 'Username', 'false' => 'Talk:Username' ), 'Namespace prefix' ), array( ' name with # hash', array( 'creatable' => false, 'usable' => false ), 'With hash' ), @@ -366,4 +367,62 @@ class UserTest extends MediaWikiTestCase { 'false' => 'With / slash' ), 'With slash' ), ); } + + /** + * @covers User::equals + */ + public function testEquals() { + $first = User::newFromName( 'EqualUser' ); + $second = User::newFromName( 'EqualUser' ); + + $this->assertTrue( $first->equals( $first ) ); + $this->assertTrue( $first->equals( $second ) ); + $this->assertTrue( $second->equals( $first ) ); + + $third = User::newFromName( '0' ); + $fourth = User::newFromName( '000' ); + + $this->assertFalse( $third->equals( $fourth ) ); + $this->assertFalse( $fourth->equals( $third ) ); + + // Test users loaded from db with id + $user = User::newFromName( 'EqualUnitTestUser' ); + if ( !$user->getId() ) { + $user->addToDatabase(); + } + + $id = $user->getId(); + + $fifth = User::newFromId( $id ); + $sixth = User::newFromName( 'EqualUnitTestUser' ); + $this->assertTrue( $fifth->equals( $sixth ) ); + } + + /** + * @covers User::getId + */ + public function testGetId() { + $user = User::newFromName( 'UTSysop' ); + $this->assertTrue( $user->getId() > 0 ); + + } + + /** + * @covers User::isLoggedIn + * @covers User::isAnon + */ + public function testLoggedIn() { + $user = User::newFromName( 'UTSysop' ); + $this->assertTrue( $user->isLoggedIn() ); + $this->assertFalse( $user->isAnon() ); + + // Non-existent users are perceived as anonymous + $user = User::newFromName( 'UTNonexistent' ); + $this->assertFalse( $user->isLoggedIn() ); + $this->assertTrue( $user->isAnon() ); + + $user = new User; + $this->assertFalse( $user->isLoggedIn() ); + $this->assertTrue( $user->isAnon() ); + } } diff --git a/tests/phpunit/includes/WikiPageTest.php b/tests/phpunit/includes/WikiPageTest.php deleted file mode 100644 index 7f7945b8..00000000 --- a/tests/phpunit/includes/WikiPageTest.php +++ /dev/null @@ -1,1301 +0,0 @@ -tablesUsed = array_merge( - $this->tablesUsed, - array( 'page', - 'revision', - 'text', - - 'recentchanges', - 'logging', - - 'page_props', - 'pagelinks', - 'categorylinks', - 'langlinks', - 'externallinks', - 'imagelinks', - 'templatelinks', - 'iwlinks' ) ); - } - - protected function setUp() { - parent::setUp(); - $this->pages_to_delete = array(); - - LinkCache::singleton()->clear(); # avoid cached redirect status, etc - } - - protected function tearDown() { - foreach ( $this->pages_to_delete as $p ) { - /* @var $p WikiPage */ - - try { - if ( $p->exists() ) { - $p->doDeleteArticle( "testing done." ); - } - } catch ( MWException $ex ) { - // fail silently - } - } - parent::tearDown(); - } - - /** - * @param Title $title - * @param string $model - * @return WikiPage - */ - protected function newPage( $title, $model = null ) { - if ( is_string( $title ) ) { - $ns = $this->getDefaultWikitextNS(); - $title = Title::newFromText( $title, $ns ); - } - - $p = new WikiPage( $title ); - - $this->pages_to_delete[] = $p; - - return $p; - } - - /** - * @param string|Title|WikiPage $page - * @param string $text - * @param int $model - * - * @return WikiPage - */ - protected function createPage( $page, $text, $model = null ) { - if ( is_string( $page ) || $page instanceof Title ) { - $page = $this->newPage( $page, $model ); - } - - $content = ContentHandler::makeContent( $text, $page->getTitle(), $model ); - $page->doEditContent( $content, "testing", EDIT_NEW ); - - return $page; - } - - /** - * @covers WikiPage::doEditContent - */ - public function testDoEditContent() { - $page = $this->newPage( "WikiPageTest_testDoEditContent" ); - $title = $page->getTitle(); - - $content = ContentHandler::makeContent( - "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam " - . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.", - $title, - CONTENT_MODEL_WIKITEXT - ); - - $page->doEditContent( $content, "[[testing]] 1" ); - - $this->assertTrue( $title->getArticleID() > 0, "Title object should have new page id" ); - $this->assertTrue( $page->getId() > 0, "WikiPage should have new page id" ); - $this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" ); - $this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" ); - - $id = $page->getId(); - - # ------------------------ - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); - $n = $res->numRows(); - $res->free(); - - $this->assertEquals( 1, $n, 'pagelinks should contain one link from the page' ); - - # ------------------------ - $page = new WikiPage( $title ); - - $retrieved = $page->getContent(); - $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' ); - - # ------------------------ - $content = ContentHandler::makeContent( - "At vero eos et accusam et justo duo [[dolores]] et ea rebum. " - . "Stet clita kasd [[gubergren]], no sea takimata sanctus est.", - $title, - CONTENT_MODEL_WIKITEXT - ); - - $page->doEditContent( $content, "testing 2" ); - - # ------------------------ - $page = new WikiPage( $title ); - - $retrieved = $page->getContent(); - $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' ); - - # ------------------------ - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); - $n = $res->numRows(); - $res->free(); - - $this->assertEquals( 2, $n, 'pagelinks should contain two links from the page' ); - } - - /** - * @covers WikiPage::doEdit - */ - public function testDoEdit() { - $this->hideDeprecated( "WikiPage::doEdit" ); - $this->hideDeprecated( "WikiPage::getText" ); - $this->hideDeprecated( "Revision::getText" ); - - //NOTE: assume help namespace will default to wikitext - $title = Title::newFromText( "Help:WikiPageTest_testDoEdit" ); - - $page = $this->newPage( $title ); - - $text = "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam " - . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat."; - - $page->doEdit( $text, "[[testing]] 1" ); - - $this->assertTrue( $title->getArticleID() > 0, "Title object should have new page id" ); - $this->assertTrue( $page->getId() > 0, "WikiPage should have new page id" ); - $this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" ); - $this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" ); - - $id = $page->getId(); - - # ------------------------ - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); - $n = $res->numRows(); - $res->free(); - - $this->assertEquals( 1, $n, 'pagelinks should contain one link from the page' ); - - # ------------------------ - $page = new WikiPage( $title ); - - $retrieved = $page->getText(); - $this->assertEquals( $text, $retrieved, 'retrieved text doesn\'t equal original' ); - - # ------------------------ - $text = "At vero eos et accusam et justo duo [[dolores]] et ea rebum. " - . "Stet clita kasd [[gubergren]], no sea takimata sanctus est."; - - $page->doEdit( $text, "testing 2" ); - - # ------------------------ - $page = new WikiPage( $title ); - - $retrieved = $page->getText(); - $this->assertEquals( $text, $retrieved, 'retrieved text doesn\'t equal original' ); - - # ------------------------ - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); - $n = $res->numRows(); - $res->free(); - - $this->assertEquals( 2, $n, 'pagelinks should contain two links from the page' ); - } - - /** - * @covers WikiPage::doQuickEdit - */ - public function testDoQuickEdit() { - global $wgUser; - - $this->hideDeprecated( "WikiPage::doQuickEdit" ); - - //NOTE: assume help namespace will default to wikitext - $page = $this->createPage( "Help:WikiPageTest_testDoQuickEdit", "original text" ); - - $text = "quick text"; - $page->doQuickEdit( $text, $wgUser, "testing q" ); - - # --------------------- - $page = new WikiPage( $page->getTitle() ); - $this->assertEquals( $text, $page->getText() ); - } - - /** - * @covers WikiPage::doQuickEditContent - */ - public function testDoQuickEditContent() { - global $wgUser; - - $page = $this->createPage( - "WikiPageTest_testDoQuickEditContent", - "original text", - CONTENT_MODEL_WIKITEXT - ); - - $content = ContentHandler::makeContent( - "quick text", - $page->getTitle(), - CONTENT_MODEL_WIKITEXT - ); - $page->doQuickEditContent( $content, $wgUser, "testing q" ); - - # --------------------- - $page = new WikiPage( $page->getTitle() ); - $this->assertTrue( $content->equals( $page->getContent() ) ); - } - - /** - * @covers WikiPage::doDeleteArticle - */ - public function testDoDeleteArticle() { - $page = $this->createPage( - "WikiPageTest_testDoDeleteArticle", - "[[original text]] foo", - CONTENT_MODEL_WIKITEXT - ); - $id = $page->getId(); - - $page->doDeleteArticle( "testing deletion" ); - - $this->assertFalse( - $page->getTitle()->getArticleID() > 0, - "Title object should now have page id 0" - ); - $this->assertFalse( $page->getId() > 0, "WikiPage should now have page id 0" ); - $this->assertFalse( - $page->exists(), - "WikiPage::exists should return false after page was deleted" - ); - $this->assertNull( - $page->getContent(), - "WikiPage::getContent should return null after page was deleted" - ); - $this->assertFalse( - $page->getText(), - "WikiPage::getText should return false after page was deleted" - ); - - $t = Title::newFromText( $page->getTitle()->getPrefixedText() ); - $this->assertFalse( - $t->exists(), - "Title::exists should return false after page was deleted" - ); - - # ------------------------ - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); - $n = $res->numRows(); - $res->free(); - - $this->assertEquals( 0, $n, 'pagelinks should contain no more links from the page' ); - } - - /** - * @covers WikiPage::doDeleteUpdates - */ - public function testDoDeleteUpdates() { - $page = $this->createPage( - "WikiPageTest_testDoDeleteArticle", - "[[original text]] foo", - CONTENT_MODEL_WIKITEXT - ); - $id = $page->getId(); - - $page->doDeleteUpdates( $id ); - - # ------------------------ - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); - $n = $res->numRows(); - $res->free(); - - $this->assertEquals( 0, $n, 'pagelinks should contain no more links from the page' ); - } - - /** - * @covers WikiPage::getRevision - */ - public function testGetRevision() { - $page = $this->newPage( "WikiPageTest_testGetRevision" ); - - $rev = $page->getRevision(); - $this->assertNull( $rev ); - - # ----------------- - $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); - - $rev = $page->getRevision(); - - $this->assertEquals( $page->getLatest(), $rev->getId() ); - $this->assertEquals( "some text", $rev->getContent()->getNativeData() ); - } - - /** - * @covers WikiPage::getContent - */ - public function testGetContent() { - $page = $this->newPage( "WikiPageTest_testGetContent" ); - - $content = $page->getContent(); - $this->assertNull( $content ); - - # ----------------- - $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); - - $content = $page->getContent(); - $this->assertEquals( "some text", $content->getNativeData() ); - } - - /** - * @covers WikiPage::getText - */ - public function testGetText() { - $this->hideDeprecated( "WikiPage::getText" ); - - $page = $this->newPage( "WikiPageTest_testGetText" ); - - $text = $page->getText(); - $this->assertFalse( $text ); - - # ----------------- - $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); - - $text = $page->getText(); - $this->assertEquals( "some text", $text ); - } - - /** - * @covers WikiPage::getRawText - */ - public function testGetRawText() { - $this->hideDeprecated( "WikiPage::getRawText" ); - - $page = $this->newPage( "WikiPageTest_testGetRawText" ); - - $text = $page->getRawText(); - $this->assertFalse( $text ); - - # ----------------- - $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); - - $text = $page->getRawText(); - $this->assertEquals( "some text", $text ); - } - - /** - * @covers WikiPage::getContentModel - */ - public function testGetContentModel() { - global $wgContentHandlerUseDB; - - if ( !$wgContentHandlerUseDB ) { - $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' ); - } - - $page = $this->createPage( - "WikiPageTest_testGetContentModel", - "some text", - CONTENT_MODEL_JAVASCRIPT - ); - - $page = new WikiPage( $page->getTitle() ); - $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $page->getContentModel() ); - } - - /** - * @covers WikiPage::getContentHandler - */ - public function testGetContentHandler() { - global $wgContentHandlerUseDB; - - if ( !$wgContentHandlerUseDB ) { - $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' ); - } - - $page = $this->createPage( - "WikiPageTest_testGetContentHandler", - "some text", - CONTENT_MODEL_JAVASCRIPT - ); - - $page = new WikiPage( $page->getTitle() ); - $this->assertEquals( 'JavaScriptContentHandler', get_class( $page->getContentHandler() ) ); - } - - /** - * @covers WikiPage::exists - */ - public function testExists() { - $page = $this->newPage( "WikiPageTest_testExists" ); - $this->assertFalse( $page->exists() ); - - # ----------------- - $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); - $this->assertTrue( $page->exists() ); - - $page = new WikiPage( $page->getTitle() ); - $this->assertTrue( $page->exists() ); - - # ----------------- - $page->doDeleteArticle( "done testing" ); - $this->assertFalse( $page->exists() ); - - $page = new WikiPage( $page->getTitle() ); - $this->assertFalse( $page->exists() ); - } - - public static function provideHasViewableContent() { - return array( - array( 'WikiPageTest_testHasViewableContent', false, true ), - array( 'Special:WikiPageTest_testHasViewableContent', false ), - array( 'MediaWiki:WikiPageTest_testHasViewableContent', false ), - array( 'Special:Userlogin', true ), - array( 'MediaWiki:help', true ), - ); - } - - /** - * @dataProvider provideHasViewableContent - * @covers WikiPage::hasViewableContent - */ - public function testHasViewableContent( $title, $viewable, $create = false ) { - $page = $this->newPage( $title ); - $this->assertEquals( $viewable, $page->hasViewableContent() ); - - if ( $create ) { - $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); - $this->assertTrue( $page->hasViewableContent() ); - - $page = new WikiPage( $page->getTitle() ); - $this->assertTrue( $page->hasViewableContent() ); - } - } - - public static function provideGetRedirectTarget() { - return array( - array( 'WikiPageTest_testGetRedirectTarget_1', CONTENT_MODEL_WIKITEXT, "hello world", null ), - array( - 'WikiPageTest_testGetRedirectTarget_2', - CONTENT_MODEL_WIKITEXT, - "#REDIRECT [[hello world]]", - "Hello world" - ), - ); - } - - /** - * @dataProvider provideGetRedirectTarget - * @covers WikiPage::getRedirectTarget - */ - public function testGetRedirectTarget( $title, $model, $text, $target ) { - $this->setMwGlobals( array( - 'wgCapitalLinks' => true, - ) ); - - $page = $this->createPage( $title, $text, $model ); - - # sanity check, because this test seems to fail for no reason for some people. - $c = $page->getContent(); - $this->assertEquals( 'WikitextContent', get_class( $c ) ); - - # now, test the actual redirect - $t = $page->getRedirectTarget(); - $this->assertEquals( $target, is_null( $t ) ? null : $t->getPrefixedText() ); - } - - /** - * @dataProvider provideGetRedirectTarget - * @covers WikiPage::isRedirect - */ - public function testIsRedirect( $title, $model, $text, $target ) { - $page = $this->createPage( $title, $text, $model ); - $this->assertEquals( !is_null( $target ), $page->isRedirect() ); - } - - public static function provideIsCountable() { - return array( - - // any - array( 'WikiPageTest_testIsCountable', - CONTENT_MODEL_WIKITEXT, - '', - 'any', - true - ), - array( 'WikiPageTest_testIsCountable', - CONTENT_MODEL_WIKITEXT, - 'Foo', - 'any', - true - ), - - // comma - array( 'WikiPageTest_testIsCountable', - CONTENT_MODEL_WIKITEXT, - 'Foo', - 'comma', - false - ), - array( 'WikiPageTest_testIsCountable', - CONTENT_MODEL_WIKITEXT, - 'Foo, bar', - 'comma', - true - ), - - // link - array( 'WikiPageTest_testIsCountable', - CONTENT_MODEL_WIKITEXT, - 'Foo', - 'link', - false - ), - array( 'WikiPageTest_testIsCountable', - CONTENT_MODEL_WIKITEXT, - 'Foo [[bar]]', - 'link', - true - ), - - // redirects - array( 'WikiPageTest_testIsCountable', - CONTENT_MODEL_WIKITEXT, - '#REDIRECT [[bar]]', - 'any', - false - ), - array( 'WikiPageTest_testIsCountable', - CONTENT_MODEL_WIKITEXT, - '#REDIRECT [[bar]]', - 'comma', - false - ), - array( 'WikiPageTest_testIsCountable', - CONTENT_MODEL_WIKITEXT, - '#REDIRECT [[bar]]', - 'link', - false - ), - - // not a content namespace - array( 'Talk:WikiPageTest_testIsCountable', - CONTENT_MODEL_WIKITEXT, - 'Foo', - 'any', - false - ), - array( 'Talk:WikiPageTest_testIsCountable', - CONTENT_MODEL_WIKITEXT, - 'Foo, bar', - 'comma', - false - ), - array( 'Talk:WikiPageTest_testIsCountable', - CONTENT_MODEL_WIKITEXT, - 'Foo [[bar]]', - 'link', - false - ), - - // not a content namespace, different model - array( 'MediaWiki:WikiPageTest_testIsCountable.js', - null, - 'Foo', - 'any', - false - ), - array( 'MediaWiki:WikiPageTest_testIsCountable.js', - null, - 'Foo, bar', - 'comma', - false - ), - array( 'MediaWiki:WikiPageTest_testIsCountable.js', - null, - 'Foo [[bar]]', - 'link', - false - ), - ); - } - - /** - * @dataProvider provideIsCountable - * @covers WikiPage::isCountable - */ - public function testIsCountable( $title, $model, $text, $mode, $expected ) { - global $wgContentHandlerUseDB; - - $this->setMwGlobals( 'wgArticleCountMethod', $mode ); - - $title = Title::newFromText( $title ); - - if ( !$wgContentHandlerUseDB - && $model - && ContentHandler::getDefaultModelFor( $title ) != $model - ) { - $this->markTestSkipped( "Can not use non-default content model $model for " - . $title->getPrefixedDBkey() . " with \$wgContentHandlerUseDB disabled." ); - } - - $page = $this->createPage( $title, $text, $model ); - - $editInfo = $page->prepareContentForEdit( $page->getContent() ); - - $v = $page->isCountable(); - $w = $page->isCountable( $editInfo ); - - $this->assertEquals( - $expected, - $v, - "isCountable( null ) returned unexpected value " . var_export( $v, true ) - . " instead of " . var_export( $expected, true ) - . " in mode `$mode` for text \"$text\"" - ); - - $this->assertEquals( - $expected, - $w, - "isCountable( \$editInfo ) returned unexpected value " . var_export( $v, true ) - . " instead of " . var_export( $expected, true ) - . " in mode `$mode` for text \"$text\"" - ); - } - - public static function provideGetParserOutput() { - return array( - array( CONTENT_MODEL_WIKITEXT, "hello ''world''\n", "

hello world

" ), - // @todo more...? - ); - } - - /** - * @dataProvider provideGetParserOutput - * @covers WikiPage::getParserOutput - */ - public function testGetParserOutput( $model, $text, $expectedHtml ) { - $page = $this->createPage( 'WikiPageTest_testGetParserOutput', $text, $model ); - - $opt = $page->makeParserOptions( 'canonical' ); - $po = $page->getParserOutput( $opt ); - $text = $po->getText(); - - $text = trim( preg_replace( '//sm', '', $text ) ); # strip injected comments - $text = preg_replace( '!\s*(

)!sm', '\1', $text ); # don't let tidy confuse us - - $this->assertEquals( $expectedHtml, $text ); - - return $po; - } - - /** - * @covers WikiPage::getParserOutput - */ - public function testGetParserOutput_nonexisting() { - static $count = 0; - $count++; - - $page = new WikiPage( new Title( "WikiPageTest_testGetParserOutput_nonexisting_$count" ) ); - - $opt = new ParserOptions(); - $po = $page->getParserOutput( $opt ); - - $this->assertFalse( $po, "getParserOutput() shall return false for non-existing pages." ); - } - - /** - * @covers WikiPage::getParserOutput - */ - public function testGetParserOutput_badrev() { - $page = $this->createPage( 'WikiPageTest_testGetParserOutput', "dummy", CONTENT_MODEL_WIKITEXT ); - - $opt = new ParserOptions(); - $po = $page->getParserOutput( $opt, $page->getLatest() + 1234 ); - - // @todo would be neat to also test deleted revision - - $this->assertFalse( $po, "getParserOutput() shall return false for non-existing revisions." ); - } - - public static $sections = - - "Intro - -== stuff == -hello world - -== test == -just a test - -== foo == -more stuff -"; - - public function dataReplaceSection() { - //NOTE: assume the Help namespace to contain wikitext - return array( - array( 'Help:WikiPageTest_testReplaceSection', - CONTENT_MODEL_WIKITEXT, - WikiPageTest::$sections, - "0", - "No more", - null, - trim( preg_replace( '/^Intro/sm', 'No more', WikiPageTest::$sections ) ) - ), - array( 'Help:WikiPageTest_testReplaceSection', - CONTENT_MODEL_WIKITEXT, - WikiPageTest::$sections, - "", - "No more", - null, - "No more" - ), - array( 'Help:WikiPageTest_testReplaceSection', - CONTENT_MODEL_WIKITEXT, - WikiPageTest::$sections, - "2", - "== TEST ==\nmore fun", - null, - trim( preg_replace( '/^== test ==.*== foo ==/sm', - "== TEST ==\nmore fun\n\n== foo ==", - WikiPageTest::$sections ) ) - ), - array( 'Help:WikiPageTest_testReplaceSection', - CONTENT_MODEL_WIKITEXT, - WikiPageTest::$sections, - "8", - "No more", - null, - trim( WikiPageTest::$sections ) - ), - array( 'Help:WikiPageTest_testReplaceSection', - CONTENT_MODEL_WIKITEXT, - WikiPageTest::$sections, - "new", - "No more", - "New", - trim( WikiPageTest::$sections ) . "\n\n== New ==\n\nNo more" - ), - ); - } - - /** - * @dataProvider dataReplaceSection - * @covers WikiPage::replaceSection - */ - public function testReplaceSection( $title, $model, $text, $section, $with, - $sectionTitle, $expected - ) { - $this->hideDeprecated( "WikiPage::replaceSection" ); - - $page = $this->createPage( $title, $text, $model ); - $text = $page->replaceSection( $section, $with, $sectionTitle ); - $text = trim( $text ); - - $this->assertEquals( $expected, $text ); - } - - /** - * @dataProvider dataReplaceSection - * @covers WikiPage::replaceSectionContent - */ - public function testReplaceSectionContent( $title, $model, $text, $section, - $with, $sectionTitle, $expected - ) { - $page = $this->createPage( $title, $text, $model ); - - $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() ); - $c = $page->replaceSectionContent( $section, $content, $sectionTitle ); - - $this->assertEquals( $expected, is_null( $c ) ? null : trim( $c->getNativeData() ) ); - } - - /** - * @dataProvider dataReplaceSection - * @covers WikiPage::replaceSectionAtRev - */ - public function testReplaceSectionAtRev( $title, $model, $text, $section, - $with, $sectionTitle, $expected - ) { - $page = $this->createPage( $title, $text, $model ); - $baseRevId = $page->getLatest(); - - $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() ); - $c = $page->replaceSectionAtRev( $section, $content, $sectionTitle, $baseRevId ); - - $this->assertEquals( $expected, is_null( $c ) ? null : trim( $c->getNativeData() ) ); - } - - /* @todo FIXME: fix this! - public function testGetUndoText() { - $this->checkHasDiff3(); - - $text = "one"; - $page = $this->createPage( "WikiPageTest_testGetUndoText", $text ); - $rev1 = $page->getRevision(); - - $text .= "\n\ntwo"; - $page->doEditContent( - ContentHandler::makeContent( $text, $page->getTitle() ), - "adding section two" - ); - $rev2 = $page->getRevision(); - - $text .= "\n\nthree"; - $page->doEditContent( - ContentHandler::makeContent( $text, $page->getTitle() ), - "adding section three" - ); - $rev3 = $page->getRevision(); - - $text .= "\n\nfour"; - $page->doEditContent( - ContentHandler::makeContent( $text, $page->getTitle() ), - "adding section four" - ); - $rev4 = $page->getRevision(); - - $text .= "\n\nfive"; - $page->doEditContent( - ContentHandler::makeContent( $text, $page->getTitle() ), - "adding section five" - ); - $rev5 = $page->getRevision(); - - $text .= "\n\nsix"; - $page->doEditContent( - ContentHandler::makeContent( $text, $page->getTitle() ), - "adding section six" - ); - $rev6 = $page->getRevision(); - - $undo6 = $page->getUndoText( $rev6 ); - if ( $undo6 === false ) $this->fail( "getUndoText failed for rev6" ); - $this->assertEquals( "one\n\ntwo\n\nthree\n\nfour\n\nfive", $undo6 ); - - $undo3 = $page->getUndoText( $rev4, $rev2 ); - if ( $undo3 === false ) $this->fail( "getUndoText failed for rev4..rev2" ); - $this->assertEquals( "one\n\ntwo\n\nfive", $undo3 ); - - $undo2 = $page->getUndoText( $rev2 ); - if ( $undo2 === false ) $this->fail( "getUndoText failed for rev2" ); - $this->assertEquals( "one\n\nfive", $undo2 ); - } - */ - - /** - * @todo FIXME: this is a better rollback test than the one below, but it - * keeps failing in jenkins for some reason. - */ - public function broken_testDoRollback() { - $admin = new User(); - $admin->setName( "Admin" ); - - $text = "one"; - $page = $this->newPage( "WikiPageTest_testDoRollback" ); - $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), - "section one", EDIT_NEW, false, $admin ); - - $user1 = new User(); - $user1->setName( "127.0.1.11" ); - $text .= "\n\ntwo"; - $page = new WikiPage( $page->getTitle() ); - $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), - "adding section two", 0, false, $user1 ); - - $user2 = new User(); - $user2->setName( "127.0.2.13" ); - $text .= "\n\nthree"; - $page = new WikiPage( $page->getTitle() ); - $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), - "adding section three", 0, false, $user2 ); - - # we are having issues with doRollback spuriously failing. Apparently - # the last revision somehow goes missing or not committed under some - # circumstances. So, make sure the last revision has the right user name. - $dbr = wfGetDB( DB_SLAVE ); - $this->assertEquals( 3, Revision::countByPageId( $dbr, $page->getId() ) ); - - $page = new WikiPage( $page->getTitle() ); - $rev3 = $page->getRevision(); - $this->assertEquals( '127.0.2.13', $rev3->getUserText() ); - - $rev2 = $rev3->getPrevious(); - $this->assertEquals( '127.0.1.11', $rev2->getUserText() ); - - $rev1 = $rev2->getPrevious(); - $this->assertEquals( 'Admin', $rev1->getUserText() ); - - # now, try the actual rollback - $admin->addGroup( "sysop" ); #XXX: make the test user a sysop... - $token = $admin->getEditToken( - array( $page->getTitle()->getPrefixedText(), $user2->getName() ), - null - ); - $errors = $page->doRollback( - $user2->getName(), - "testing revert", - $token, - false, - $details, - $admin - ); - - if ( $errors ) { - $this->fail( "Rollback failed:\n" . print_r( $errors, true ) - . ";\n" . print_r( $details, true ) ); - } - - $page = new WikiPage( $page->getTitle() ); - $this->assertEquals( $rev2->getSha1(), $page->getRevision()->getSha1(), - "rollback did not revert to the correct revision" ); - $this->assertEquals( "one\n\ntwo", $page->getContent()->getNativeData() ); - } - - /** - * @todo FIXME: the above rollback test is better, but it keeps failing in jenkins for some reason. - * @covers WikiPage::doRollback - */ - public function testDoRollback() { - $admin = new User(); - $admin->setName( "Admin" ); - - $text = "one"; - $page = $this->newPage( "WikiPageTest_testDoRollback" ); - $page->doEditContent( - ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ), - "section one", - EDIT_NEW, - false, - $admin - ); - $rev1 = $page->getRevision(); - - $user1 = new User(); - $user1->setName( "127.0.1.11" ); - $text .= "\n\ntwo"; - $page = new WikiPage( $page->getTitle() ); - $page->doEditContent( - ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ), - "adding section two", - 0, - false, - $user1 - ); - - # now, try the rollback - $admin->addGroup( "sysop" ); #XXX: make the test user a sysop... - $token = $admin->getEditToken( - array( $page->getTitle()->getPrefixedText(), $user1->getName() ), - null - ); - $errors = $page->doRollback( - $user1->getName(), - "testing revert", - $token, - false, - $details, - $admin - ); - - if ( $errors ) { - $this->fail( "Rollback failed:\n" . print_r( $errors, true ) - . ";\n" . print_r( $details, true ) ); - } - - $page = new WikiPage( $page->getTitle() ); - $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(), - "rollback did not revert to the correct revision" ); - $this->assertEquals( "one", $page->getContent()->getNativeData() ); - } - - /** - * @covers WikiPage::doRollback - */ - public function testDoRollbackFailureSameContent() { - $admin = new User(); - $admin->setName( "Admin" ); - $admin->addGroup( "sysop" ); #XXX: make the test user a sysop... - - $text = "one"; - $page = $this->newPage( "WikiPageTest_testDoRollback" ); - $page->doEditContent( - ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ), - "section one", - EDIT_NEW, - false, - $admin - ); - $rev1 = $page->getRevision(); - - $user1 = new User(); - $user1->setName( "127.0.1.11" ); - $user1->addGroup( "sysop" ); #XXX: make the test user a sysop... - $text .= "\n\ntwo"; - $page = new WikiPage( $page->getTitle() ); - $page->doEditContent( - ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ), - "adding section two", - 0, - false, - $user1 - ); - - # now, do a the rollback from the same user was doing the edit before - $resultDetails = array(); - $token = $user1->getEditToken( - array( $page->getTitle()->getPrefixedText(), $user1->getName() ), - null - ); - $errors = $page->doRollback( - $user1->getName(), - "testing revert same user", - $token, - false, - $resultDetails, - $admin - ); - - $this->assertEquals( array(), $errors, "Rollback failed same user" ); - - # now, try the rollback - $resultDetails = array(); - $token = $admin->getEditToken( - array( $page->getTitle()->getPrefixedText(), $user1->getName() ), - null - ); - $errors = $page->doRollback( - $user1->getName(), - "testing revert", - $token, - false, - $resultDetails, - $admin - ); - - $this->assertEquals( array( array( 'alreadyrolled', 'WikiPageTest testDoRollback', - '127.0.1.11', 'Admin' ) ), $errors, "Rollback not failed" ); - - $page = new WikiPage( $page->getTitle() ); - $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(), - "rollback did not revert to the correct revision" ); - $this->assertEquals( "one", $page->getContent()->getNativeData() ); - } - - public static function provideGetAutosummary() { - return array( - array( - 'Hello there, world!', - '#REDIRECT [[Foo]]', - 0, - '/^Redirected page .*Foo/' - ), - - array( - null, - 'Hello world!', - EDIT_NEW, - '/^Created page .*Hello/' - ), - - array( - 'Hello there, world!', - '', - 0, - '/^Blanked/' - ), - - array( - 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy - eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam - voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet - clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.', - 'Hello world!', - 0, - '/^Replaced .*Hello/' - ), - - array( - 'foo', - 'bar', - 0, - '/^$/' - ), - ); - } - - /** - * @dataProvider provideGetAutoSummary - * @covers WikiPage::getAutosummary - */ - public function testGetAutosummary( $old, $new, $flags, $expected ) { - $this->hideDeprecated( "WikiPage::getAutosummary" ); - - $page = $this->newPage( "WikiPageTest_testGetAutosummary" ); - - $summary = $page->getAutosummary( $old, $new, $flags ); - - $this->assertTrue( (bool)preg_match( $expected, $summary ), - "Autosummary didn't match expected pattern $expected: $summary" ); - } - - public static function provideGetAutoDeleteReason() { - return array( - array( - array(), - false, - false - ), - - array( - array( - array( "first edit", null ), - ), - "/first edit.*only contributor/", - false - ), - - array( - array( - array( "first edit", null ), - array( "second edit", null ), - ), - "/second edit.*only contributor/", - true - ), - - array( - array( - array( "first edit", "127.0.2.22" ), - array( "second edit", "127.0.3.33" ), - ), - "/second edit/", - true - ), - - array( - array( - array( - "first edit: " - . "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam " - . " nonumy eirmod tempor invidunt ut labore et dolore magna " - . "aliquyam erat, sed diam voluptua. At vero eos et accusam " - . "et justo duo dolores et ea rebum. Stet clita kasd gubergren, " - . "no sea takimata sanctus est Lorem ipsum dolor sit amet.'", - null - ), - ), - '/first edit:.*\.\.\."/', - false - ), - - array( - array( - array( "first edit", "127.0.2.22" ), - array( "", "127.0.3.33" ), - ), - "/before blanking.*first edit/", - true - ), - - ); - } - - /** - * @dataProvider provideGetAutoDeleteReason - * @covers WikiPage::getAutoDeleteReason - */ - public function testGetAutoDeleteReason( $edits, $expectedResult, $expectedHistory ) { - global $wgUser; - - //NOTE: assume Help namespace to contain wikitext - $page = $this->newPage( "Help:WikiPageTest_testGetAutoDeleteReason" ); - - $c = 1; - - foreach ( $edits as $edit ) { - $user = new User(); - - if ( !empty( $edit[1] ) ) { - $user->setName( $edit[1] ); - } else { - $user = $wgUser; - } - - $content = ContentHandler::makeContent( $edit[0], $page->getTitle(), $page->getContentModel() ); - - $page->doEditContent( $content, "test edit $c", $c < 2 ? EDIT_NEW : 0, false, $user ); - - $c += 1; - } - - $reason = $page->getAutoDeleteReason( $hasHistory ); - - if ( is_bool( $expectedResult ) || is_null( $expectedResult ) ) { - $this->assertEquals( $expectedResult, $reason ); - } else { - $this->assertTrue( (bool)preg_match( $expectedResult, $reason ), - "Autosummary didn't match expected pattern $expectedResult: $reason" ); - } - - $this->assertEquals( $expectedHistory, $hasHistory, - "expected \$hasHistory to be " . var_export( $expectedHistory, true ) ); - - $page->doDeleteArticle( "done" ); - } - - public static function providePreSaveTransform() { - return array( - array( 'hello this is ~~~', - "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]", - ), - array( 'hello \'\'this\'\' is ~~~', - 'hello \'\'this\'\' is ~~~', - ), - ); - } - - /** - * @dataProvider providePreSaveTransform - * @covers WikiPage::preSaveTransform - */ - public function testPreSaveTransform( $text, $expected ) { - $this->hideDeprecated( 'WikiPage::preSaveTransform' ); - $user = new User(); - $user->setName( "127.0.0.1" ); - - //NOTE: assume Help namespace to contain wikitext - $page = $this->newPage( "Help:WikiPageTest_testPreloadTransform" ); - $text = $page->preSaveTransform( $text, $user ); - - $this->assertEquals( $expected, $text ); - } - - /** - * @covers WikiPage::factory - */ - public function testWikiPageFactory() { - $title = Title::makeTitle( NS_FILE, 'Someimage.png' ); - $page = WikiPage::factory( $title ); - $this->assertEquals( 'WikiFilePage', get_class( $page ) ); - - $title = Title::makeTitle( NS_CATEGORY, 'SomeCategory' ); - $page = WikiPage::factory( $title ); - $this->assertEquals( 'WikiCategoryPage', get_class( $page ) ); - - $title = Title::makeTitle( NS_MAIN, 'SomePage' ); - $page = WikiPage::factory( $title ); - $this->assertEquals( 'WikiPage', get_class( $page ) ); - } -} diff --git a/tests/phpunit/includes/WikiPageTestContentHandlerUseDB.php b/tests/phpunit/includes/WikiPageTestContentHandlerUseDB.php deleted file mode 100644 index 3db76280..00000000 --- a/tests/phpunit/includes/WikiPageTestContentHandlerUseDB.php +++ /dev/null @@ -1,61 +0,0 @@ -setMwGlobals( 'wgContentHandlerUseDB', false ); - - $dbw = wfGetDB( DB_MASTER ); - - $page_table = $dbw->tableName( 'page' ); - $revision_table = $dbw->tableName( 'revision' ); - $archive_table = $dbw->tableName( 'archive' ); - - if ( $dbw->fieldExists( $page_table, 'page_content_model' ) ) { - $dbw->query( "alter table $page_table drop column page_content_model" ); - $dbw->query( "alter table $revision_table drop column rev_content_model" ); - $dbw->query( "alter table $revision_table drop column rev_content_format" ); - $dbw->query( "alter table $archive_table drop column ar_content_model" ); - $dbw->query( "alter table $archive_table drop column ar_content_format" ); - } - } - - /** - * @covers WikiPage::getContentModel - */ - public function testGetContentModel() { - $page = $this->createPage( - "WikiPageTest_testGetContentModel", - "some text", - CONTENT_MODEL_JAVASCRIPT - ); - - $page = new WikiPage( $page->getTitle() ); - - // NOTE: since the content model is not recorded in the database, - // we expect to get the default, namely CONTENT_MODEL_WIKITEXT - $this->assertEquals( CONTENT_MODEL_WIKITEXT, $page->getContentModel() ); - } - - /** - * @covers WikiPage::getContentHandler - */ - public function testGetContentHandler() { - $page = $this->createPage( - "WikiPageTest_testGetContentHandler", - "some text", - CONTENT_MODEL_JAVASCRIPT - ); - - // NOTE: since the content model is not recorded in the database, - // we expect to get the default, namely CONTENT_MODEL_WIKITEXT - $page = new WikiPage( $page->getTitle() ); - $this->assertEquals( 'WikitextContentHandler', get_class( $page->getContentHandler() ) ); - } -} diff --git a/tests/phpunit/includes/XmlSelectTest.php b/tests/phpunit/includes/XmlSelectTest.php index 9f154bb7..0e03add4 100644 --- a/tests/phpunit/includes/XmlSelectTest.php +++ b/tests/phpunit/includes/XmlSelectTest.php @@ -166,7 +166,7 @@ class XmlSelectTest extends MediaWikiTestCase { 'razor' ); - # inexistant keys should give us 'null' + # inexistent keys should give us 'null' $this->assertEquals( $this->select->getAttribute( 'I DO NOT EXIT' ), null diff --git a/tests/phpunit/includes/XmlTest.php b/tests/phpunit/includes/XmlTest.php index e6558819..382e3d89 100644 --- a/tests/phpunit/includes/XmlTest.php +++ b/tests/phpunit/includes/XmlTest.php @@ -81,7 +81,7 @@ class XmlTest extends MediaWikiTestCase { */ public function testElementInputCanHaveAValueOfZero() { $this->assertEquals( - '', + '', Xml::input( 'name', false, 0 ), 'Input with a value of 0 (bug 23797)' ); @@ -152,7 +152,7 @@ class XmlTest extends MediaWikiTestCase { $this->assertEquals( ' ' . - ' ' . + ' ' . ' ' . ' ' . + ' ' . ' ' . ' ' . + ' ' . ' ' . '', + '', Xml::textarea( 'name', '' ), 'textarea() with not content' ); @@ -244,7 +244,7 @@ class XmlTest extends MediaWikiTestCase { */ public function testTextareaAttribs() { $this->assertEquals( - '', + '', Xml::textarea( 'name', '', 20, 10 ), 'textarea() with custom attribs' ); diff --git a/tests/phpunit/includes/XmlTypeCheckTest.php b/tests/phpunit/includes/XmlTypeCheckTest.php deleted file mode 100644 index 6ad97fd4..00000000 --- a/tests/phpunit/includes/XmlTypeCheckTest.php +++ /dev/null @@ -1,49 +0,0 @@ -"; - const MAL_FORMED_XML = ""; - 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 ); - } - - /** - * @covers XMLTypeCheck::processingInstructionHandler - */ - public function testProcessingInstructionHandler() { - $called = false; - $testXML = new XmlTypeCheck( - self::XML_WITH_PIH, - null, - false, - array( - 'processing_instruction_handler' => function() use ( &$called ) { - $called = true; - } - ) - ); - $this->assertTrue( $called ); - } - -} diff --git a/tests/phpunit/includes/actions/ActionTest.php b/tests/phpunit/includes/actions/ActionTest.php index cc6fb11a..3babb97f 100644 --- a/tests/phpunit/includes/actions/ActionTest.php +++ b/tests/phpunit/includes/actions/ActionTest.php @@ -3,7 +3,6 @@ /** * @covers Action * - * @licence GNU GPL v2+ * @author Thiemo Mättig * * @group Action @@ -20,7 +19,7 @@ class ActionTest extends MediaWikiTestCase { 'disabled' => false, 'view' => true, 'edit' => true, - 'revisiondelete' => true, + 'revisiondelete' => 'SpecialPageAction', 'dummy' => true, 'string' => 'NamedDummyAction', 'declared' => 'NonExistingClassName', @@ -117,6 +116,15 @@ class ActionTest extends MediaWikiTestCase { $this->assertEquals( 'revisiondelete', $actionName ); } + public function testGetActionName_whenCanNotUseWikiPage_defaultsToView() { + $request = new FauxRequest( array( 'action' => 'edit' ) ); + $context = new DerivativeContext( RequestContext::getMain() ); + $context->setRequest( $request ); + $actionName = Action::getActionName( $context ); + + $this->assertEquals( 'view', $actionName ); + } + /** * @dataProvider actionProvider * @param string $requestedAction diff --git a/tests/phpunit/includes/api/ApiContinuationManagerTest.php b/tests/phpunit/includes/api/ApiContinuationManagerTest.php new file mode 100644 index 00000000..2edf0c6f --- /dev/null +++ b/tests/phpunit/includes/api/ApiContinuationManagerTest.php @@ -0,0 +1,195 @@ +setRequest( new FauxRequest( array( 'continue' => $continue ) ) ); + $main = new ApiMain( $context ); + return new ApiContinuationManager( $main, $allModules, $generatedModules ); + } + + public function testContinuation() { + $allModules = array( + new MockApiQueryBase( 'mock1' ), + new MockApiQueryBase( 'mock2' ), + new MockApiQueryBase( 'mocklist' ), + ); + $generator = new MockApiQueryBase( 'generator' ); + + $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( 'ApiMain', $manager->getSource() ); + $this->assertSame( false, $manager->isGeneratorDone() ); + $this->assertSame( $allModules, $manager->getRunModules() ); + $manager->addContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) ); + $manager->addContinueParam( $allModules[2], 'mlcontinue', 2 ); + $manager->addGeneratorContinueParam( $generator, 'gcontinue', 3 ); + $this->assertSame( array( array( + 'mlcontinue' => 2, + 'm1continue' => '1|2', + 'continue' => '||mock2', + ), false ), $manager->getContinuation() ); + $this->assertSame( array( + 'mock1' => array( 'm1continue' => '1|2' ), + 'mocklist' => array( 'mlcontinue' => 2 ), + 'generator' => array( 'gcontinue' => 3 ), + ), $manager->getRawContinuation() ); + + $result = new ApiResult( 0 ); + $manager->setContinuationIntoResult( $result ); + $this->assertSame( array( + 'mlcontinue' => 2, + 'm1continue' => '1|2', + 'continue' => '||mock2', + ), $result->getResultData( 'continue' ) ); + $this->assertSame( null, $result->getResultData( 'batchcomplete' ) ); + + $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( false, $manager->isGeneratorDone() ); + $this->assertSame( $allModules, $manager->getRunModules() ); + $manager->addContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) ); + $manager->addGeneratorContinueParam( $generator, 'gcontinue', array( 3, 4 ) ); + $this->assertSame( array( array( + 'm1continue' => '1|2', + 'continue' => '||mock2|mocklist', + ), false ), $manager->getContinuation() ); + $this->assertSame( array( + 'mock1' => array( 'm1continue' => '1|2' ), + 'generator' => array( 'gcontinue' => '3|4' ), + ), $manager->getRawContinuation() ); + + $manager = self::getManager( '', $allModules, array( '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( array( array( + 'mlcontinue' => 2, + 'gcontinue' => 3, + 'continue' => 'gcontinue||', + ), true ), $manager->getContinuation() ); + $this->assertSame( array( + 'mocklist' => array( 'mlcontinue' => 2 ), + 'generator' => array( 'gcontinue' => 3 ), + ), $manager->getRawContinuation() ); + + $result = new ApiResult( 0 ); + $manager->setContinuationIntoResult( $result ); + $this->assertSame( array( + 'mlcontinue' => 2, + 'gcontinue' => 3, + 'continue' => 'gcontinue||', + ), $result->getResultData( 'continue' ) ); + $this->assertSame( true, $result->getResultData( 'batchcomplete' ) ); + + $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( false, $manager->isGeneratorDone() ); + $this->assertSame( $allModules, $manager->getRunModules() ); + $manager->addGeneratorContinueParam( $generator, 'gcontinue', 3 ); + $this->assertSame( array( array( + 'gcontinue' => 3, + 'continue' => 'gcontinue||mocklist', + ), true ), $manager->getContinuation() ); + $this->assertSame( array( + 'generator' => array( 'gcontinue' => 3 ), + ), $manager->getRawContinuation() ); + + $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( false, $manager->isGeneratorDone() ); + $this->assertSame( $allModules, $manager->getRunModules() ); + $manager->addContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) ); + $manager->addContinueParam( $allModules[2], 'mlcontinue', 2 ); + $this->assertSame( array( array( + 'mlcontinue' => 2, + 'm1continue' => '1|2', + 'continue' => '||mock2', + ), false ), $manager->getContinuation() ); + $this->assertSame( array( + 'mock1' => array( 'm1continue' => '1|2' ), + 'mocklist' => array( 'mlcontinue' => 2 ), + ), $manager->getRawContinuation() ); + + $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( false, $manager->isGeneratorDone() ); + $this->assertSame( $allModules, $manager->getRunModules() ); + $manager->addContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) ); + $this->assertSame( array( array( + 'm1continue' => '1|2', + 'continue' => '||mock2|mocklist', + ), false ), $manager->getContinuation() ); + $this->assertSame( array( + 'mock1' => array( 'm1continue' => '1|2' ), + ), $manager->getRawContinuation() ); + + $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( false, $manager->isGeneratorDone() ); + $this->assertSame( $allModules, $manager->getRunModules() ); + $manager->addContinueParam( $allModules[2], 'mlcontinue', 2 ); + $this->assertSame( array( array( + 'mlcontinue' => 2, + 'continue' => '-||mock1|mock2', + ), true ), $manager->getContinuation() ); + $this->assertSame( array( + 'mocklist' => array( 'mlcontinue' => 2 ), + ), $manager->getRawContinuation() ); + + $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( false, $manager->isGeneratorDone() ); + $this->assertSame( $allModules, $manager->getRunModules() ); + $this->assertSame( array( array(), true ), $manager->getContinuation() ); + $this->assertSame( array(), $manager->getRawContinuation() ); + + $manager = self::getManager( '||mock2', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( false, $manager->isGeneratorDone() ); + $this->assertSame( + array_values( array_diff_key( $allModules, array( 1 => 1 ) ) ), + $manager->getRunModules() + ); + + $manager = self::getManager( '-||', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( true, $manager->isGeneratorDone() ); + $this->assertSame( + array_values( array_diff_key( $allModules, array( 0 => 0, 1 => 1 ) ) ), + $manager->getRunModules() + ); + + try { + self::getManager( 'foo', $allModules, array( 'mock1', 'mock2' ) ); + $this->fail( 'Expected exception not thrown' ); + } catch ( UsageException $ex ) { + $this->assertSame( + 'Invalid continue param. You should pass the original value returned by the previous query', + $ex->getMessage(), + 'Expected exception' + ); + } + + $manager = self::getManager( '||mock2', array_slice( $allModules, 0, 2 ), array( '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/ApiErrorFormatterTest.php b/tests/phpunit/includes/api/ApiErrorFormatterTest.php new file mode 100644 index 00000000..8ebdf60f --- /dev/null +++ b/tests/phpunit/includes/api/ApiErrorFormatterTest.php @@ -0,0 +1,351 @@ +addWarning( 'string', 'mainpage' ); + $formatter->addError( 'err', 'mainpage' ); + $this->assertSame( $expect1, $result->getResultData(), 'Simple test' ); + + $result->reset(); + $formatter->addWarning( 'foo', 'mainpage' ); + $formatter->addWarning( 'foo', 'mainpage' ); + $formatter->addWarning( 'foo', array( 'parentheses', 'foobar' ) ); + $msg1 = wfMessage( 'mainpage' ); + $formatter->addWarning( 'message', $msg1 ); + $msg2 = new ApiMessage( 'mainpage', 'overriddenCode', array( 'overriddenData' => true ) ); + $formatter->addWarning( 'messageWithData', $msg2 ); + $formatter->addError( 'errWithData', $msg2 ); + $this->assertSame( $expect2, $result->getResultData(), 'Complex test' ); + + $result->reset(); + $status = Status::newGood(); + $status->warning( 'mainpage' ); + $status->warning( 'parentheses', 'foobar' ); + $status->warning( $msg1 ); + $status->warning( $msg2 ); + $status->error( 'mainpage' ); + $status->error( 'parentheses', 'foobar' ); + $formatter->addMessagesFromStatus( 'status', $status ); + $this->assertSame( $expect3, $result->getResultData(), 'Status test' ); + + $this->assertSame( + $expect3['errors']['status'], + $formatter->arrayFromStatus( $status, 'error' ), + 'arrayFromStatus test for error' + ); + $this->assertSame( + $expect3['warnings']['status'], + $formatter->arrayFromStatus( $status, 'warning' ), + 'arrayFromStatus test for warning' + ); + } + + public static function provideErrorFormatter() { + $mainpagePlain = wfMessage( 'mainpage' )->useDatabase( false )->plain(); + $parensPlain = wfMessage( 'parentheses', 'foobar' )->useDatabase( false )->plain(); + $mainpageText = wfMessage( 'mainpage' )->inLanguage( 'de' )->text(); + $parensText = wfMessage( 'parentheses', 'foobar' )->inLanguage( 'de' )->text(); + $C = ApiResult::META_CONTENT; + $I = ApiResult::META_INDEXED_TAG_NAME; + + return array( + array( 'wikitext', 'de', true, + array( + 'errors' => array( + 'err' => array( + array( 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ), + $I => 'error', + ), + ), + 'warnings' => array( + 'string' => array( + array( 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ), + $I => 'warning', + ), + ), + ), + array( + 'errors' => array( + 'errWithData' => array( + array( 'code' => 'overriddenCode', 'text' => $mainpageText, + 'overriddenData' => true, $C => 'text' ), + $I => 'error', + ), + ), + 'warnings' => array( + 'messageWithData' => array( + array( 'code' => 'overriddenCode', 'text' => $mainpageText, + 'overriddenData' => true, $C => 'text' ), + $I => 'warning', + ), + 'message' => array( + array( 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ), + $I => 'warning', + ), + 'foo' => array( + array( 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ), + array( 'code' => 'parentheses', 'text' => $parensText, $C => 'text' ), + $I => 'warning', + ), + ), + ), + array( + 'errors' => array( + 'status' => array( + array( 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ), + array( 'code' => 'parentheses', 'text' => $parensText, $C => 'text' ), + $I => 'error', + ), + ), + 'warnings' => array( + 'status' => array( + array( 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ), + array( 'code' => 'parentheses', 'text' => $parensText, $C => 'text' ), + array( 'code' => 'overriddenCode', 'text' => $mainpageText, + 'overriddenData' => true, $C => 'text' ), + $I => 'warning', + ), + ), + ), + ), + array( 'raw', 'fr', true, + array( + 'errors' => array( + 'err' => array( + array( 'code' => 'mainpage', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ), + $I => 'error', + ), + ), + 'warnings' => array( + 'string' => array( + array( 'code' => 'mainpage', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ), + $I => 'warning', + ), + ), + ), + array( + 'errors' => array( + 'errWithData' => array( + array( 'code' => 'overriddenCode', 'message' => 'mainpage', 'params' => array( $I => 'param' ), + 'overriddenData' => true ), + $I => 'error', + ), + ), + 'warnings' => array( + 'messageWithData' => array( + array( 'code' => 'overriddenCode', 'message' => 'mainpage', 'params' => array( $I => 'param' ), + 'overriddenData' => true ), + $I => 'warning', + ), + 'message' => array( + array( 'code' => 'mainpage', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ), + $I => 'warning', + ), + 'foo' => array( + array( 'code' => 'mainpage', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ), + array( 'code' => 'parentheses', 'message' => 'parentheses', 'params' => array( 'foobar', $I => 'param' ) ), + $I => 'warning', + ), + ), + ), + array( + 'errors' => array( + 'status' => array( + array( 'code' => 'mainpage', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ), + array( 'code' => 'parentheses', 'message' => 'parentheses', 'params' => array( 'foobar', $I => 'param' ) ), + $I => 'error', + ), + ), + 'warnings' => array( + 'status' => array( + array( 'code' => 'mainpage', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ), + array( 'code' => 'parentheses', 'message' => 'parentheses', 'params' => array( 'foobar', $I => 'param' ) ), + array( 'code' => 'overriddenCode', 'message' => 'mainpage', 'params' => array( $I => 'param' ), + 'overriddenData' => true ), + $I => 'warning', + ), + ), + ), + ), + array( 'none', 'fr', true, + array( + 'errors' => array( + 'err' => array( + array( 'code' => 'mainpage' ), + $I => 'error', + ), + ), + 'warnings' => array( + 'string' => array( + array( 'code' => 'mainpage' ), + $I => 'warning', + ), + ), + ), + array( + 'errors' => array( + 'errWithData' => array( + array( 'code' => 'overriddenCode', 'overriddenData' => true ), + $I => 'error', + ), + ), + 'warnings' => array( + 'messageWithData' => array( + array( 'code' => 'overriddenCode', 'overriddenData' => true ), + $I => 'warning', + ), + 'message' => array( + array( 'code' => 'mainpage' ), + $I => 'warning', + ), + 'foo' => array( + array( 'code' => 'mainpage' ), + array( 'code' => 'parentheses' ), + $I => 'warning', + ), + ), + ), + array( + 'errors' => array( + 'status' => array( + array( 'code' => 'mainpage' ), + array( 'code' => 'parentheses' ), + $I => 'error', + ), + ), + 'warnings' => array( + 'status' => array( + array( 'code' => 'mainpage' ), + array( 'code' => 'parentheses' ), + array( 'code' => 'overriddenCode', 'overriddenData' => true ), + $I => 'warning', + ), + ), + ), + ), + ); + } + + /** + * @covers ApiErrorFormatter_BackCompat + */ + public function testErrorFormatterBC() { + $mainpagePlain = wfMessage( 'mainpage' )->useDatabase( false )->plain(); + $parensPlain = wfMessage( 'parentheses', 'foobar' )->useDatabase( false )->plain(); + + $result = new ApiResult( 8388608 ); + $formatter = new ApiErrorFormatter_BackCompat( $result ); + + $formatter->addWarning( 'string', 'mainpage' ); + $formatter->addError( 'err', 'mainpage' ); + $this->assertSame( array( + 'error' => array( + 'code' => 'mainpage', + 'info' => $mainpagePlain, + ), + 'warnings' => array( + 'string' => array( + 'warnings' => $mainpagePlain, + ApiResult::META_CONTENT => 'warnings', + ), + ), + ApiResult::META_TYPE => 'assoc', + ), $result->getResultData(), 'Simple test' ); + + $result->reset(); + $formatter->addWarning( 'foo', 'mainpage' ); + $formatter->addWarning( 'foo', 'mainpage' ); + $formatter->addWarning( 'foo', array( 'parentheses', 'foobar' ) ); + $msg1 = wfMessage( 'mainpage' ); + $formatter->addWarning( 'message', $msg1 ); + $msg2 = new ApiMessage( 'mainpage', 'overriddenCode', array( 'overriddenData' => true ) ); + $formatter->addWarning( 'messageWithData', $msg2 ); + $formatter->addError( 'errWithData', $msg2 ); + $this->assertSame( array( + 'error' => array( + 'code' => 'overriddenCode', + 'info' => $mainpagePlain, + 'overriddenData' => true, + ), + 'warnings' => array( + 'messageWithData' => array( + 'warnings' => $mainpagePlain, + ApiResult::META_CONTENT => 'warnings', + ), + 'message' => array( + 'warnings' => $mainpagePlain, + ApiResult::META_CONTENT => 'warnings', + ), + 'foo' => array( + 'warnings' => "$mainpagePlain\n$parensPlain", + ApiResult::META_CONTENT => 'warnings', + ), + ), + ApiResult::META_TYPE => 'assoc', + ), $result->getResultData(), 'Complex test' ); + + $result->reset(); + $status = Status::newGood(); + $status->warning( 'mainpage' ); + $status->warning( 'parentheses', 'foobar' ); + $status->warning( $msg1 ); + $status->warning( $msg2 ); + $status->error( 'mainpage' ); + $status->error( 'parentheses', 'foobar' ); + $formatter->addMessagesFromStatus( 'status', $status ); + $this->assertSame( array( + 'error' => array( + 'code' => 'parentheses', + 'info' => $parensPlain, + ), + 'warnings' => array( + 'status' => array( + 'warnings' => "$mainpagePlain\n$parensPlain", + ApiResult::META_CONTENT => 'warnings', + ), + ), + ApiResult::META_TYPE => 'assoc', + ), $result->getResultData(), 'Status test' ); + + $I = ApiResult::META_INDEXED_TAG_NAME; + $this->assertSame( + array( + array( 'type' => 'error', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ), + array( 'type' => 'error', 'message' => 'parentheses', 'params' => array( 'foobar', $I => 'param' ) ), + $I => 'error', + ), + $formatter->arrayFromStatus( $status, 'error' ), + 'arrayFromStatus test for error' + ); + $this->assertSame( + array( + array( 'type' => 'warning', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ), + array( 'type' => 'warning', 'message' => 'parentheses', 'params' => array( 'foobar', $I => 'param' ) ), + array( 'message' => 'mainpage', 'params' => array( $I => 'param' ), 'type' => 'warning' ), + array( 'message' => 'mainpage', 'params' => array( $I => 'param' ), 'type' => 'warning' ), + $I => 'warning', + ), + $formatter->arrayFromStatus( $status, 'warning' ), + 'arrayFromStatus test for warning' + ); + } + +} diff --git a/tests/phpunit/includes/api/ApiLoginTest.php b/tests/phpunit/includes/api/ApiLoginTest.php index 67a75f36..88a99e9b 100644 --- a/tests/phpunit/includes/api/ApiLoginTest.php +++ b/tests/phpunit/includes/api/ApiLoginTest.php @@ -123,7 +123,8 @@ class ApiLoginTest extends ApiTestCase { "lgname" => $user->username, "lgpassword" => $user->password ) - ) + ), + __METHOD__ ); $req->execute(); diff --git a/tests/phpunit/includes/api/ApiMainTest.php b/tests/phpunit/includes/api/ApiMainTest.php index 780cf9ed..e8ef1804 100644 --- a/tests/phpunit/includes/api/ApiMainTest.php +++ b/tests/phpunit/includes/api/ApiMainTest.php @@ -2,7 +2,6 @@ /** * @group API - * @group Database * @group medium * * @covers ApiMain @@ -10,41 +9,25 @@ class ApiMainTest extends ApiTestCase { /** - * Test that the API will accept a FauxRequest and execute. The help action - * (default) throws a UsageException. Just validate we're getting proper XML - * - * @expectedException UsageException + * Test that the API will accept a FauxRequest and execute. */ public function testApi() { $api = new ApiMain( - new FauxRequest( array( 'action' => 'help', 'format' => 'xml' ) ) + new FauxRequest( array( 'action' => 'query', 'meta' => 'siteinfo' ) ) ); $api->execute(); - $api->getPrinter()->setBufferResult( true ); - $api->printResult( false ); - $resp = $api->getPrinter()->getBuffer(); - - libxml_use_internal_errors( true ); - $sxe = simplexml_load_string( $resp ); - $this->assertNotInternalType( "bool", $sxe ); - $this->assertThat( $sxe, $this->isInstanceOf( "SimpleXMLElement" ) ); + $data = $api->getResult()->getResultData(); + $this->assertInternalType( 'array', $data ); + $this->assertArrayHasKey( 'query', $data ); } public static function provideAssert() { - $anon = new User(); - $bot = new User(); - $bot->setName( 'Bot' ); - $bot->addToDatabase(); - $bot->addGroup( 'bot' ); - $user = new User(); - $user->setName( 'User' ); - $user->addToDatabase(); return array( - array( $anon, 'user', 'assertuserfailed' ), - array( $user, 'user', false ), - array( $user, 'bot', 'assertbotfailed' ), - array( $bot, 'user', false ), - array( $bot, 'bot', false ), + array( false, array(), 'user', 'assertuserfailed' ), + array( true, array(), 'user', false ), + array( true, array(), 'bot', 'assertbotfailed' ), + array( true, array( 'bot' ), 'user', false ), + array( true, array( 'bot' ), 'bot', false ), ); } @@ -53,11 +36,17 @@ class ApiMainTest extends ApiTestCase { * * @covers ApiMain::checkAsserts * @dataProvider provideAssert - * @param User $user + * @param bool $registered + * @param array $rights * @param string $assert * @param string|bool $error False if no error expected */ - public function testAssert( $user, $assert, $error ) { + public function testAssert( $registered, $rights, $assert, $error ) { + $user = new User(); + if ( $registered ) { + $user->setId( 1 ); + } + $user->mRights = $rights; try { $this->doApiRequest( array( 'action' => 'query', @@ -69,4 +58,25 @@ class ApiMainTest extends ApiTestCase { } } + /** + * Test if all classes in the main module manager exists + */ + public function testClassNamesInModuleManager() { + global $wgAutoloadLocalClasses, $wgAutoloadClasses; + + // wgAutoloadLocalClasses has precedence, just like in includes/AutoLoader.php + $classes = $wgAutoloadLocalClasses + $wgAutoloadClasses; + + $api = new ApiMain( + new FauxRequest( array( 'action' => 'query', 'meta' => 'siteinfo' ) ) + ); + $modules = $api->getModuleManager()->getNamesWithClasses(); + foreach( $modules as $name => $class ) { + $this->assertArrayHasKey( + $class, + $classes, + 'Class ' . $class . ' for api module ' . $name . ' not in autoloader (with exact case)' + ); + } + } } diff --git a/tests/phpunit/includes/api/ApiMessageTest.php b/tests/phpunit/includes/api/ApiMessageTest.php new file mode 100644 index 00000000..6c3ce60d --- /dev/null +++ b/tests/phpunit/includes/api/ApiMessageTest.php @@ -0,0 +1,103 @@ +assertSame( $msg->getKey(), $msg2->getKey(), 'getKey' ); + $this->assertSame( $msg->getKeysToTry(), $msg2->getKeysToTry(), 'getKeysToTry' ); + $this->assertSame( $msg->getParams(), $msg2->getParams(), 'getParams' ); + $this->assertSame( $msg->getFormat(), $msg2->getFormat(), 'getFormat' ); + $this->assertSame( $msg->getLanguage(), $msg2->getLanguage(), 'getLanguage' ); + + $msg = TestingAccessWrapper::newFromObject( $msg ); + $msg2 = TestingAccessWrapper::newFromObject( $msg2 ); + foreach ( array( 'interface', 'useDatabase', 'title' ) as $key ) { + $this->assertSame( $msg->$key, $msg2->$key, $key ); + } + } + + /** + * @covers ApiMessage + */ + public function testApiMessage() { + $msg = new Message( array( 'foo', 'bar' ), array( 'baz' ) ); + $msg->inLanguage( 'de' )->title( Title::newMainPage() ); + $msg2 = new ApiMessage( $msg, 'code', array( 'data' ) ); + $this->compareMessages( $msg, $msg2 ); + $this->assertEquals( 'code', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + + $msg = new Message( array( 'foo', 'bar' ), array( 'baz' ) ); + $msg2 = new ApiMessage( array( array( 'foo', 'bar' ), 'baz' ), 'code', array( 'data' ) ); + $this->compareMessages( $msg, $msg2 ); + $this->assertEquals( 'code', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + + $msg = new Message( 'foo' ); + $msg2 = new ApiMessage( 'foo' ); + $this->compareMessages( $msg, $msg2 ); + $this->assertEquals( 'foo', $msg2->getApiCode() ); + $this->assertEquals( array(), $msg2->getApiData() ); + + $msg2->setApiCode( 'code', array( 'data' ) ); + $this->assertEquals( 'code', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + $msg2->setApiCode( null ); + $this->assertEquals( 'foo', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + $msg2->setApiData( array( 'data2' ) ); + $this->assertEquals( array( 'data2' ), $msg2->getApiData() ); + } + + /** + * @covers ApiRawMessage + */ + public function testApiRawMessage() { + $msg = new RawMessage( 'foo', array( 'baz' ) ); + $msg->inLanguage( 'de' )->title( Title::newMainPage() ); + $msg2 = new ApiRawMessage( $msg, 'code', array( 'data' ) ); + $this->compareMessages( $msg, $msg2 ); + $this->assertEquals( 'code', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + + $msg = new RawMessage( 'foo', array( 'baz' ) ); + $msg2 = new ApiRawMessage( array( 'foo', 'baz' ), 'code', array( 'data' ) ); + $this->compareMessages( $msg, $msg2 ); + $this->assertEquals( 'code', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + + $msg = new RawMessage( 'foo' ); + $msg2 = new ApiRawMessage( 'foo', 'code', array( 'data' ) ); + $this->compareMessages( $msg, $msg2 ); + $this->assertEquals( 'code', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + + $msg2->setApiCode( 'code', array( 'data' ) ); + $this->assertEquals( 'code', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + $msg2->setApiCode( null ); + $this->assertEquals( 'foo', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + $msg2->setApiData( array( 'data2' ) ); + $this->assertEquals( array( 'data2' ), $msg2->getApiData() ); + } + + /** + * @covers ApiMessage::create + */ + public function testApiMessageCreate() { + $this->assertInstanceOf( 'ApiMessage', ApiMessage::create( new Message( 'mainpage' ) ) ); + $this->assertInstanceOf( 'ApiRawMessage', ApiMessage::create( new RawMessage( 'mainpage' ) ) ); + $this->assertInstanceOf( 'ApiMessage', ApiMessage::create( 'mainpage' ) ); + + $msg = new ApiMessage( 'mainpage' ); + $this->assertSame( $msg, ApiMessage::create( $msg ) ); + + $msg = new ApiRawMessage( 'mainpage' ); + $this->assertSame( $msg, ApiMessage::create( $msg ) ); + } + +} diff --git a/tests/phpunit/includes/api/ApiOptionsTest.php b/tests/phpunit/includes/api/ApiOptionsTest.php index 5f955bbc..51154ae3 100644 --- a/tests/phpunit/includes/api/ApiOptionsTest.php +++ b/tests/phpunit/includes/api/ApiOptionsTest.php @@ -17,8 +17,6 @@ class ApiOptionsTest extends MediaWikiLangTestCase { /** @var DerivativeContext */ private $mContext; - private $mOldGetPreferencesHooks; - private static $Success = array( 'options' => 'success' ); protected function setUp() { @@ -50,21 +48,11 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mTested = new ApiOptions( $main, 'options' ); - global $wgHooks; - if ( !isset( $wgHooks['GetPreferences'] ) ) { - $wgHooks['GetPreferences'] = array(); - } - $this->mOldGetPreferencesHooks = $wgHooks['GetPreferences']; - $wgHooks['GetPreferences'][] = array( $this, 'hookGetPreferences' ); - } - - protected function tearDown() { - global $wgHooks; - - $wgHooks['GetPreferences'] = $this->mOldGetPreferencesHooks; - $this->mOldGetPreferencesHooks = false; - - parent::tearDown(); + $this->mergeMwGlobalArrayValue( 'wgHooks', array( + 'GetPreferences' => array( + array( $this, 'hookGetPreferences' ) + ) + ) ); } public function hookGetPreferences( $user, &$preferences ) { @@ -150,7 +138,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mContext->setRequest( new FauxRequest( $request, true, $this->mSession ) ); $this->mTested->execute(); - return $this->mTested->getResult()->getData(); + return $this->mTested->getResult()->getResultData( null, array( 'Strip' => 'all' ) ); } /** @@ -408,7 +396,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase { 'options' => 'success', 'warnings' => array( 'options' => array( - '*' => "Validation error for 'special': cannot be set by this module" + 'warnings' => "Validation error for 'special': cannot be set by this module" ) ) ), $response ); @@ -431,7 +419,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase { 'options' => 'success', 'warnings' => array( 'options' => array( - '*' => "Validation error for 'unknownOption': not a valid preference" + 'warnings' => "Validation error for 'unknownOption': not a valid preference" ) ) ), $response ); diff --git a/tests/phpunit/includes/api/ApiResultTest.php b/tests/phpunit/includes/api/ApiResultTest.php new file mode 100644 index 00000000..f0d84552 --- /dev/null +++ b/tests/phpunit/includes/api/ApiResultTest.php @@ -0,0 +1,1563 @@ +assertSame( array( + '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 = array( '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( array( 0, 'top', 'foo', 'bar', 'bottom' ), array_keys( $arr ) ); + + $arr = array(); + ApiResult::setValue( $arr, 'sub', array( 'foo' => 1 ) ); + ApiResult::setValue( $arr, 'sub', array( 'bar' => 1 ) ); + $this->assertSame( array( 'sub' => array( 'foo' => 1, 'bar' => 1 ) ), $arr ); + + try { + ApiResult::setValue( $arr, 'sub', array( '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 = array(); + $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( array( + 'title' => (string)$title, + 'obj' => array( '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' + ); + } + + $arr = array(); + $result2 = new ApiResult( 8388608 ); + $result2->addValue( null, 'foo', 'bar' ); + ApiResult::setValue( $arr, 'baz', $result2 ); + $this->assertSame( array( + 'baz' => array( + ApiResult::META_TYPE => 'assoc', + 'foo' => 'bar', + ) + ), $arr ); + + $arr = array(); + 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( array( + 'foo' => "foo\xef\xbf\xbdbar", + 'bar' => "\xc3\xa1", + 'baz' => 74, + 0 => "foo\xef\xbf\xbdbar", + 1 => "\xc3\xa1", + ), $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( array( 'a', 'b' ), 'deleteValue', '3' ); + $result->removeValue( array( 'a', 'b', 'deleteValue' ), null, '3' ); + + $result->addContentValue( null, 'setContentValue', '3' ); + + $this->assertSame( array( + 'setValue' => '1', + 'unnamed 1', + 'unnamed 2', + 'a' => array( 'b' => array() ), + '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( array( 'setValue' ) ) ); + + $result->addContentValue( null, 'setContentValue2', '99', ApiResult::OVERRIDE ); + $this->assertSame( 'setContentValue2', + $result->getResultData( array( ApiResult::META_CONTENT ) ) ); + + $result->reset(); + $this->assertSame( array( + 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( array( 0, 'top', 'foo', 'bar', 'bottom', ApiResult::META_TYPE ), + array_keys( $result->getResultData() ) ); + + $result->reset(); + $result->addValue( null, 'foo', array( 'bar' => 1 ) ); + $result->addValue( array( 'foo', 'top' ), 'x', 2, ApiResult::ADD_ON_TOP ); + $result->addValue( array( 'foo', 'bottom' ), 'x', 2 ); + $this->assertSame( array( 'top', 'bar', 'bottom' ), + array_keys( $result->getResultData( array( 'foo' ) ) ) ); + + $result->reset(); + $result->addValue( null, 'sub', array( 'foo' => 1 ) ); + $result->addValue( null, 'sub', array( 'bar' => 1 ) ); + $this->assertSame( array( + 'sub' => array( 'foo' => 1, 'bar' => 1 ), + ApiResult::META_TYPE => 'assoc', + ), $result->getResultData() ); + + try { + $result->addValue( null, 'sub', array( '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( array( + 'title' => (string)$title, + 'obj' => array( '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->reset(); + $result->addParsedLimit( 'foo', 12 ); + $this->assertSame( array( + 'limits' => array( 'foo' => 12 ), + ApiResult::META_TYPE => 'assoc', + ), $result->getResultData() ); + $result->addParsedLimit( 'foo', 13 ); + $this->assertSame( array( + 'limits' => array( 'foo' => 13 ), + ApiResult::META_TYPE => 'assoc', + ), $result->getResultData() ); + $this->assertSame( null, $result->getResultData( array( 'foo', 'bar', 'baz' ) ) ); + $this->assertSame( 13, $result->getResultData( array( 'limits', 'foo' ) ) ); + try { + $result->getResultData( array( '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' + ); + } + + $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( 8388608 ); + $result2 = new ApiResult( 8388608 ); + $result2->addValue( null, 'foo', 'bar' ); + $result->addValue( null, 'baz', $result2 ); + $this->assertSame( array( + 'baz' => array( + '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( array( + 'foo' => "foo\xef\xbf\xbdbar", + 'bar' => "\xc3\xa1", + 'baz' => 74, + 0 => "foo\xef\xbf\xbdbar", + 1 => "\xc3\xa1", + ApiResult::META_TYPE => 'assoc', + ), $result->getResultData() ); + } + + /** + * @covers ApiResult + */ + public function testMetadata() { + $arr = array( 'foo' => array( 'bar' => array() ) ); + $result = new ApiResult( 8388608 ); + $result->addValue( null, 'foo', array( 'bar' => array() ) ); + + $expect = array( + 'foo' => array( + 'bar' => array( + ApiResult::META_INDEXED_TAG_NAME => 'ritn', + ApiResult::META_TYPE => 'default', + ), + ApiResult::META_INDEXED_TAG_NAME => 'ritn', + ApiResult::META_TYPE => 'default', + ), + ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ), + ApiResult::META_INDEXED_TAG_NAME => 'itn', + ApiResult::META_PRESERVE_KEYS => array( 'foo', 'bar' ), + ApiResult::META_TYPE => 'array', + ); + + ApiResult::setSubelementsList( $arr, 'foo' ); + ApiResult::setSubelementsList( $arr, array( 'bar', 'baz' ) ); + ApiResult::unsetSubelementsList( $arr, 'baz' ); + ApiResult::setIndexedTagNameRecursive( $arr, 'ritn' ); + ApiResult::setIndexedTagName( $arr, 'itn' ); + ApiResult::setPreserveKeysList( $arr, 'foo' ); + ApiResult::setPreserveKeysList( $arr, array( '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, array( 'bar', 'baz' ) ); + $result->removeSubelementsList( null, 'baz' ); + $result->addIndexedTagNameRecursive( null, 'ritn' ); + $result->addIndexedTagName( null, 'itn' ); + $result->addPreserveKeysList( null, 'foo' ); + $result->addPreserveKeysList( null, array( 'bar', 'baz' ) ); + $result->removePreserveKeysList( null, 'baz' ); + $result->addArrayTypeRecursive( null, 'default' ); + $result->addArrayType( null, 'array' ); + $this->assertEquals( $expect, $result->getResultData() ); + + $arr = array( 'foo' => array( 'bar' => array() ) ); + $expect = array( + 'foo' => array( + 'bar' => array( + 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 = array( + 'foo' => array( + 'bar' => array( '_dummy' => 'foobaz' ), + 'bar2' => (object)array( '_dummy' => 'foobaz' ), + 'x' => 'ok', + '_dummy' => 'foobaz', + ), + 'foo2' => (object)array( + 'bar' => array( '_dummy' => 'foobaz' ), + 'bar2' => (object)array( '_dummy' => 'foobaz' ), + 'x' => 'ok', + '_dummy' => 'foobaz', + ), + ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ), + ApiResult::META_INDEXED_TAG_NAME => 'itn', + ApiResult::META_PRESERVE_KEYS => array( 'foo', 'bar', '_dummy2', 0 ), + ApiResult::META_TYPE => 'array', + '_dummy' => 'foobaz', + '_dummy2' => 'foobaz!', + ); + $this->assertEquals( array( + 'foo' => array( + 'bar' => array(), + 'bar2' => (object)array(), + 'x' => 'ok', + ), + 'foo2' => (object)array( + 'bar' => array(), + 'bar2' => (object)array(), + 'x' => 'ok', + ), + '_dummy2' => 'foobaz!', + ), ApiResult::stripMetadata( $arr ), 'ApiResult::stripMetadata' ); + + $metadata = array(); + $data = ApiResult::stripMetadataNonRecursive( $arr, $metadata ); + $this->assertEquals( array( + 'foo' => array( + 'bar' => array( '_dummy' => 'foobaz' ), + 'bar2' => (object)array( '_dummy' => 'foobaz' ), + 'x' => 'ok', + '_dummy' => 'foobaz', + ), + 'foo2' => (object)array( + 'bar' => array( '_dummy' => 'foobaz' ), + 'bar2' => (object)array( '_dummy' => 'foobaz' ), + 'x' => 'ok', + '_dummy' => 'foobaz', + ), + '_dummy2' => 'foobaz!', + ), $data, 'ApiResult::stripMetadataNonRecursive ($data)' ); + $this->assertEquals( array( + ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ), + ApiResult::META_INDEXED_TAG_NAME => 'itn', + ApiResult::META_PRESERVE_KEYS => array( '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)array( + 'foo' => array( + 'bar' => array( '_dummy' => 'foobaz' ), + 'bar2' => (object)array( '_dummy' => 'foobaz' ), + 'x' => 'ok', + '_dummy' => 'foobaz', + ), + 'foo2' => (object)array( + 'bar' => array( '_dummy' => 'foobaz' ), + 'bar2' => (object)array( '_dummy' => 'foobaz' ), + 'x' => 'ok', + '_dummy' => 'foobaz', + ), + '_dummy2' => 'foobaz!', + ), $data, 'ApiResult::stripMetadataNonRecursive on object ($data)' ); + $this->assertEquals( array( + ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ), + ApiResult::META_INDEXED_TAG_NAME => 'itn', + ApiResult::META_PRESERVE_KEYS => array( '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 array( + $keyKey => $key, + $valKey => $value, + ApiResult::META_PRESERVE_KEYS => array( $keyKey ), + ApiResult::META_CONTENT => $valKey, + ApiResult::META_TYPE => 'assoc', + ); + }; + $typeArr = array( + 'defaultArray' => array( 2 => 'a', 0 => 'b', 1 => 'c' ), + 'defaultAssoc' => array( 'x' => 'a', 1 => 'b', 0 => 'c' ), + 'defaultAssoc2' => array( 2 => 'a', 3 => 'b', 0 => 'c' ), + 'array' => array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'array' ), + 'BCarray' => array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'BCarray' ), + 'BCassoc' => array( 'a', 'b', 'c', ApiResult::META_TYPE => 'BCassoc' ), + 'assoc' => array( 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'kvp' => array( 'x' => 'a', 'y' => 'b', 'z' => array( 'c' ), ApiResult::META_TYPE => 'kvp' ), + 'BCkvp' => array( 'x' => 'a', 'y' => 'b', + ApiResult::META_TYPE => 'BCkvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ), + 'emptyDefault' => array( '_dummy' => 1 ), + 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), + '_dummy' => 1, + ApiResult::META_PRESERVE_KEYS => array( '_dummy' ), + ); + $stripArr = array( + 'foo' => array( + 'bar' => array( '_dummy' => 'foobaz' ), + 'baz' => array( + ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ), + ApiResult::META_INDEXED_TAG_NAME => 'itn', + ApiResult::META_PRESERVE_KEYS => array( 'foo', 'bar', '_dummy2', 0 ), + ApiResult::META_TYPE => 'array', + ), + 'x' => 'ok', + '_dummy' => 'foobaz', + ), + ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ), + ApiResult::META_INDEXED_TAG_NAME => 'itn', + ApiResult::META_PRESERVE_KEYS => array( 'foo', 'bar', '_dummy2', 0 ), + ApiResult::META_TYPE => 'array', + '_dummy' => 'foobaz', + '_dummy2' => 'foobaz!', + ); + + return array( + array( + 'BC: META_BC_BOOLS', + array( + 'BCtrue' => true, + 'BCfalse' => false, + 'true' => true, + 'false' => false, + ApiResult::META_BC_BOOLS => array( 0, 'true', 'false' ), + ), + array( 'BC' => array() ), + array( + 'BCtrue' => '', + 'true' => true, + 'false' => false, + ApiResult::META_BC_BOOLS => array( 0, 'true', 'false' ), + ) + ), + array( + 'BC: META_BC_SUBELEMENTS', + array( + 'bc' => 'foo', + 'nobc' => 'bar', + ApiResult::META_BC_SUBELEMENTS => array( 'bc' ), + ), + array( 'BC' => array() ), + array( + 'bc' => array( + '*' => 'foo', + ApiResult::META_CONTENT => '*', + ApiResult::META_TYPE => 'assoc', + ), + 'nobc' => 'bar', + ApiResult::META_BC_SUBELEMENTS => array( 'bc' ), + ), + ), + array( + 'BC: META_CONTENT', + array( + 'content' => '!!!', + ApiResult::META_CONTENT => 'content', + ), + array( 'BC' => array() ), + array( + '*' => '!!!', + ApiResult::META_CONTENT => '*', + ), + ), + array( + 'BC: BCkvp type', + array( + 'foo' => 'foo value', + 'bar' => 'bar value', + '_baz' => 'baz value', + ApiResult::META_TYPE => 'BCkvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ApiResult::META_PRESERVE_KEYS => array( '_baz' ), + ), + array( 'BC' => array() ), + array( + $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 => array( '_baz' ), + ), + ), + array( + 'BC: BCarray type', + array( + ApiResult::META_TYPE => 'BCarray', + ), + array( 'BC' => array() ), + array( + ApiResult::META_TYPE => 'default', + ), + ), + array( + 'BC: BCassoc type', + array( + ApiResult::META_TYPE => 'BCassoc', + ), + array( 'BC' => array() ), + array( + ApiResult::META_TYPE => 'default', + ), + ), + array( + 'BC: BCkvp exception', + array( + ApiResult::META_TYPE => 'BCkvp', + ), + array( 'BC' => array() ), + new UnexpectedValueException( + 'Type "BCkvp" used without setting ApiResult::META_KVP_KEY_NAME metadata item' + ), + ), + array( + 'BC: nobool, no*, nosub', + array( + 'true' => true, + 'false' => false, + 'content' => 'content', + ApiResult::META_CONTENT => 'content', + 'bc' => 'foo', + ApiResult::META_BC_SUBELEMENTS => array( 'bc' ), + 'BCarray' => array( ApiResult::META_TYPE => 'BCarray' ), + 'BCassoc' => array( ApiResult::META_TYPE => 'BCassoc' ), + 'BCkvp' => array( + 'foo' => 'foo value', + 'bar' => 'bar value', + '_baz' => 'baz value', + ApiResult::META_TYPE => 'BCkvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ApiResult::META_PRESERVE_KEYS => array( '_baz' ), + ), + ), + array( 'BC' => array( 'nobool', 'no*', 'nosub' ) ), + array( + 'true' => true, + 'false' => false, + 'content' => 'content', + 'bc' => 'foo', + 'BCarray' => array( ApiResult::META_TYPE => 'default' ), + 'BCassoc' => array( ApiResult::META_TYPE => 'default' ), + 'BCkvp' => array( + $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 => array( '_baz' ), + ), + ApiResult::META_CONTENT => 'content', + ApiResult::META_BC_SUBELEMENTS => array( 'bc' ), + ), + ), + + array( + 'Types: Normal transform', + $typeArr, + array( 'Types' => array() ), + array( + 'defaultArray' => array( 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ), + 'defaultAssoc' => array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'defaultAssoc2' => array( 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'array' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), + 'BCarray' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), + 'BCassoc' => array( 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ), + 'assoc' => array( 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'kvp' => array( 'x' => 'a', 'y' => 'b', + 'z' => array( 'c', ApiResult::META_TYPE => 'array' ), + ApiResult::META_TYPE => 'assoc' + ), + 'BCkvp' => array( 'x' => 'a', 'y' => 'b', + ApiResult::META_TYPE => 'assoc', + ApiResult::META_KVP_KEY_NAME => 'key', + ), + 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), + 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), + '_dummy' => 1, + ApiResult::META_PRESERVE_KEYS => array( '_dummy' ), + ApiResult::META_TYPE => 'assoc', + ), + ), + array( + 'Types: AssocAsObject', + $typeArr, + array( 'Types' => array( 'AssocAsObject' => true ) ), + (object)array( + 'defaultArray' => array( 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ), + 'defaultAssoc' => (object)array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'defaultAssoc2' => (object)array( 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'array' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), + 'BCarray' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), + 'BCassoc' => (object)array( 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ), + 'assoc' => (object)array( 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'kvp' => (object)array( 'x' => 'a', 'y' => 'b', + 'z' => array( 'c', ApiResult::META_TYPE => 'array' ), + ApiResult::META_TYPE => 'assoc' + ), + 'BCkvp' => (object)array( 'x' => 'a', 'y' => 'b', + ApiResult::META_TYPE => 'assoc', + ApiResult::META_KVP_KEY_NAME => 'key', + ), + 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), + 'emptyAssoc' => (object)array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), + '_dummy' => 1, + ApiResult::META_PRESERVE_KEYS => array( '_dummy' ), + ApiResult::META_TYPE => 'assoc', + ), + ), + array( + 'Types: ArmorKVP', + $typeArr, + array( 'Types' => array( 'ArmorKVP' => 'name' ) ), + array( + 'defaultArray' => array( 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ), + 'defaultAssoc' => array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'defaultAssoc2' => array( 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'array' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), + 'BCarray' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), + 'BCassoc' => array( 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ), + 'assoc' => array( 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'kvp' => array( + $kvp( 'name', 'x', 'value', 'a' ), + $kvp( 'name', 'y', 'value', 'b' ), + $kvp( 'name', 'z', 'value', array( 'c', ApiResult::META_TYPE => 'array' ) ), + ApiResult::META_TYPE => 'array' + ), + 'BCkvp' => array( + $kvp( 'key', 'x', 'value', 'a' ), + $kvp( 'key', 'y', 'value', 'b' ), + ApiResult::META_TYPE => 'array', + ApiResult::META_KVP_KEY_NAME => 'key', + ), + 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), + 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), + '_dummy' => 1, + ApiResult::META_PRESERVE_KEYS => array( '_dummy' ), + ApiResult::META_TYPE => 'assoc', + ), + ), + array( + 'Types: ArmorKVP + BC', + $typeArr, + array( 'BC' => array(), 'Types' => array( 'ArmorKVP' => 'name' ) ), + array( + 'defaultArray' => array( 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ), + 'defaultAssoc' => array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'defaultAssoc2' => array( 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'array' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), + 'BCarray' => array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'BCassoc' => array( 'a', 'b', 'c', ApiResult::META_TYPE => 'array' ), + 'assoc' => array( 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'kvp' => array( + $kvp( 'name', 'x', '*', 'a' ), + $kvp( 'name', 'y', '*', 'b' ), + $kvp( 'name', 'z', '*', array( 'c', ApiResult::META_TYPE => 'array' ) ), + ApiResult::META_TYPE => 'array' + ), + 'BCkvp' => array( + $kvp( 'key', 'x', '*', 'a' ), + $kvp( 'key', 'y', '*', 'b' ), + ApiResult::META_TYPE => 'array', + ApiResult::META_KVP_KEY_NAME => 'key', + ), + 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), + 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), + '_dummy' => 1, + ApiResult::META_PRESERVE_KEYS => array( '_dummy' ), + ApiResult::META_TYPE => 'assoc', + ), + ), + array( + 'Types: ArmorKVP + AssocAsObject', + $typeArr, + array( 'Types' => array( 'ArmorKVP' => 'name', 'AssocAsObject' => true ) ), + (object)array( + 'defaultArray' => array( 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ), + 'defaultAssoc' => (object)array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'defaultAssoc2' => (object)array( 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'array' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), + 'BCarray' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), + 'BCassoc' => (object)array( 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ), + 'assoc' => (object)array( 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'kvp' => array( + (object)$kvp( 'name', 'x', 'value', 'a' ), + (object)$kvp( 'name', 'y', 'value', 'b' ), + (object)$kvp( 'name', 'z', 'value', array( 'c', ApiResult::META_TYPE => 'array' ) ), + ApiResult::META_TYPE => 'array' + ), + 'BCkvp' => array( + (object)$kvp( 'key', 'x', 'value', 'a' ), + (object)$kvp( 'key', 'y', 'value', 'b' ), + ApiResult::META_TYPE => 'array', + ApiResult::META_KVP_KEY_NAME => 'key', + ), + 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), + 'emptyAssoc' => (object)array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), + '_dummy' => 1, + ApiResult::META_PRESERVE_KEYS => array( '_dummy' ), + ApiResult::META_TYPE => 'assoc', + ), + ), + array( + 'Types: BCkvp exception', + array( + ApiResult::META_TYPE => 'BCkvp', + ), + array( 'Types' => array() ), + new UnexpectedValueException( + 'Type "BCkvp" used without setting ApiResult::META_KVP_KEY_NAME metadata item' + ), + ), + + array( + 'Strip: With ArmorKVP + AssocAsObject transforms', + $typeArr, + array( 'Types' => array( 'ArmorKVP' => 'name', 'AssocAsObject' => true ), 'Strip' => 'all' ), + (object)array( + 'defaultArray' => array( 'b', 'c', 'a' ), + 'defaultAssoc' => (object)array( 'x' => 'a', 1 => 'b', 0 => 'c' ), + 'defaultAssoc2' => (object)array( 2 => 'a', 3 => 'b', 0 => 'c' ), + 'array' => array( 'a', 'c', 'b' ), + 'BCarray' => array( 'a', 'c', 'b' ), + 'BCassoc' => (object)array( 'a', 'b', 'c' ), + 'assoc' => (object)array( 2 => 'a', 0 => 'b', 1 => 'c' ), + 'kvp' => array( + (object)array( 'name' => 'x', 'value' => 'a' ), + (object)array( 'name' => 'y', 'value' => 'b' ), + (object)array( 'name' => 'z', 'value' => array( 'c' ) ), + ), + 'BCkvp' => array( + (object)array( 'key' => 'x', 'value' => 'a' ), + (object)array( 'key' => 'y', 'value' => 'b' ), + ), + 'emptyDefault' => array(), + 'emptyAssoc' => (object)array(), + '_dummy' => 1, + ), + ), + + array( + 'Strip: all', + $stripArr, + array( 'Strip' => 'all' ), + array( + 'foo' => array( + 'bar' => array(), + 'baz' => array(), + 'x' => 'ok', + ), + '_dummy2' => 'foobaz!', + ), + ), + array( + 'Strip: base', + $stripArr, + array( 'Strip' => 'base' ), + array( + 'foo' => array( + 'bar' => array( '_dummy' => 'foobaz' ), + 'baz' => array( + ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ), + ApiResult::META_INDEXED_TAG_NAME => 'itn', + ApiResult::META_PRESERVE_KEYS => array( 'foo', 'bar', '_dummy2', 0 ), + ApiResult::META_TYPE => 'array', + ), + 'x' => 'ok', + '_dummy' => 'foobaz', + ), + '_dummy2' => 'foobaz!', + ), + ), + array( + 'Strip: bc', + $stripArr, + array( 'Strip' => 'bc' ), + array( + 'foo' => array( + 'bar' => array(), + 'baz' => array( + ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ), + ApiResult::META_INDEXED_TAG_NAME => 'itn', + ), + 'x' => 'ok', + ), + '_dummy2' => 'foobaz!', + ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ), + ApiResult::META_INDEXED_TAG_NAME => 'itn', + ), + ), + + array( + 'Custom transform', + array( + 'foo' => '?', + 'bar' => '?', + '_dummy' => '?', + '_dummy2' => '?', + '_dummy3' => '?', + ApiResult::META_CONTENT => 'foo', + ApiResult::META_PRESERVE_KEYS => array( '_dummy2', '_dummy3' ), + ), + array( + 'Custom' => array( $this, 'customTransform' ), + 'BC' => array(), + 'Types' => array(), + 'Strip' => 'all' + ), + array( + '*' => 'FOO', + 'bar' => 'BAR', + 'baz' => array( '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'] = array( '_added' => 1, 'z' => 'b', 'y' => 'a' ); + $metadata[ApiResult::META_PRESERVE_KEYS][0] = '_dummy'; + $data[ApiResult::META_CONTENT] = 'bar'; + } + + /** + * @covers ApiResult + */ + public function testDeprecatedFunctions() { + // Ignore ApiResult deprecation warnings during this test + set_error_handler( function ( $errno, $errstr ) use ( &$warnings ) { + if ( preg_match( '/Use of ApiResult::\S+ was deprecated in MediaWiki \d+.\d+\./', $errstr ) ) { + return true; + } + if ( preg_match( '/Use of ApiMain to ApiResult::__construct was deprecated in MediaWiki \d+.\d+\./', $errstr ) ) { + return true; + } + return false; + } ); + $reset = new ScopedCallback( 'restore_error_handler' ); + + $context = new DerivativeContext( RequestContext::getMain() ); + $context->setConfig( new HashConfig( array( + 'APIModules' => array(), + 'APIFormatModules' => array(), + 'APIMaxResultSize' => 42, + ) ) ); + $main = new ApiMain( $context ); + $result = TestingAccessWrapper::newFromObject( new ApiResult( $main ) ); + $this->assertSame( 42, $result->maxSize ); + $this->assertSame( $main->getErrorFormatter(), $result->errorFormatter ); + $this->assertSame( $main, $result->mainForContinuation ); + + $result = new ApiResult( 8388608 ); + + $result->addContentValue( null, 'test', 'content' ); + $result->addContentValue( array( 'foo', 'bar' ), 'test', 'content' ); + $result->addIndexedTagName( null, 'itn' ); + $result->addSubelementsList( null, array( 'sub' ) ); + $this->assertSame( array( + 'foo' => array( + 'bar' => array( + '*' => 'content', + ), + ), + '*' => 'content', + ), $result->getData() ); + $result->setRawMode(); + $this->assertSame( array( + 'foo' => array( + 'bar' => array( + '*' => 'content', + ), + ), + '*' => 'content', + '_element' => 'itn', + '_subelements' => array( 'sub' ), + ), $result->getData() ); + + $arr = array(); + ApiResult::setContent( $arr, 'value' ); + ApiResult::setContent( $arr, 'value2', 'foobar' ); + $this->assertSame( array( + ApiResult::META_CONTENT => 'content', + 'content' => 'value', + 'foobar' => array( + ApiResult::META_CONTENT => 'content', + 'content' => 'value2', + ), + ), $arr ); + + $result = new ApiResult( 3 ); + $formatter = new ApiErrorFormatter_BackCompat( $result ); + $result->setErrorFormatter( $formatter ); + $result->disableSizeCheck(); + $this->assertTrue( $result->addValue( null, 'foo', '1234567890' ) ); + $result->enableSizeCheck(); + $this->assertSame( 0, $result->getSize() ); + $this->assertFalse( $result->addValue( null, 'foo', '1234567890' ) ); + + $arr = array( 'foo' => array( 'bar' => 1 ) ); + $result->setIndexedTagName_recursive( $arr, 'itn' ); + $this->assertSame( array( + 'foo' => array( + 'bar' => 1, + ApiResult::META_INDEXED_TAG_NAME => 'itn' + ), + ), $arr ); + + $status = Status::newGood(); + $status->fatal( 'parentheses', '1' ); + $status->fatal( 'parentheses', '2' ); + $status->warning( 'parentheses', '3' ); + $status->warning( 'parentheses', '4' ); + $this->assertSame( array( + array( + 'type' => 'error', + 'message' => 'parentheses', + 'params' => array( + 0 => '1', + ApiResult::META_INDEXED_TAG_NAME => 'param', + ), + ), + array( + 'type' => 'error', + 'message' => 'parentheses', + 'params' => array( + 0 => '2', + ApiResult::META_INDEXED_TAG_NAME => 'param', + ), + ), + ApiResult::META_INDEXED_TAG_NAME => 'error', + ), $result->convertStatusToArray( $status, 'error' ) ); + $this->assertSame( array( + array( + 'type' => 'warning', + 'message' => 'parentheses', + 'params' => array( + 0 => '3', + ApiResult::META_INDEXED_TAG_NAME => 'param', + ), + ), + array( + 'type' => 'warning', + 'message' => 'parentheses', + 'params' => array( + 0 => '4', + ApiResult::META_INDEXED_TAG_NAME => 'param', + ), + ), + ApiResult::META_INDEXED_TAG_NAME => 'warning', + ), $result->convertStatusToArray( $status, 'warning' ) ); + } + + /** + * @covers ApiResult + */ + public function testDeprecatedContinuation() { + // Ignore ApiResult deprecation warnings during this test + set_error_handler( function ( $errno, $errstr ) use ( &$warnings ) { + if ( preg_match( '/Use of ApiResult::\S+ was deprecated in MediaWiki \d+.\d+\./', $errstr ) ) { + return true; + } + return false; + } ); + + $reset = new ScopedCallback( 'restore_error_handler' ); + $allModules = array( + new MockApiQueryBase( 'mock1' ), + new MockApiQueryBase( 'mock2' ), + new MockApiQueryBase( 'mocklist' ), + ); + $generator = new MockApiQueryBase( 'generator' ); + + $main = new ApiMain( RequestContext::getMain() ); + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( array( false, $allModules ), $ret ); + $result->setContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) ); + $result->setContinueParam( $allModules[2], 'mlcontinue', 2 ); + $result->setGeneratorContinueParam( $generator, 'gcontinue', 3 ); + $result->endContinuation( 'raw' ); + $result->endContinuation( 'standard' ); + $this->assertSame( array( + 'mlcontinue' => 2, + 'm1continue' => '1|2', + 'continue' => '||mock2', + ), $result->getResultData( 'continue' ) ); + $this->assertSame( null, $result->getResultData( 'batchcomplete' ) ); + $this->assertSame( array( + 'mock1' => array( 'm1continue' => '1|2' ), + 'mocklist' => array( 'mlcontinue' => 2 ), + 'generator' => array( 'gcontinue' => 3 ), + ), $result->getResultData( 'query-continue' ) ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( array( false, $allModules ), $ret ); + $result->setContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) ); + $result->setGeneratorContinueParam( $generator, 'gcontinue', array( 3, 4 ) ); + $result->endContinuation( 'raw' ); + $result->endContinuation( 'standard' ); + $this->assertSame( array( + 'm1continue' => '1|2', + 'continue' => '||mock2|mocklist', + ), $result->getResultData( 'continue' ) ); + $this->assertSame( null, $result->getResultData( 'batchcomplete' ) ); + $this->assertSame( array( + 'mock1' => array( 'm1continue' => '1|2' ), + 'generator' => array( 'gcontinue' => '3|4' ), + ), $result->getResultData( 'query-continue' ) ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( array( false, $allModules ), $ret ); + $result->setContinueParam( $allModules[2], 'mlcontinue', 2 ); + $result->setGeneratorContinueParam( $generator, 'gcontinue', 3 ); + $result->endContinuation( 'raw' ); + $result->endContinuation( 'standard' ); + $this->assertSame( array( + 'mlcontinue' => 2, + 'gcontinue' => 3, + 'continue' => 'gcontinue||', + ), $result->getResultData( 'continue' ) ); + $this->assertSame( true, $result->getResultData( 'batchcomplete' ) ); + $this->assertSame( array( + 'mocklist' => array( 'mlcontinue' => 2 ), + 'generator' => array( 'gcontinue' => 3 ), + ), $result->getResultData( 'query-continue' ) ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( array( false, $allModules ), $ret ); + $result->setGeneratorContinueParam( $generator, 'gcontinue', 3 ); + $result->endContinuation( 'raw' ); + $result->endContinuation( 'standard' ); + $this->assertSame( array( + 'gcontinue' => 3, + 'continue' => 'gcontinue||mocklist', + ), $result->getResultData( 'continue' ) ); + $this->assertSame( true, $result->getResultData( 'batchcomplete' ) ); + $this->assertSame( array( + 'generator' => array( 'gcontinue' => 3 ), + ), $result->getResultData( 'query-continue' ) ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( array( false, $allModules ), $ret ); + $result->setContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) ); + $result->setContinueParam( $allModules[2], 'mlcontinue', 2 ); + $result->endContinuation( 'raw' ); + $result->endContinuation( 'standard' ); + $this->assertSame( array( + 'mlcontinue' => 2, + 'm1continue' => '1|2', + 'continue' => '||mock2', + ), $result->getResultData( 'continue' ) ); + $this->assertSame( null, $result->getResultData( 'batchcomplete' ) ); + $this->assertSame( array( + 'mock1' => array( 'm1continue' => '1|2' ), + 'mocklist' => array( 'mlcontinue' => 2 ), + ), $result->getResultData( 'query-continue' ) ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( array( false, $allModules ), $ret ); + $result->setContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) ); + $result->endContinuation( 'raw' ); + $result->endContinuation( 'standard' ); + $this->assertSame( array( + 'm1continue' => '1|2', + 'continue' => '||mock2|mocklist', + ), $result->getResultData( 'continue' ) ); + $this->assertSame( null, $result->getResultData( 'batchcomplete' ) ); + $this->assertSame( array( + 'mock1' => array( 'm1continue' => '1|2' ), + ), $result->getResultData( 'query-continue' ) ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( array( false, $allModules ), $ret ); + $result->setContinueParam( $allModules[2], 'mlcontinue', 2 ); + $result->endContinuation( 'raw' ); + $result->endContinuation( 'standard' ); + $this->assertSame( array( + 'mlcontinue' => 2, + 'continue' => '-||mock1|mock2', + ), $result->getResultData( 'continue' ) ); + $this->assertSame( true, $result->getResultData( 'batchcomplete' ) ); + $this->assertSame( array( + 'mocklist' => array( 'mlcontinue' => 2 ), + ), $result->getResultData( 'query-continue' ) ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( array( false, $allModules ), $ret ); + $result->endContinuation( 'raw' ); + $result->endContinuation( 'standard' ); + $this->assertSame( null, $result->getResultData( 'continue' ) ); + $this->assertSame( true, $result->getResultData( 'batchcomplete' ) ); + $this->assertSame( null, $result->getResultData( 'query-continue' ) ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( '||mock2', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( + array( false, array_values( array_diff_key( $allModules, array( 1 => 1 ) ) ) ), + $ret + ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( '-||', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( + array( true, array_values( array_diff_key( $allModules, array( 0 => 0, 1 => 1 ) ) ) ), + $ret + ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + try { + $result->beginContinuation( 'foo', $allModules, array( 'mock1', 'mock2' ) ); + $this->fail( 'Expected exception not thrown' ); + } catch ( UsageException $ex ) { + $this->assertSame( + 'Invalid continue param. You should pass the original value returned by the previous query', + $ex->getMessage(), + 'Expected exception' + ); + } + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $result->beginContinuation( '||mock2', array_slice( $allModules, 0, 2 ), array( 'mock1', 'mock2' ) ); + try { + $result->setContinueParam( $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 { + $result->setContinueParam( $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' + ); + } + $main->setContinuationManager( null ); + + } + + public function testObjectSerialization() { + $arr = array(); + ApiResult::setValue( $arr, 'foo', (object)array( 'a' => 1, 'b' => 2 ) ); + $this->assertSame( array( + 'a' => 1, + 'b' => 2, + ApiResult::META_TYPE => 'assoc', + ), $arr['foo'] ); + + $arr = array(); + ApiResult::setValue( $arr, 'foo', new ApiResultTestStringifiableObject() ); + $this->assertSame( 'Ok', $arr['foo'] ); + + $arr = array(); + ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( 'Ok' ) ); + $this->assertSame( 'Ok', $arr['foo'] ); + + try { + $arr = array(); + 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 = array(); + 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 = array(); + ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( + array( + 'one' => new ApiResultTestStringifiableObject( '1' ), + 'two' => new ApiResultTestSerializableObject( 2 ), + ) + ) ); + $this->assertSame( array( + '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/ApiTestCase.php b/tests/phpunit/includes/api/ApiTestCase.php index cd141947..da62bb0a 100644 --- a/tests/phpunit/includes/api/ApiTestCase.php +++ b/tests/phpunit/includes/api/ApiTestCase.php @@ -8,6 +8,11 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { */ protected $apiContext; + /** + * @var array + */ + protected $tablesUsed = array( 'user', 'user_groups', 'user_properties' ); + protected function setUp() { global $wgServer; @@ -41,6 +46,17 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { $this->apiContext = new ApiTestContext(); } + protected function tearDown() { + // Avoid leaking session over tests + if ( session_id() != '' ) { + global $wgUser; + $wgUser->logout(); + session_destroy(); + } + + parent::tearDown(); + } + /** * Edits or creates a page/revision * @param string $pageName Page title @@ -100,7 +116,7 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { // construct result $results = array( - $module->getResultData(), + $module->getResult()->getResultData( null, array( 'Strip' => 'all' ) ), $context->getRequest(), $context->getRequest()->getSessionArray() ); @@ -134,10 +150,14 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { } if ( isset( $session['wsToken'] ) && $session['wsToken'] ) { + // @todo Why does this directly mess with the session? Fix that. // add edit token to fake session $session['wsEditToken'] = $session['wsToken']; // add token to request parameters - $params['token'] = md5( $session['wsToken'] ) . User::EDIT_TOKEN_SUFFIX; + $timestamp = wfTimestamp(); + $params['token'] = hash_hmac( 'md5', $timestamp, $session['wsToken'] ) . + dechex( $timestamp ) . + User::EDIT_TOKEN_SUFFIX; return $this->doApiRequest( $params, $session, false, $user ); } else { diff --git a/tests/phpunit/includes/api/ApiTestCaseUpload.php b/tests/phpunit/includes/api/ApiTestCaseUpload.php index 7e513394..87f794c1 100644 --- a/tests/phpunit/includes/api/ApiTestCaseUpload.php +++ b/tests/phpunit/includes/api/ApiTestCaseUpload.php @@ -1,9 +1,8 @@ clearFakeUploads(); } - protected function tearDown() { - $this->clearTempUpload(); - - parent::tearDown(); - } - /** * Helper function -- remove files and associated articles by Title * @@ -105,7 +98,7 @@ abstract class ApiTestCaseUpload extends ApiTestCase { * @return bool */ function fakeUploadFile( $fieldName, $fileName, $type, $filePath ) { - $tmpName = tempnam( wfTempDir(), "" ); + $tmpName = $this->getNewTempFile(); if ( !file_exists( $filePath ) ) { throw new Exception( "$filePath doesn't exist!" ); } @@ -132,7 +125,7 @@ abstract class ApiTestCaseUpload extends ApiTestCase { } function fakeUploadChunk( $fieldName, $fileName, $type, & $chunkData ) { - $tmpName = tempnam( wfTempDir(), "" ); + $tmpName = $this->getNewTempFile(); // copy the chunk data to temp location: if ( !file_put_contents( $tmpName, $chunkData ) ) { throw new Exception( "couldn't copy chunk data to $tmpName" ); @@ -153,15 +146,6 @@ abstract class ApiTestCaseUpload extends ApiTestCase { ); } - function clearTempUpload() { - if ( isset( $_FILES['file']['tmp_name'] ) ) { - $tmp = $_FILES['file']['tmp_name']; - if ( file_exists( $tmp ) ) { - unlink( $tmp ); - } - } - } - /** * Remove traces of previous fake uploads */ diff --git a/tests/phpunit/includes/api/ApiUploadTest.php b/tests/phpunit/includes/api/ApiUploadTest.php index 8ea761f8..f74fc354 100644 --- a/tests/phpunit/includes/api/ApiUploadTest.php +++ b/tests/phpunit/includes/api/ApiUploadTest.php @@ -1,31 +1,24 @@ writeImages( 1, $extension, wfTempDir() ); + $filePaths = $randomImageGenerator->writeImages( 1, $extension, $this->getNewTempDirectory() ); } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -145,7 +138,6 @@ class ApiUploadTest extends ApiTestCaseUpload { // clean up $this->deleteFileByFilename( $fileName ); - unlink( $filePath ); } /** @@ -154,7 +146,7 @@ class ApiUploadTest extends ApiTestCaseUpload { public function testUploadZeroLength( $session ) { $mimeType = 'image/png'; - $filePath = tempnam( wfTempDir(), "" ); + $filePath = $this->getNewTempFile(); $fileName = "apiTestUploadZeroLength.png"; $this->deleteFileByFileName( $fileName ); @@ -182,7 +174,6 @@ class ApiUploadTest extends ApiTestCaseUpload { // clean up $this->deleteFileByFilename( $fileName ); - unlink( $filePath ); } /** @@ -194,7 +185,7 @@ class ApiUploadTest extends ApiTestCaseUpload { try { $randomImageGenerator = new RandomImageGenerator(); - $filePaths = $randomImageGenerator->writeImages( 2, $extension, wfTempDir() ); + $filePaths = $randomImageGenerator->writeImages( 2, $extension, $this->getNewTempDirectory() ); } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -253,8 +244,6 @@ class ApiUploadTest extends ApiTestCaseUpload { // clean up $this->deleteFileByFilename( $fileName ); - unlink( $filePaths[0] ); - unlink( $filePaths[1] ); } /** @@ -266,7 +255,7 @@ class ApiUploadTest extends ApiTestCaseUpload { try { $randomImageGenerator = new RandomImageGenerator(); - $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() ); + $filePaths = $randomImageGenerator->writeImages( 1, $extension, $this->getNewTempDirectory() ); } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -335,7 +324,6 @@ class ApiUploadTest extends ApiTestCaseUpload { // clean up $this->deleteFileByFilename( $fileNames[0] ); $this->deleteFileByFilename( $fileNames[1] ); - unlink( $filePaths[0] ); } /** @@ -351,7 +339,7 @@ class ApiUploadTest extends ApiTestCaseUpload { try { $randomImageGenerator = new RandomImageGenerator(); - $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() ); + $filePaths = $randomImageGenerator->writeImages( 1, $extension, $this->getNewTempDirectory() ); } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -419,7 +407,6 @@ class ApiUploadTest extends ApiTestCaseUpload { // clean up $this->deleteFileByFilename( $fileName ); - unlink( $filePath ); } /** @@ -433,16 +420,14 @@ class ApiUploadTest extends ApiTestCaseUpload { $chunkSize = 1048576; // Download a large image file - // ( using RandomImageGenerator for large files is not stable ) + // (using RandomImageGenerator for large files is not stable) + // @todo Don't download files from wikimedia.org $mimeType = 'image/jpeg'; $url = 'http://upload.wikimedia.org/wikipedia/commons/' . 'e/ed/Oberaargletscher_from_Oberaar%2C_2010_07.JPG'; - $filePath = wfTempDir() . '/Oberaargletscher_from_Oberaar.jpg'; + $filePath = $this->getNewTempDirectory() . '/Oberaargletscher_from_Oberaar.jpg'; try { - // Only download if the file is not avaliable in the temp location: - if ( !is_file( $filePath ) ) { - copy( $url, $filePath ); - } + copy( $url, $filePath ); } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -566,7 +551,5 @@ class ApiUploadTest extends ApiTestCaseUpload { // clean up $this->deleteFileByFilename( $fileName ); - // don't remove downloaded temporary file for fast subquent tests. - //unlink( $filePath ); } } diff --git a/tests/phpunit/includes/api/MockApi.php b/tests/phpunit/includes/api/MockApi.php index d94aa2cd..516da0c8 100644 --- a/tests/phpunit/includes/api/MockApi.php +++ b/tests/phpunit/includes/api/MockApi.php @@ -4,9 +4,6 @@ class MockApi extends ApiBase { public function execute() { } - public function getVersion() { - } - public function __construct() { } diff --git a/tests/phpunit/includes/api/MockApiQueryBase.php b/tests/phpunit/includes/api/MockApiQueryBase.php index 4bede519..f5b50e5a 100644 --- a/tests/phpunit/includes/api/MockApiQueryBase.php +++ b/tests/phpunit/includes/api/MockApiQueryBase.php @@ -1,11 +1,15 @@ name = $name; } - public function __construct() { + public function getModuleName() { + return $this->name; } } diff --git a/tests/phpunit/includes/api/PrefixUniquenessTest.php b/tests/phpunit/includes/api/PrefixUniquenessTest.php index 13da33c7..d04766be 100644 --- a/tests/phpunit/includes/api/PrefixUniquenessTest.php +++ b/tests/phpunit/includes/api/PrefixUniquenessTest.php @@ -20,7 +20,7 @@ class PrefixUniquenessTest extends MediaWikiTestCase { $class = get_class( $module ); $prefix = $module->getModulePrefix(); - if ( isset( $prefixes[$prefix] ) ) { + if ( $prefix !== '' && isset( $prefixes[$prefix] ) ) { $this->fail( "Module prefix '{$prefix}' is shared between {$class} and {$prefixes[$prefix]}" ); } $prefixes[$module->getModulePrefix()] = $class; diff --git a/tests/phpunit/includes/api/format/ApiFormatDbgTest.php b/tests/phpunit/includes/api/format/ApiFormatDbgTest.php new file mode 100644 index 00000000..3fcfc73f --- /dev/null +++ b/tests/phpunit/includes/api/format/ApiFormatDbgTest.php @@ -0,0 +1,55 @@ + \n array (\n 'dbg' => \n array (\n" . + " '*' => 'format=dbg has been deprecated. Please use format=json instead.',\n" . + " ),\n ),"; + + return array( + // Basic types + array( array( null ), "array ({$warning}\n 0 => NULL,\n)" ), + array( array( true ), "array ({$warning}\n 0 => '',\n)" ), + array( array( false ), "array ({$warning}\n)" ), + array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), + "array ({$warning}\n 0 => true,\n)" ), + array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), + "array ({$warning}\n 0 => false,\n)" ), + array( array( 42 ), "array ({$warning}\n 0 => 42,\n)" ), + array( array( 42.5 ), "array ({$warning}\n 0 => 42.5,\n)" ), + array( array( 1e42 ), "array ({$warning}\n 0 => 1.0E+42,\n)" ), + array( array( 'foo' ), "array ({$warning}\n 0 => 'foo',\n)" ), + array( array( 'fóo' ), "array ({$warning}\n 0 => 'fóo',\n)" ), + + // Arrays and objects + array( array( array() ), "array ({$warning}\n 0 => \n array (\n ),\n)" ), + array( array( array( 1 ) ), "array ({$warning}\n 0 => \n array (\n 0 => 1,\n ),\n)" ), + array( array( array( 'x' => 1 ) ), "array ({$warning}\n 0 => \n array (\n 'x' => 1,\n ),\n)" ), + array( array( array( 2 => 1 ) ), "array ({$warning}\n 0 => \n array (\n 2 => 1,\n ),\n)" ), + array( array( (object)array() ), "array ({$warning}\n 0 => \n array (\n ),\n)" ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), "array ({$warning}\n 0 => \n array (\n 0 => 1,\n ),\n)" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), "array ({$warning}\n 0 => \n array (\n 0 => 1,\n ),\n)" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), "array ({$warning}\n 0 => \n array (\n 'x' => 1,\n ),\n)" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + "array ({$warning}\n 0 => \n array (\n 0 => \n array (\n 'key' => 'x',\n '*' => 1,\n ),\n ),\n)" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), "array ({$warning}\n 0 => \n array (\n 'x' => 1,\n ),\n)" ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), "array ({$warning}\n 0 => \n array (\n 0 => 'a',\n 1 => 'b',\n ),\n)" ), + + // Content + array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + "array ({$warning}\n '*' => 'foo',\n)" ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), + "array ({$warning}\n 'foo' => \n array (\n '*' => 'foo',\n ),\n)" ), + ); + } + +} diff --git a/tests/phpunit/includes/api/format/ApiFormatDumpTest.php b/tests/phpunit/includes/api/format/ApiFormatDumpTest.php new file mode 100644 index 00000000..c0f67f8d --- /dev/null +++ b/tests/phpunit/includes/api/format/ApiFormatDumpTest.php @@ -0,0 +1,63 @@ + true ) ), + ); + } + + $warning = "\n [\"warnings\"]=>\n array(1) {\n [\"dump\"]=>\n array(1) {\n [\"*\"]=>\n" . + " string(64) \"format=dump has been deprecated. Please use format=json instead.\"\n" . + " }\n }"; + + return array( + // Basic types + array( array( null ), "array(2) {{$warning}\n [0]=>\n NULL\n}\n" ), + array( array( true ), "array(2) {{$warning}\n [0]=>\n string(0) \"\"\n}\n" ), + array( array( false ), "array(1) {{$warning}\n}\n" ), + array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), + "array(2) {{$warning}\n [0]=>\n bool(true)\n}\n" ), + array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), + "array(2) {{$warning}\n [0]=>\n bool(false)\n}\n" ), + array( array( 42 ), "array(2) {{$warning}\n [0]=>\n int(42)\n}\n" ), + array( array( 42.5 ), "array(2) {{$warning}\n [0]=>\n float(42.5)\n}\n" ), + array( array( 1e42 ), "array(2) {{$warning}\n [0]=>\n float(1.0E+42)\n}\n" ), + array( array( 'foo' ), "array(2) {{$warning}\n [0]=>\n string(3) \"foo\"\n}\n" ), + array( array( 'fóo' ), "array(2) {{$warning}\n [0]=>\n string(4) \"fóo\"\n}\n" ), + + // Arrays + array( array( array() ), "array(2) {{$warning}\n [0]=>\n array(0) {\n }\n}\n" ), + array( array( array( 1 ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [0]=>\n int(1)\n }\n}\n" ), + array( array( array( 'x' => 1 ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [\"x\"]=>\n int(1)\n }\n}\n" ), + array( array( array( 2 => 1 ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [2]=>\n int(1)\n }\n}\n" ), + array( array( (object)array() ), "array(2) {{$warning}\n [0]=>\n array(0) {\n }\n}\n" ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [0]=>\n int(1)\n }\n}\n" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [0]=>\n int(1)\n }\n}\n" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [\"x\"]=>\n int(1)\n }\n}\n" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + "array(2) {{$warning}\n [0]=>\n array(1) {\n [0]=>\n array(2) {\n [\"key\"]=>\n string(1) \"x\"\n [\"*\"]=>\n int(1)\n }\n }\n}\n" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [\"x\"]=>\n int(1)\n }\n}\n" ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), "array(2) {{$warning}\n [0]=>\n array(2) {\n [0]=>\n string(1) \"a\"\n [1]=>\n string(1) \"b\"\n }\n}\n" ), + + // Content + array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + "array(2) {{$warning}\n [\"*\"]=>\n string(3) \"foo\"\n}\n" ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), + "array(2) {{$warning}\n [\"foo\"]=>\n array(1) {\n [\"*\"]=>\n string(3) \"foo\"\n }\n}\n" ), + ); + } + +} diff --git a/tests/phpunit/includes/api/format/ApiFormatJsonTest.php b/tests/phpunit/includes/api/format/ApiFormatJsonTest.php index fc1f9021..3dfcaf0f 100644 --- a/tests/phpunit/includes/api/format/ApiFormatJsonTest.php +++ b/tests/phpunit/includes/api/format/ApiFormatJsonTest.php @@ -2,21 +2,109 @@ /** * @group API - * @group Database - * @group medium * @covers ApiFormatJson */ class ApiFormatJsonTest extends ApiFormatTestBase { - public function testValidSyntax( ) { - $data = $this->apiRequest( 'json', array( 'action' => 'query', 'meta' => 'siteinfo' ) ); + protected $printerName = 'json'; - $this->assertInternalType( 'array', json_decode( $data, true ) ); - $this->assertGreaterThan( 0, count( (array)$data ) ); + private static function addFormatVersion( $format, $arr ) { + foreach ( $arr as &$p ) { + if ( !isset( $p[2] ) ) { + $p[2] = array( 'formatversion' => $format ); + } else { + $p[2]['formatversion'] = $format; + } + } + return $arr; } - public function testJsonpInjection( ) { - $data = $this->apiRequest( 'json', array( 'action' => 'query', 'meta' => 'siteinfo', 'callback' => 'myCallback' ) ); - $this->assertEquals( '/**/myCallback(', substr( $data, 0, 15 ) ); + public static function provideGeneralEncoding() { + return array_merge( + self::addFormatVersion( 1, array( + // Basic types + array( array( null ), '[null]' ), + array( array( true ), '[""]' ), + array( array( false ), '[]' ), + array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), '[true]' ), + array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), '[false]' ), + array( array( 42 ), '[42]' ), + array( array( 42.5 ), '[42.5]' ), + array( array( 1e42 ), '[1.0e+42]' ), + array( array( 'foo' ), '["foo"]' ), + array( array( 'fóo' ), '["f\u00f3o"]' ), + array( array( 'fóo' ), '["fóo"]', array( 'utf8' => 1 ) ), + + // Arrays and objects + array( array( array() ), '[[]]' ), + array( array( array( 1 ) ), '[[1]]' ), + array( array( array( 'x' => 1 ) ), '[{"x":1}]' ), + array( array( array( 2 => 1 ) ), '[{"2":1}]' ), + array( array( (object)array() ), '[{}]' ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), '[{"0":1}]' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), '[[1]]' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), '[{"x":1}]' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + '[[{"key":"x","*":1}]]' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), '[{"x":1}]' ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), '[["a","b"]]' ), + + // Content + array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + '{"*":"foo"}' ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), + '{"foo":{"*":"foo"}}' ), + + // Callbacks + array( array( 1 ), '/**/myCallback([1])', array( 'callback' => 'myCallback' ) ), + + // Cross-domain mangling + array( array( '< Cross-Domain-Policy >' ), '["\u003C Cross-Domain-Policy \u003E"]' ), + ) ), + self::addFormatVersion( 2, array( + // Basic types + array( array( null ), '[null]' ), + array( array( true ), '[true]' ), + array( array( false ), '[false]' ), + array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), '[true]' ), + array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), '[false]' ), + array( array( 42 ), '[42]' ), + array( array( 42.5 ), '[42.5]' ), + array( array( 1e42 ), '[1.0e+42]' ), + array( array( 'foo' ), '["foo"]' ), + array( array( 'fóo' ), '["fóo"]' ), + array( array( 'fóo' ), '["f\u00f3o"]', array( 'ascii' => 1 ) ), + + // Arrays and objects + array( array( array() ), '[[]]' ), + array( array( array( 'x' => 1 ) ), '[{"x":1}]' ), + array( array( array( 2 => 1 ) ), '[{"2":1}]' ), + array( array( (object)array() ), '[{}]' ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), '[{"0":1}]' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), '[[1]]' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), '[{"x":1}]' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + '[{"x":1}]' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), '[[1]]' ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), '[{"0":"a","1":"b"}]' ), + + // Content + array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + '{"content":"foo"}' ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), + '{"foo":"foo"}' ), + + // Callbacks + array( array( 1 ), '/**/myCallback([1])', array( 'callback' => 'myCallback' ) ), + + // Cross-domain mangling + array( array( '< Cross-Domain-Policy >' ), '["\u003C Cross-Domain-Policy \u003E"]' ), + ) ) + ); } + } diff --git a/tests/phpunit/includes/api/format/ApiFormatNoneTest.php b/tests/phpunit/includes/api/format/ApiFormatNoneTest.php index cabd750b..8f81a411 100644 --- a/tests/phpunit/includes/api/format/ApiFormatNoneTest.php +++ b/tests/phpunit/includes/api/format/ApiFormatNoneTest.php @@ -2,15 +2,43 @@ /** * @group API - * @group Database - * @group medium * @covers ApiFormatNone */ class ApiFormatNoneTest extends ApiFormatTestBase { - public function testValidSyntax( ) { - $data = $this->apiRequest( 'none', array( 'action' => 'query', 'meta' => 'siteinfo' ) ); + protected $printerName = 'none'; - $this->assertEquals( '', $data ); // No output! + public static function provideGeneralEncoding() { + return array( + // Basic types + array( array( null ), '' ), + array( array( true ), '' ), + array( array( false ), '' ), + array( array( 42 ), '' ), + array( array( 42.5 ), '' ), + array( array( 1e42 ), '' ), + array( array( 'foo' ), '' ), + array( array( 'fóo' ), '' ), + + // Arrays and objects + array( array( array() ), '' ), + array( array( array( 1 ) ), '' ), + array( array( array( 'x' => 1 ) ), '' ), + array( array( array( 2 => 1 ) ), '' ), + array( array( (object)array() ), '' ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), '' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), '' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), '' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), '' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), '' ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), '' ), + + // Content + array( array( '*' => 'foo' ), '' ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), '' ), + ); } + } diff --git a/tests/phpunit/includes/api/format/ApiFormatPhpTest.php b/tests/phpunit/includes/api/format/ApiFormatPhpTest.php index 54f447a9..0cb44e92 100644 --- a/tests/phpunit/includes/api/format/ApiFormatPhpTest.php +++ b/tests/phpunit/includes/api/format/ApiFormatPhpTest.php @@ -2,16 +2,143 @@ /** * @group API - * @group Database - * @group medium * @covers ApiFormatPhp */ class ApiFormatPhpTest extends ApiFormatTestBase { - public function testValidSyntax( ) { - $data = $this->apiRequest( 'php', array( 'action' => 'query', 'meta' => 'siteinfo' ) ); + protected $printerName = 'php'; - $this->assertInternalType( 'array', unserialize( $data ) ); - $this->assertGreaterThan( 0, count( (array)$data ) ); + private static function addFormatVersion( $format, $arr ) { + foreach ( $arr as &$p ) { + if ( !isset( $p[2] ) ) { + $p[2] = array( 'formatversion' => $format ); + } else { + $p[2]['formatversion'] = $format; + } + } + return $arr; } + + public static function provideGeneralEncoding() { + return array_merge( + self::addFormatVersion( 1, array( + // Basic types + array( array( null ), 'a:1:{i:0;N;}' ), + array( array( true ), 'a:1:{i:0;s:0:"";}' ), + array( array( false ), 'a:0:{}' ), + array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), + 'a:1:{i:0;b:1;}' ), + array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), + 'a:1:{i:0;b:0;}' ), + array( array( 42 ), 'a:1:{i:0;i:42;}' ), + array( array( 42.5 ), 'a:1:{i:0;d:42.5;}' ), + array( array( 1e42 ), 'a:1:{i:0;d:1.0E+42;}' ), + array( array( 'foo' ), 'a:1:{i:0;s:3:"foo";}' ), + array( array( 'fóo' ), 'a:1:{i:0;s:4:"fóo";}' ), + + // Arrays and objects + array( array( array() ), 'a:1:{i:0;a:0:{}}' ), + array( array( array( 1 ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ), + array( array( array( 'x' => 1 ) ), 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ), + array( array( array( 2 => 1 ) ), 'a:1:{i:0;a:1:{i:2;i:1;}}' ), + array( array( (object)array() ), 'a:1:{i:0;a:0:{}}' ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + 'a:1:{i:0;a:1:{i:0;a:2:{s:3:"key";s:1:"x";s:1:"*";i:1;}}}' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), 'a:1:{i:0;a:2:{i:0;s:1:"a";i:1;s:1:"b";}}' ), + + // Content + array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + 'a:1:{s:1:"*";s:3:"foo";}' ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), + 'a:1:{s:3:"foo";a:1:{s:1:"*";s:3:"foo";}}' ), + ) ), + self::addFormatVersion( 2, array( + // Basic types + array( array( null ), 'a:1:{i:0;N;}' ), + array( array( true ), 'a:1:{i:0;b:1;}' ), + array( array( false ), 'a:1:{i:0;b:0;}' ), + array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), + 'a:1:{i:0;b:1;}' ), + array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), + 'a:1:{i:0;b:0;}' ), + array( array( 42 ), 'a:1:{i:0;i:42;}' ), + array( array( 42.5 ), 'a:1:{i:0;d:42.5;}' ), + array( array( 1e42 ), 'a:1:{i:0;d:1.0E+42;}' ), + array( array( 'foo' ), 'a:1:{i:0;s:3:"foo";}' ), + array( array( 'fóo' ), 'a:1:{i:0;s:4:"fóo";}' ), + + // Arrays and objects + array( array( array() ), 'a:1:{i:0;a:0:{}}' ), + array( array( array( 1 ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ), + array( array( array( 'x' => 1 ) ), 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ), + array( array( array( 2 => 1 ) ), 'a:1:{i:0;a:1:{i:2;i:1;}}' ), + array( array( (object)array() ), 'a:1:{i:0;a:0:{}}' ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), 'a:1:{i:0;a:2:{i:0;s:1:"a";i:1;s:1:"b";}}' ), + + // Content + array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + 'a:1:{s:7:"content";s:3:"foo";}' ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), + 'a:1:{s:3:"foo";s:3:"foo";}' ), + ) ) + ); + } + + public function testCrossDomainMangling() { + $config = new HashConfig( array( 'MangleFlashPolicy' => false ) ); + $context = new RequestContext; + $context->setConfig( new MultiConfig( array( + $config, + $context->getConfig(), + ) ) ); + $main = new ApiMain( $context ); + $main->getResult()->addValue( null, null, '< Cross-Domain-Policy >' ); + + if ( !function_exists( 'wfOutputHandler' ) ) { + function wfOutputHandler( $s ) { + return $s; + } + } + + $printer = $main->createPrinterByName( 'php' ); + ob_start( 'wfOutputHandler' ); + $printer->initPrinter(); + $printer->execute(); + $printer->closePrinter(); + $ret = ob_get_clean(); + $this->assertSame( 'a:1:{i:0;s:23:"< Cross-Domain-Policy >";}', $ret ); + + $config->set( 'MangleFlashPolicy', true ); + $printer = $main->createPrinterByName( 'php' ); + ob_start( 'wfOutputHandler' ); + try { + $printer->initPrinter(); + $printer->execute(); + $printer->closePrinter(); + ob_end_clean(); + $this->fail( 'Expected exception not thrown' ); + } catch ( UsageException $ex ) { + ob_end_clean(); + $this->assertSame( + 'This response cannot be represented using format=php. See https://bugzilla.wikimedia.org/show_bug.cgi?id=66776', + $ex->getMessage(), + 'Expected exception' + ); + } + } + } diff --git a/tests/phpunit/includes/api/format/ApiFormatTestBase.php b/tests/phpunit/includes/api/format/ApiFormatTestBase.php index 5f6d53ce..cabf62b1 100644 --- a/tests/phpunit/includes/api/format/ApiFormatTestBase.php +++ b/tests/phpunit/includes/api/format/ApiFormatTestBase.php @@ -1,32 +1,64 @@ createPrinterByName( $format ); - $printer->setUnescapeAmps( false ); + /** + * Return general data to be encoded for testing + * @return array See self::testGeneralEncoding + * @throws Exception + */ + public static function provideGeneralEncoding() { + throw new Exception( 'Subclass must implement ' . __METHOD__ ); + } - $printer->initPrinter( false ); + /** + * Get the formatter output for the given input data + * @param array $params Query parameters + * @param array $data Data to encode + * @param string $class Printer class to use instead of the normal one + * @return string + * @throws Exception + */ + protected function encodeData( array $params, array $data, $class = null ) { + $context = new RequestContext; + $context->setRequest( new FauxRequest( $params, true ) ); + $main = new ApiMain( $context ); + if ( $class !== null ) { + $main->getModuleManager()->addModule( $this->printerName, 'format', $class ); + } + $result = $main->getResult(); + $result->addArrayType( null, 'default' ); + foreach ( $data as $k => $v ) { + $result->addValue( null, $k, $v ); + } - ob_start(); + $printer = $main->createPrinterByName( $this->printerName ); + $printer->initPrinter(); $printer->execute(); - $out = ob_get_clean(); - - $printer->closePrinter(); + ob_start(); + try { + $printer->closePrinter(); + return ob_get_clean(); + } catch ( Exception $ex ) { + ob_end_clean(); + throw $ex; + } + } - return $out; + /** + * @dataProvider provideGeneralEncoding + */ + public function testGeneralEncoding( array $data, $expect, array $params = array() ) { + if ( isset( $params['SKIP'] ) ) { + $this->markTestSkipped( $expect ); + } + $this->assertSame( $expect, $this->encodeData( $params, $data ) ); } } diff --git a/tests/phpunit/includes/api/format/ApiFormatTxtTest.php b/tests/phpunit/includes/api/format/ApiFormatTxtTest.php new file mode 100644 index 00000000..b0a2a960 --- /dev/null +++ b/tests/phpunit/includes/api/format/ApiFormatTxtTest.php @@ -0,0 +1,55 @@ + Array\n (\n [txt] => Array\n (\n" . + " [*] => format=txt has been deprecated. Please use format=json instead.\n" . + " )\n\n )\n"; + + return array( + // Basic types + array( array( null ), "Array\n({$warning}\n [0] => \n)\n" ), + array( array( true ), "Array\n({$warning}\n [0] => \n)\n" ), + array( array( false ), "Array\n({$warning}\n)\n" ), + array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), + "Array\n({$warning}\n [0] => 1\n)\n" ), + array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), + "Array\n({$warning}\n [0] => \n)\n" ), + array( array( 42 ), "Array\n({$warning}\n [0] => 42\n)\n" ), + array( array( 42.5 ), "Array\n({$warning}\n [0] => 42.5\n)\n" ), + array( array( 1e42 ), "Array\n({$warning}\n [0] => 1.0E+42\n)\n" ), + array( array( 'foo' ), "Array\n({$warning}\n [0] => foo\n)\n" ), + array( array( 'fóo' ), "Array\n({$warning}\n [0] => fóo\n)\n" ), + + // Arrays and objects + array( array( array() ), "Array\n({$warning}\n [0] => Array\n (\n )\n\n)\n" ), + array( array( array( 1 ) ), "Array\n({$warning}\n [0] => Array\n (\n [0] => 1\n )\n\n)\n" ), + array( array( array( 'x' => 1 ) ), "Array\n({$warning}\n [0] => Array\n (\n [x] => 1\n )\n\n)\n" ), + array( array( array( 2 => 1 ) ), "Array\n({$warning}\n [0] => Array\n (\n [2] => 1\n )\n\n)\n" ), + array( array( (object)array() ), "Array\n({$warning}\n [0] => Array\n (\n )\n\n)\n" ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), "Array\n({$warning}\n [0] => Array\n (\n [0] => 1\n )\n\n)\n" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), "Array\n({$warning}\n [0] => Array\n (\n [0] => 1\n )\n\n)\n" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), "Array\n({$warning}\n [0] => Array\n (\n [x] => 1\n )\n\n)\n" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + "Array\n({$warning}\n [0] => Array\n (\n [0] => Array\n (\n [key] => x\n [*] => 1\n )\n\n )\n\n)\n" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), "Array\n({$warning}\n [0] => Array\n (\n [x] => 1\n )\n\n)\n" ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), "Array\n({$warning}\n [0] => Array\n (\n [0] => a\n [1] => b\n )\n\n)\n" ), + + // Content + array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + "Array\n({$warning}\n [*] => foo\n)\n" ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), + "Array\n({$warning}\n [foo] => Array\n (\n [*] => foo\n )\n\n)\n" ), + ); + } + +} diff --git a/tests/phpunit/includes/api/format/ApiFormatWddxTest.php b/tests/phpunit/includes/api/format/ApiFormatWddxTest.php index d075f547..07111300 100644 --- a/tests/phpunit/includes/api/format/ApiFormatWddxTest.php +++ b/tests/phpunit/includes/api/format/ApiFormatWddxTest.php @@ -2,19 +2,79 @@ /** * @group API - * @group Database - * @group medium * @covers ApiFormatWddx */ class ApiFormatWddxTest extends ApiFormatTestBase { + protected $printerName = 'wddx'; + + public static function provideGeneralEncoding() { + if ( ApiFormatWddx::useSlowPrinter() ) { + return array( + array( array(), 'Fast Wddx printer is unavailable', array( 'SKIP' => true ) ) + ); + } + return self::provideEncoding(); + } + + public static function provideEncoding() { + $p = '
format=wddx has been deprecated. Please use format=json instead.'; + $s = ''; + + return array( + // Basic types + array( array( null ), "{$p}{$s}" ), + array( array( true ), "{$p}{$s}" ), + array( array( false ), "{$p}{$s}" ), + array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), + "{$p}{$s}" ), + array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), + "{$p}{$s}" ), + array( array( 42 ), "{$p}42{$s}" ), + array( array( 42.5 ), "{$p}42.5{$s}" ), + array( array( 1e42 ), "{$p}1.0E+42{$s}" ), + array( array( 'foo' ), "{$p}foo{$s}" ), + array( array( 'fóo' ), "{$p}fóo{$s}" ), + + // Arrays and objects + array( array( array() ), "{$p}{$s}" ), + array( array( array( 1 ) ), "{$p}1{$s}" ), + array( array( array( 'x' => 1 ) ), "{$p}1{$s}" ), + array( array( array( 2 => 1 ) ), "{$p}1{$s}" ), + array( array( (object)array() ), "{$p}{$s}" ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), "{$p}1{$s}" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), "{$p}1{$s}" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), "{$p}1{$s}" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + "{$p}x1{$s}" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), "{$p}1{$s}" ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), "{$p}ab{$s}" ), + + // Content + array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + "{$p}foo{$s}" ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), + "{$p}foo{$s}" ), + ); + } + /** - * @requires function wddx_deserialize + * @dataProvider provideEncoding */ - public function testValidSyntax( ) { - $data = $this->apiRequest( 'wddx', array( 'action' => 'query', 'meta' => 'siteinfo' ) ); + public function testSlowEncoding( array $data, $expect, array $params = array() ) { + // Adjust expectation for differences between fast and slow printers. + $expect = str_replace( '\'', '"', $expect ); + $expect = str_replace( '/>', ' />', $expect ); + $expect = '' . $expect; + + $this->assertSame( $expect, $this->encodeData( $params, $data, 'ApiFormatWddxTest_SlowWddx' ) ); + } +} - $this->assertInternalType( 'array', wddx_deserialize( $data ) ); - $this->assertGreaterThan( 0, count( (array)$data ) ); +class ApiFormatWddxTest_SlowWddx extends ApiFormatWddx { + public static function useSlowPrinter() { + return true; } } diff --git a/tests/phpunit/includes/api/format/ApiFormatXmlTest.php b/tests/phpunit/includes/api/format/ApiFormatXmlTest.php new file mode 100644 index 00000000..7babaedb --- /dev/null +++ b/tests/phpunit/includes/api/format/ApiFormatXmlTest.php @@ -0,0 +1,119 @@ +doEditContent( new WikitextContent( + '' + ), 'Summary' ); + $page = WikiPage::factory( Title::newFromText( 'MediaWiki:ApiFormatXmlTest' ) ); + $page->doEditContent( new WikitextContent( 'Bogus' ), 'Summary' ); + $page = WikiPage::factory( Title::newFromText( 'ApiFormatXmlTest' ) ); + $page->doEditContent( new WikitextContent( 'Bogus' ), 'Summary' ); + } + + public static function provideGeneralEncoding() { + return array( + // Basic types + array( array( null, 'a' => null ), '<_v _idx="0" />' ), + array( array( true, 'a' => true ), '<_v _idx="0">true' ), + array( array( false, 'a' => false ), '<_v _idx="0">false' ), + array( array( true, 'a' => true, ApiResult::META_BC_BOOLS => array( 0, 'a' ) ), + '<_v _idx="0">1' ), + array( array( false, 'a' => false, ApiResult::META_BC_BOOLS => array( 0, 'a' ) ), + '<_v _idx="0">' ), + array( array( 42, 'a' => 42 ), '<_v _idx="0">42' ), + array( array( 42.5, 'a' => 42.5 ), '<_v _idx="0">42.5' ), + array( array( 1e42, 'a' => 1e42 ), '<_v _idx="0">1.0E+42' ), + array( array( 'foo', 'a' => 'foo' ), '<_v _idx="0">foo' ), + array( array( 'fóo', 'a' => 'fóo' ), '<_v _idx="0">fóo' ), + + // Arrays and objects + array( array( array() ), '<_v />' ), + array( array( array( 'x' => 1 ) ), '<_v x="1" />' ), + array( array( array( 2 => 1 ) ), '<_v><_v _idx="2">1' ), + array( array( (object)array() ), '<_v />' ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), '<_v><_v _idx="0">1' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), '<_v><_v>1' ), + array( array( array( 'x' => 1, 'y' => array( 'z' => 1 ), ApiResult::META_TYPE => 'kvp' ) ), + '<_v><_v _name="x" xml:space="preserve">1<_v _name="y">1' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp', ApiResult::META_INDEXED_TAG_NAME => 'i', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + '<_v>1' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + '<_v><_v key="x" xml:space="preserve">1' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), '<_v x="1" />' ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), '<_v><_v _idx="0">a<_v _idx="1">b' ), + + // Content + array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + 'foo' ), + + // Specified element name + array( array( 'foo', 'bar', ApiResult::META_INDEXED_TAG_NAME => 'itn' ), + 'foobar' ), + + // Subelements + array( array( 'a' => 1, 's' => 1, '_subelements' => array( 's' ) ), + '1' ), + + // Content and subelement + array( array( 'a' => 1, 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + 'foo' ), + array( array( 's' => array(), 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + 'foo' ), + array( + array( + 's' => 1, + 'content' => 'foo', + ApiResult::META_CONTENT => 'content', + ApiResult::META_SUBELEMENTS => array( 's' ) + ), + '1foo' + ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), + 'foo' ), + + // Name mangling + array( array( 'foo.bar' => 1 ), '' ), + array( array( '' => 1 ), '' ), + array( array( 'foo bar' => 1 ), '' ), + array( array( 'foo:bar' => 1 ), '' ), + array( array( 'foo%.bar' => 1 ), '' ), + array( array( '4foo' => 1, 'foo4' => 1 ), '' ), + array( array( "foo\xe3\x80\x80bar" => 1 ), '' ), + array( array( 'foo:bar' => 1, ApiResult::META_PRESERVE_KEYS => array( 'foo:bar' ) ), + '' ), + array( array( 'a', 'b', ApiResult::META_INDEXED_TAG_NAME => 'foo bar' ), + '<_foo.20.bar>a<_foo.20.bar>b' ), + + // includenamespace param + array( array( 'x' => 'foo' ), '', + array( 'includexmlnamespace' => 1 ) ), + + // xslt param + array( array(), 'Invalid or non-existent stylesheet specified', + array( 'xslt' => 'DoesNotExist' ) ), + array( array(), 'Stylesheet should be in the MediaWiki namespace.', + array( 'xslt' => 'ApiFormatXmlTest' ) ), + array( array(), 'Stylesheet should have .xsl extension.', + array( 'xslt' => 'MediaWiki:ApiFormatXmlTest' ) ), + array( array(), + 'getLocalURL( 'action=raw' ) ) . + '" type="text/xsl" ?>', + array( 'xslt' => 'MediaWiki:ApiFormatXmlTest.xsl' ) ), + ); + } + +} diff --git a/tests/phpunit/includes/api/query/ApiQueryBasicTest.php b/tests/phpunit/includes/api/query/ApiQueryBasicTest.php index e486c4f4..fa0e4cb5 100644 --- a/tests/phpunit/includes/api/query/ApiQueryBasicTest.php +++ b/tests/phpunit/includes/api/query/ApiQueryBasicTest.php @@ -23,8 +23,6 @@ * @file */ -require_once 'ApiQueryTestBase.php'; - /** * These tests validate basic functionality of the api query module * diff --git a/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php b/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php index 347cd6f8..cd735223 100644 --- a/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php +++ b/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php @@ -18,8 +18,6 @@ * http://www.gnu.org/copyleft/gpl.html */ -require_once 'ApiQueryContinueTestBase.php'; - /** * @group API * @group Database @@ -62,7 +60,8 @@ class ApiQueryContinue2Test extends ApiQueryContinueTestBase { ); }; // generator + 1 prop + 1 list - $data = $this->query( $mk( 99, 99, true ), 1, 'g1p', false ); + $data = $this->query( $mk( 99, 99, true ), 1, 'g1p', false ) + + array( 'batchcomplete' => true ); $this->checkC( $data, $mk( 1, 1, true ), 6, 'g1p-11t' ); $this->checkC( $data, $mk( 2, 2, true ), 3, 'g1p-22t' ); $this->checkC( $data, $mk( 1, 1, false ), 6, 'g1p-11f' ); diff --git a/tests/phpunit/includes/api/query/ApiQueryContinueTest.php b/tests/phpunit/includes/api/query/ApiQueryContinueTest.php index 03797901..d441f4c4 100644 --- a/tests/phpunit/includes/api/query/ApiQueryContinueTest.php +++ b/tests/phpunit/includes/api/query/ApiQueryContinueTest.php @@ -18,8 +18,6 @@ * http://www.gnu.org/copyleft/gpl.html */ -require_once 'ApiQueryContinueTestBase.php'; - /** * These tests validate the new continue functionality of the api query module by * doing multiple requests with varying parameters, merging the results, and checking @@ -68,7 +66,8 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase { 'aplimit' => "$l", ); }; - $data = $this->query( $mk( 99 ), 1, '1L', false ); + $data = $this->query( $mk( 99 ), 1, '1L', false ) + + array( 'batchcomplete' => true ); // 1 list $this->checkC( $data, $mk( 1 ), 5, '1L-1' ); @@ -95,7 +94,8 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase { ); }; // 2 lists - $data = $this->query( $mk( 99, 99 ), 1, '2L', false ); + $data = $this->query( $mk( 99, 99 ), 1, '2L', false ) + + array( 'batchcomplete' => true ); $this->checkC( $data, $mk( 1, 1 ), 5, '2L-11' ); $this->checkC( $data, $mk( 2, 2 ), 3, '2L-22' ); $this->checkC( $data, $mk( 3, 3 ), 2, '2L-33' ); @@ -119,7 +119,8 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase { ); }; // generator + 1 prop - $data = $this->query( $mk( 99, 99 ), 1, 'G1P', false ); + $data = $this->query( $mk( 99, 99 ), 1, 'G1P', false ) + + array( 'batchcomplete' => true ); $this->checkC( $data, $mk( 1, 1 ), 11, 'G1P-11' ); $this->checkC( $data, $mk( 2, 2 ), 6, 'G1P-22' ); $this->checkC( $data, $mk( 3, 3 ), 4, 'G1P-33' ); @@ -144,7 +145,8 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase { ); }; // generator + 2 props - $data = $this->query( $mk( 99, 99, 99 ), 1, 'G2P', false ); + $data = $this->query( $mk( 99, 99, 99 ), 1, 'G2P', false ) + + array( 'batchcomplete' => true ); $this->checkC( $data, $mk( 1, 1, 1 ), 16, 'G2P-111' ); $this->checkC( $data, $mk( 2, 2, 2 ), 9, 'G2P-222' ); $this->checkC( $data, $mk( 3, 3, 3 ), 6, 'G2P-333' ); @@ -177,7 +179,8 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase { ); }; // generator + 1 prop + 1 list - $data = $this->query( $mk( 99, 99, 99 ), 1, 'G1P1L', false ); + $data = $this->query( $mk( 99, 99, 99 ), 1, 'G1P1L', false ) + + array( 'batchcomplete' => true ); $this->checkC( $data, $mk( 1, 1, 1 ), 11, 'G1P1L-111' ); $this->checkC( $data, $mk( 2, 2, 2 ), 6, 'G1P1L-222' ); $this->checkC( $data, $mk( 3, 3, 3 ), 4, 'G1P1L-333' ); @@ -214,7 +217,8 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase { ); }; // generator + 1 prop + 1 list - $data = $this->query( $mk( 99, 99, 99, 99, 99 ), 1, 'G2P2L1M', false ); + $data = $this->query( $mk( 99, 99, 99, 99, 99 ), 1, 'G2P2L1M', false ) + + array( 'batchcomplete' => true ); $this->checkC( $data, $mk( 1, 1, 1, 1, 1 ), 16, 'G2P2L1M-11111' ); $this->checkC( $data, $mk( 2, 2, 2, 2, 2 ), 9, 'G2P2L1M-22222' ); $this->checkC( $data, $mk( 3, 3, 3, 3, 3 ), 6, 'G2P2L1M-33333' ); @@ -244,7 +248,8 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase { ); }; // generator + 1 prop - $data = $this->query( $mk( 99, true, 99, true ), 1, 'G=P', false ); + $data = $this->query( $mk( 99, true, 99, true ), 1, 'G=P', false ) + + array( 'batchcomplete' => true ); $this->checkC( $data, $mk( 1, true, 1, true ), 4, 'G=P-1t1t' ); $this->checkC( $data, $mk( 2, true, 2, true ), 2, 'G=P-2t2t' ); @@ -290,7 +295,8 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase { ); }; // generator + 1 list - $data = $this->query( $mk( 99, true, 99, true ), 1, 'G=L', false ); + $data = $this->query( $mk( 99, true, 99, true ), 1, 'G=L', false ) + + array( 'batchcomplete' => true ); $this->checkC( $data, $mk( 1, true, 1, true ), 5, 'G=L-1t1t' ); $this->checkC( $data, $mk( 2, true, 2, true ), 3, 'G=L-2t2t' ); diff --git a/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php b/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php index bce62685..ce2f70de 100644 --- a/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php +++ b/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php @@ -21,9 +21,6 @@ * * @file */ - -require_once 'ApiQueryTestBase.php'; - abstract class ApiQueryContinueTestBase extends ApiQueryTestBase { /** @@ -62,6 +59,8 @@ abstract class ApiQueryContinueTestBase extends ApiQueryTestBase { } if ( $useContinue && !isset( $params['continue'] ) ) { $params['continue'] = ''; + } else { + $params['rawcontinue'] = '1'; } $count = 0; $result = array(); diff --git a/tests/phpunit/includes/api/query/ApiQueryTest.php b/tests/phpunit/includes/api/query/ApiQueryTest.php index bba22c77..5f061b50 100644 --- a/tests/phpunit/includes/api/query/ApiQueryTest.php +++ b/tests/phpunit/includes/api/query/ApiQueryTest.php @@ -7,32 +7,21 @@ * @covers ApiQuery */ class ApiQueryTest extends ApiTestCase { - /** - * @var array Storage for $wgHooks - */ - protected $hooks; - protected function setUp() { - global $wgHooks; - parent::setUp(); $this->doLogin(); - // Setup en: as interwiki prefix - $this->hooks = $wgHooks; - $wgHooks['InterwikiLoadPrefix'][] = function ( $prefix, &$data ) { - if ( $prefix == 'apiquerytestiw' ) { - $data = array( 'iw_url' => 'wikipedia' ); - } - return false; - }; - } - - protected function tearDown() { - global $wgHooks; - $wgHooks = $this->hooks; - - parent::tearDown(); + // Setup apiquerytestiw: as interwiki prefix + $this->setMwGlobals( 'wgHooks', array( + 'InterwikiLoadPrefix' => array( + function ( $prefix, &$data ) { + if ( $prefix == 'apiquerytestiw' ) { + $data = array( 'iw_url' => 'wikipedia' ); + } + return false; + } + ) + ) ); } public function testTitlesGetNormalized() { @@ -127,4 +116,27 @@ class ApiQueryTest extends ApiTestCase { array( 'apiquerytestiw:foo', NS_MAIN, null, true ), ); } + + /** + * Test if all classes in the query module manager exists + */ + public function testClassNamesInModuleManager() { + global $wgAutoloadLocalClasses, $wgAutoloadClasses; + + // wgAutoloadLocalClasses has precedence, just like in includes/AutoLoader.php + $classes = $wgAutoloadLocalClasses + $wgAutoloadClasses; + + $api = new ApiMain( + new FauxRequest( array( 'action' => 'query', 'meta' => 'siteinfo' ) ) + ); + $queryApi = new ApiQuery( $api, 'query' ); + $modules = $queryApi->getModuleManager()->getNamesWithClasses(); + foreach( $modules as $name => $class ) { + $this->assertArrayHasKey( + $class, + $classes, + 'Class ' . $class . ' for api module ' . $name . ' not in autoloader (with exact case)' + ); + } + } } diff --git a/tests/phpunit/includes/api/query/ApiQueryTestBase.php b/tests/phpunit/includes/api/query/ApiQueryTestBase.php index 56c15b23..dabf72e0 100644 --- a/tests/phpunit/includes/api/query/ApiQueryTestBase.php +++ b/tests/phpunit/includes/api/query/ApiQueryTestBase.php @@ -88,19 +88,26 @@ STR; /** * Checks that the request's result matches the expected results. * @param array $values Array is a two element array( request, expected_results ) - * @throws Exception + * @param array $session + * @param bool $appendModule + * @param User $user */ - protected function check( $values ) { + protected function check( $values, array $session = null, + $appendModule = false, User $user = null + ) { list( $req, $exp ) = $this->validateRequestExpectedPair( $values ); if ( !array_key_exists( 'action', $req ) ) { $req['action'] = 'query'; } + if ( !array_key_exists( 'continue', $req ) ) { + $req['rawcontinue'] = '1'; + } foreach ( $req as &$val ) { if ( is_array( $val ) ) { $val = implode( '|', array_unique( $val ) ); } } - $result = $this->doApiRequest( $req ); + $result = $this->doApiRequest( $req, $session, $appendModule, $user ); $this->assertResult( array( 'query' => $exp ), $result[0], $req ); } @@ -113,9 +120,16 @@ STR; if ( is_array( $message ) ) { $message = http_build_query( $message ); } + + // FIXME: once we migrate to phpunit 4.1+, hardcode ComparisonFailure exception use + $compEx = 'SebastianBergmann\Comparator\ComparisonFailure'; + if ( !class_exists( $compEx ) ) { + $compEx = 'PHPUnit_Framework_ComparisonFailure'; + } + throw new PHPUnit_Framework_ExpectationFailedException( $e->getMessage() . "\nRequest: $message", - new PHPUnit_Framework_ComparisonFailure( + new $compEx( $exp, $result, print_r( $exp, true ), diff --git a/tests/phpunit/includes/cache/GenderCacheTest.php b/tests/phpunit/includes/cache/GenderCacheTest.php index ce2db5d7..04fb00d9 100644 --- a/tests/phpunit/includes/cache/GenderCacheTest.php +++ b/tests/phpunit/includes/cache/GenderCacheTest.php @@ -6,14 +6,10 @@ */ class GenderCacheTest extends MediaWikiLangTestCase { - protected function setUp() { - global $wgDefaultUserOptions; - parent::setUp(); + function addDBData() { //ensure the correct default gender - $wgDefaultUserOptions['gender'] = 'unknown'; - } + $this->mergeMwGlobalArrayValue( 'wgDefaultUserOptions', array( 'gender' => 'unknown' ) ); - function addDBData() { $user = User::newFromName( 'UTMale' ); if ( $user->getID() == 0 ) { $user->addToDatabase(); diff --git a/tests/phpunit/includes/cache/LocalisationCacheTest.php b/tests/phpunit/includes/cache/LocalisationCacheTest.php index fc06a501..a0d308aa 100644 --- a/tests/phpunit/includes/cache/LocalisationCacheTest.php +++ b/tests/phpunit/includes/cache/LocalisationCacheTest.php @@ -7,18 +7,32 @@ */ class LocalisationCacheTest extends MediaWikiTestCase { protected function setUp() { - global $IP; - parent::setUp(); $this->setMwGlobals( array( - 'wgMessagesDirs' => array( "$IP/tests/phpunit/data/localisationcache" ), 'wgExtensionMessagesFiles' => array(), 'wgHooks' => array(), ) ); } + /** + * @return PHPUnit_Framework_MockObject_MockObject|LocalisationCache + */ + protected function getMockLocalisationCache() { + global $IP; + $lc = $this->getMockBuilder( 'LocalisationCache' ) + ->setConstructorArgs( array( array( 'store' => 'detect' ) ) ) + ->setMethods( array( 'getMessagesDirs' ) ) + ->getMock(); + $lc->expects( $this->any() )->method( 'getMessagesDirs' ) + ->will( $this->returnValue( + array( "$IP/tests/phpunit/data/localisationcache" ) + ) ); + + return $lc; + } + public function testPuralRulesFallback() { - $cache = new LocalisationCache( array( 'store' => 'detect' ) ); + $cache = $this->getMockLocalisationCache(); $this->assertEquals( $cache->getItem( 'ar', 'pluralRules' ), @@ -46,7 +60,7 @@ class LocalisationCacheTest extends MediaWikiTestCase { } public function testRecacheFallbacks() { - $lc = new LocalisationCache( array( 'store' => 'detect' ) ); + $lc = $this->getMockLocalisationCache(); $lc->recache( 'uk' ); $this->assertEquals( array( @@ -60,23 +74,25 @@ class LocalisationCacheTest extends MediaWikiTestCase { } public function testRecacheFallbacksWithHooks() { - global $wgHooks; - // Use hook to provide updates for messages. This is what the // LocalisationUpdate extension does. See bug 68781. - $wgHooks['LocalisationCacheRecacheFallback'][] = function ( - LocalisationCache $lc, - $code, - array &$cache - ) { - if ( $code === 'ru' ) { - $cache['messages']['present-uk'] = 'ru-override'; - $cache['messages']['present-ru'] = 'ru-override'; - $cache['messages']['present-en'] = 'ru-override'; - } - }; + $this->mergeMwGlobalArrayValue( 'wgHooks', array( + 'LocalisationCacheRecacheFallback' => array( + function ( + LocalisationCache $lc, + $code, + array &$cache + ) { + if ( $code === 'ru' ) { + $cache['messages']['present-uk'] = 'ru-override'; + $cache['messages']['present-ru'] = 'ru-override'; + $cache['messages']['present-en'] = 'ru-override'; + } + } + ) + ) ); - $lc = new LocalisationCache( array( 'store' => 'detect' ) ); + $lc = $this->getMockLocalisationCache(); $lc->recache( 'uk' ); $this->assertEquals( array( diff --git a/tests/phpunit/includes/cache/RedisBloomCacheTest.php b/tests/phpunit/includes/cache/RedisBloomCacheTest.php deleted file mode 100644 index 3d491e90..00000000 --- a/tests/phpunit/includes/cache/RedisBloomCacheTest.php +++ /dev/null @@ -1,71 +0,0 @@ -delete( "unit-testing-" . self::$suffix ); - } else { - $this->markTestSkipped( 'The main bloom cache is not redis.' ); - } - } - - public function testBloomCache() { - $key = "unit-testing-" . self::$suffix; - $fcache = BloomCache::get( 'main' ); - $count = 1500; - - $this->assertTrue( $fcache->delete( $key ), "OK delete of filter '$key'." ); - $this->assertTrue( $fcache->init( $key, $count, .001 ), "OK init of filter '$key'." ); - - $members = array(); - for ( $i = 0; $i < $count; ++$i ) { - $members[] = "$i-value-$i"; - } - $this->assertTrue( $fcache->add( $key, $members ), "Addition of members to '$key' OK." ); - - for ( $i = 0; $i < $count; ++$i ) { - $this->assertTrue( $fcache->isHit( $key, "$i-value-$i" ), "Hit on member '$i-value-$i'." ); - } - - $falsePositives = array(); - for ( $i = $count; $i < 2 * $count; ++$i ) { - if ( $fcache->isHit( $key, "value$i" ) ) { - $falsePositives[] = "value$i"; - } - } - - $eFalsePositives = array( - 'value1763', - 'value2245', - 'value2353', - 'value2791', - 'value2898', - 'value2975' - ); - $this->assertEquals( $eFalsePositives, $falsePositives, "Correct number of false positives found." ); - } - - protected function tearDown() { - parent::tearDown(); - - $fcache = BloomCache::get( 'main' ); - if ( $fcache instanceof BloomCacheRedis ) { - $fcache->delete( "unit-testing-" . self::$suffix ); - } - } -} diff --git a/tests/phpunit/includes/changes/EnhancedChangesListTest.php b/tests/phpunit/includes/changes/EnhancedChangesListTest.php index 40a11d2d..a14a50d2 100644 --- a/tests/phpunit/includes/changes/EnhancedChangesListTest.php +++ b/tests/phpunit/includes/changes/EnhancedChangesListTest.php @@ -5,7 +5,6 @@ * * @group Database * - * @licence GNU GPL v2+ * @author Katie Filbert < aude.wiki@gmail.com > */ class EnhancedChangesListTest extends MediaWikiLangTestCase { @@ -31,7 +30,7 @@ class EnhancedChangesListTest extends MediaWikiLangTestCase { 'mediawiki.special.changeslist', $styleModules, 'has mediawiki.special.changeslist' - ); + ); $this->assertContains( 'mediawiki.special.changeslist.enhanced', @@ -75,10 +74,10 @@ class EnhancedChangesListTest extends MediaWikiLangTestCase { $this->assertEquals( '', $html ); } - /** - * @todo more tests for actual formatting, this is more of a smoke test - */ - public function testEndRecentChangesList() { + /** + * @todo more tests for actual formatting, this is more of a smoke test + */ + public function testEndRecentChangesList() { $enhancedChangesList = $this->newEnhancedChangesList(); $enhancedChangesList->beginRecentChangesList(); @@ -92,7 +91,7 @@ class EnhancedChangesListTest extends MediaWikiLangTestCase { preg_match_all( '/td class="mw-enhanced-rc-nested"/', $html, $matches ); $this->assertCount( 2, $matches[0] ); - } + } /** * @return EnhancedChangesList diff --git a/tests/phpunit/includes/changes/OldChangesListTest.php b/tests/phpunit/includes/changes/OldChangesListTest.php index 2ea9f33e..311ad89c 100644 --- a/tests/phpunit/includes/changes/OldChangesListTest.php +++ b/tests/phpunit/includes/changes/OldChangesListTest.php @@ -9,7 +9,6 @@ * * @group Database * - * @licence GNU GPL v2+ * @author Katie Filbert < aude.wiki@gmail.com > */ class OldChangesListTest extends MediaWikiLangTestCase { diff --git a/tests/phpunit/includes/changes/RCCacheEntryFactoryTest.php b/tests/phpunit/includes/changes/RCCacheEntryFactoryTest.php index ee1a4d0e..0b877275 100644 --- a/tests/phpunit/includes/changes/RCCacheEntryFactoryTest.php +++ b/tests/phpunit/includes/changes/RCCacheEntryFactoryTest.php @@ -5,7 +5,6 @@ * * @group Database * - * @licence GNU GPL v2+ * @author Katie Filbert < aude.wiki@gmail.com > */ class RCCacheEntryFactoryTest extends MediaWikiLangTestCase { diff --git a/tests/phpunit/includes/changes/RecentChangeTest.php b/tests/phpunit/includes/changes/RecentChangeTest.php index 98903f1e..b3cb7b52 100644 --- a/tests/phpunit/includes/changes/RecentChangeTest.php +++ b/tests/phpunit/includes/changes/RecentChangeTest.php @@ -35,6 +35,7 @@ class RecentChangeTest extends MediaWikiTestCase { * Should cover the following log actions (which are most commonly used by bots): * - block/block * - block/unblock + * - block/reblock * - delete/delete * - delete/restore * - newusers/create @@ -46,6 +47,9 @@ class RecentChangeTest extends MediaWikiTestCase { * - protect/modifyprotect * - protect/unprotect * - upload/upload + * - merge/merge + * - import/upload + * - import/interwiki * * As well as the following Auto Edit Summaries: * - blank @@ -62,9 +66,13 @@ class RecentChangeTest extends MediaWikiTestCase { # block/block $this->assertIRCComment( - $this->context->msg( 'blocklogentry', 'SomeTitle' )->plain() . $sep . $this->user_comment, + $this->context->msg( 'blocklogentry', 'SomeTitle', 'duration', '(flags)' )->plain() + . $sep . $this->user_comment, 'block', 'block', - array(), + array( + '5::duration' => 'duration', + '6::flags' => 'flags', + ), $this->user_comment ); # block/unblock @@ -74,6 +82,17 @@ class RecentChangeTest extends MediaWikiTestCase { array(), $this->user_comment ); + # block/reblock + $this->assertIRCComment( + $this->context->msg( 'reblock-logentry', 'SomeTitle', 'duration', '(flags)' )->plain() + . $sep . $this->user_comment, + 'block', 'reblock', + array( + '5::duration' => 'duration', + '6::flags' => 'flags', + ), + $this->user_comment + ); } /** @@ -229,6 +248,48 @@ class RecentChangeTest extends MediaWikiTestCase { ); } + /** + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeMerge() { + $sep = $this->context->msg( 'colon-separator' )->text(); + + # merge/merge + $this->assertIRCComment( + $this->context->msg( 'pagemerge-logentry', 'SomeTitle', 'Dest', 'timestamp' )->plain() + . $sep . $this->user_comment, + 'merge', 'merge', + array( + '4::dest' => 'Dest', + '5::mergepoint' => 'timestamp', + ), + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeImport() { + $sep = $this->context->msg( 'colon-separator' )->text(); + + # import/upload + $this->assertIRCComment( + $this->context->msg( 'import-logentry-upload', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'import', 'upload', + array(), + $this->user_comment + ); + + # import/interwiki + $this->assertIRCComment( + $this->context->msg( 'import-logentry-interwiki', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'import', 'interwiki', + array(), + $this->user_comment + ); + } + /** * @todo Emulate these edits somehow and extract * raw edit summary from RecentChange object diff --git a/tests/phpunit/includes/changes/TestRecentChangesHelper.php b/tests/phpunit/includes/changes/TestRecentChangesHelper.php index ad643274..2506087b 100644 --- a/tests/phpunit/includes/changes/TestRecentChangesHelper.php +++ b/tests/phpunit/includes/changes/TestRecentChangesHelper.php @@ -3,7 +3,6 @@ /** * Helper for generating test recent changes entries. * - * @licence GNU GPL v2+ * @author Katie Filbert < aude.wiki@gmail.com > */ class TestRecentChangesHelper { diff --git a/tests/phpunit/includes/composer/ComposerVersionNormalizerTest.php b/tests/phpunit/includes/composer/ComposerVersionNormalizerTest.php index 3f887dc0..2fa11eaf 100644 --- a/tests/phpunit/includes/composer/ComposerVersionNormalizerTest.php +++ b/tests/phpunit/includes/composer/ComposerVersionNormalizerTest.php @@ -5,7 +5,6 @@ * * @group ComposerHooks * - * @licence GNU GPL v2+ * @author Jeroen De Dauw < jeroendedauw@gmail.com > */ class ComposerVersionNormalizerTest extends PHPUnit_Framework_TestCase { diff --git a/tests/phpunit/includes/config/GlobalVarConfigTest.php b/tests/phpunit/includes/config/GlobalVarConfigTest.php index 70b9e684..28068d5e 100644 --- a/tests/phpunit/includes/config/GlobalVarConfigTest.php +++ b/tests/phpunit/includes/config/GlobalVarConfigTest.php @@ -87,34 +87,10 @@ class GlobalVarConfigTest extends MediaWikiTestCase { $this->assertEquals( $config->get( $name ), $expected ); } - public static function provideSet() { - return array( - array( 'Foo', 'wg', 'wgFoo' ), - array( 'SomethingRandom', 'wg', 'wgSomethingRandom' ), - array( 'FromAnExtension', 'eg', 'egFromAnExtension' ), - array( 'NoPrefixHere', '', 'NoPrefixHere' ), - ); - } - private function maybeStashGlobal( $var ) { if ( array_key_exists( $var, $GLOBALS ) ) { // Will be reset after this test is over $this->stashMwGlobals( $var ); } } - - /** - * @dataProvider provideSet - * @covers GlobalVarConfig::set - * @covers GlobalVarConfig::setWithPrefix - */ - public function testSet( $name, $prefix, $var ) { - $this->hideDeprecated( 'GlobalVarConfig::set' ); - $this->maybeStashGlobal( $var ); - $config = new GlobalVarConfig( $prefix ); - $random = wfRandomString(); - $config->set( $name, $random ); - $this->assertArrayHasKey( $var, $GLOBALS ); - $this->assertEquals( $random, $GLOBALS[$var] ); - } } diff --git a/tests/phpunit/includes/config/HashConfigTest.php b/tests/phpunit/includes/config/HashConfigTest.php index 3ad3bfbd..06973b09 100644 --- a/tests/phpunit/includes/config/HashConfigTest.php +++ b/tests/phpunit/includes/config/HashConfigTest.php @@ -60,4 +60,4 @@ class HashConfigTest extends MediaWikiTestCase { $conf->set( 'one', '3' ); $this->assertEquals( '3', $conf->get( 'one' ) ); } -} \ No newline at end of file +} diff --git a/tests/phpunit/includes/content/ContentHandlerTest.php b/tests/phpunit/includes/content/ContentHandlerTest.php index f7449734..988a59ee 100644 --- a/tests/phpunit/includes/content/ContentHandlerTest.php +++ b/tests/phpunit/includes/content/ContentHandlerTest.php @@ -2,11 +2,6 @@ /** * @group ContentHandler - * @group Database - * - * @note Declare that we are using the database, because otherwise we'll fail in - * the "databaseless" test run. This is because the LinkHolderArray used by the - * parser needs database access. */ class ContentHandlerTest extends MediaWikiTestCase { @@ -36,6 +31,8 @@ class ContentHandlerTest extends MediaWikiTestCase { // Reset namespace cache MWNamespace::getCanonicalNamespaces( true ); $wgContLang->resetNamespaces(); + // And LinkCache + LinkCache::destroySingleton(); } protected function tearDown() { @@ -44,6 +41,8 @@ class ContentHandlerTest extends MediaWikiTestCase { // Reset namespace cache MWNamespace::getCanonicalNamespaces( true ); $wgContLang->resetNamespaces(); + // And LinkCache + LinkCache::destroySingleton(); parent::tearDown(); } @@ -83,6 +82,7 @@ class ContentHandlerTest extends MediaWikiTestCase { */ public function testGetForTitle( $title, $expectedContentModel ) { $title = Title::newFromText( $title ); + LinkCache::singleton()->addBadLinkObj( $title ); $handler = ContentHandler::getForTitle( $title ); $this->assertEquals( $expectedContentModel, $handler->getModelID() ); } @@ -139,6 +139,7 @@ class ContentHandlerTest extends MediaWikiTestCase { public function testGetPageLanguage( $title, $expected ) { if ( is_string( $title ) ) { $title = Title::newFromText( $title ); + LinkCache::singleton()->addBadLinkObj( $title ); } $expected = wfGetLangObj( $expected ); @@ -292,7 +293,7 @@ class ContentHandlerTest extends MediaWikiTestCase { $expectedModelId, $expectedNativeData, $shouldFail ) { $title = Title::newFromText( $title ); - + LinkCache::singleton()->addBadLinkObj( $title ); try { $content = ContentHandler::makeContent( $data, $title, $modelId, $format ); @@ -317,6 +318,8 @@ class ContentHandlerTest extends MediaWikiTestCase { * page. */ public function testGetAutosummary() { + $this->setMwGlobals( 'wgContLang', Language::factory( 'en' ) ); + $content = new DummyContentHandlerForTesting( CONTENT_MODEL_WIKITEXT ); $title = Title::newFromText( 'Help:Test' ); // Create a new content object with no content diff --git a/tests/phpunit/includes/content/JsonContentTest.php b/tests/phpunit/includes/content/JsonContentTest.php index 77b542f4..cccfe7b1 100644 --- a/tests/phpunit/includes/content/JsonContentTest.php +++ b/tests/phpunit/includes/content/JsonContentTest.php @@ -6,48 +6,85 @@ */ class JsonContentTest extends MediaWikiLangTestCase { - /** - * @dataProvider provideValidConstruction - */ - public function testValidConstruct( $text, $modelId, $isValid, $expected ) { - $obj = new JsonContent( $text, $modelId ); - $this->assertEquals( $isValid, $obj->isValid() ); - $this->assertEquals( $expected, $obj->getJsonData() ); + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( 'wgWellFormedXml', true ); } public static function provideValidConstruction() { return array( - array( 'foo', CONTENT_MODEL_JSON, false, null ), - array( FormatJson::encode( array() ), CONTENT_MODEL_JSON, true, array() ), - array( FormatJson::encode( array( 'foo' ) ), CONTENT_MODEL_JSON, true, array( 'foo' ) ), + array( 'foo', false, null ), + array( '[]', true, array() ), + array( '{}', true, (object)array() ), + array( '""', true, '' ), + array( '"0"', true, '0' ), + array( '"bar"', true, 'bar' ), + array( '0', true, '0' ), + array( '{ "0": "bar" }', true, (object)array( 'bar' ) ), ); } /** - * @dataProvider provideDataToEncode + * @dataProvider provideValidConstruction */ - public function testBeautifyUsesFormatJson( $data ) { - $obj = new JsonContent( FormatJson::encode( $data ) ); - $this->assertEquals( FormatJson::encode( $data, true ), $obj->beautifyJSON() ); + public function testIsValid( $text, $isValid, $expected ) { + $obj = new JsonContent( $text, CONTENT_MODEL_JSON ); + $this->assertEquals( $isValid, $obj->isValid() ); + $this->assertEquals( $expected, $obj->getData()->getValue() ); } public static function provideDataToEncode() { return array( - array( array() ), - array( array( 'foo' ) ), - array( array( 'foo', 'bar' ) ), - array( array( 'baz' => 'foo', 'bar' ) ), - array( array( 'baz' => 1000, 'bar' ) ), + array( + // Round-trip empty array + '[]', + '[]', + ), + array( + // Round-trip empty object + '{}', + '{}', + ), + array( + // Round-trip empty array/object (nested) + '{ "foo": {}, "bar": [] }', + "{\n \"foo\": {},\n \"bar\": []\n}", + ), + array( + '{ "foo": "bar" }', + "{\n \"foo\": \"bar\"\n}", + ), + array( + '{ "foo": 1000 }', + "{\n \"foo\": 1000\n}", + ), + array( + '{ "foo": 1000, "0": "bar" }', + "{\n \"foo\": 1000,\n \"0\": \"bar\"\n}", + ), ); } /** * @dataProvider provideDataToEncode */ - public function testPreSaveTransform( $data ) { - $obj = new JsonContent( FormatJson::encode( $data ) ); - $newObj = $obj->preSaveTransform( $this->getMockTitle(), $this->getMockUser(), $this->getMockParserOptions() ); - $this->assertTrue( $newObj->equals( new JsonContent( FormatJson::encode( $data, true ) ) ) ); + public function testBeautifyJson( $input, $beautified ) { + $obj = new JsonContent( $input ); + $this->assertEquals( $beautified, $obj->beautifyJSON() ); + } + + /** + * @dataProvider provideDataToEncode + */ + public function testPreSaveTransform( $input, $transformed ) { + $obj = new JsonContent( $input ); + $newObj = $obj->preSaveTransform( + $this->getMockTitle(), + $this->getMockUser(), + $this->getMockParserOptions() + ); + $this->assertTrue( $newObj->equals( new JsonContent( $transformed ) ) ); } private function getMockTitle() { @@ -67,48 +104,55 @@ class JsonContentTest extends MediaWikiLangTestCase { ->getMock(); } - /** - * @dataProvider provideDataAndParserText - */ - public function testFillParserOutput( $data, $expected ) { - $obj = new JsonContent( FormatJson::encode( $data ) ); - $parserOutput = $obj->getParserOutput( $this->getMockTitle(), null, null, true ); - $this->assertInstanceOf( 'ParserOutput', $parserOutput ); - $this->assertEquals( $expected, $parserOutput->getText() ); - } - public static function provideDataAndParserText() { return array( array( array(), - '
' + '
' . + '' + . '
Empty array
' ), array( - array( 'foo' ), - '
0"foo"
' + (object)array(), + '' . + '
Empty object
' ), array( - array( 'foo', 'bar' ), - '' . - "\n" . - '
0"foo"
1"bar"
' + (object)array( 'foo' ), + '' . + '
0"foo"
' ), array( - array( 'baz' => 'foo', 'bar' ), - '' . - "\n" . - '
baz"foo"
0"bar"
' + (object)array( 'foo', 'bar' ), + '' . + '
0"foo"
1"bar"
' ), array( - array( 'baz' => 1000, 'bar' ), + (object)array( 'baz' => 'foo', 'bar' ), + '' . + '
baz"foo"
0"bar"
' + ), + array( + (object)array( 'baz' => 1000, 'bar' ), '' . - "\n" . - '
baz1000
0"bar"
' + '0"bar"' ), array( - array( ''), - '
0"<script>alert("evil!")</script>"
', + (object)array( ''), + '
0"' . + '<script>alert("evil!")</script>"' . + '
', ), ); } + + /** + * @dataProvider provideDataAndParserText + */ + public function testFillParserOutput( $data, $expected ) { + $obj = new JsonContent( FormatJson::encode( $data ) ); + $parserOutput = $obj->getParserOutput( $this->getMockTitle(), null, null, true ); + $this->assertInstanceOf( 'ParserOutput', $parserOutput ); + $this->assertEquals( $expected, $parserOutput->getText() ); + } } diff --git a/tests/phpunit/includes/content/TextContentTest.php b/tests/phpunit/includes/content/TextContentTest.php index 2f811094..dd61f85b 100644 --- a/tests/phpunit/includes/content/TextContentTest.php +++ b/tests/phpunit/includes/content/TextContentTest.php @@ -7,11 +7,8 @@ */ class TextContentTest extends MediaWikiLangTestCase { protected $context; - protected $savedContentGetParserOutput; protected function setUp() { - global $wgHooks; - parent::setUp(); // Anon user @@ -32,24 +29,8 @@ class TextContentTest extends MediaWikiLangTestCase { 'wgUseTidy' => false, 'wgAlwaysUseTidy' => false, 'wgCapitalLinks' => true, + 'wgHooks' => array(), // bypass hook ContentGetParserOutput that force custom rendering ) ); - - // bypass hooks that force custom rendering - if ( isset( $wgHooks['ContentGetParserOutput'] ) ) { - $this->savedContentGetParserOutput = $wgHooks['ContentGetParserOutput']; - unset( $wgHooks['ContentGetParserOutput'] ); - } - } - - public function teardown() { - global $wgHooks; - - // restore hooks that force custom rendering - if ( $this->savedContentGetParserOutput !== null ) { - $wgHooks['ContentGetParserOutput'] = $this->savedContentGetParserOutput; - } - - parent::teardown(); } public function newContent( $text ) { diff --git a/tests/phpunit/includes/context/RequestContextTest.php b/tests/phpunit/includes/context/RequestContextTest.php new file mode 100644 index 00000000..a9e5be24 --- /dev/null +++ b/tests/phpunit/includes/context/RequestContextTest.php @@ -0,0 +1,96 @@ +setTitle( $curTitle ); + $this->assertTrue( $curTitle->equals( $context->getWikiPage()->getTitle() ), + "When a title is first set WikiPage should be created on-demand for that title." ); + + $curTitle = Title::newFromText( "B" ); + $context->setWikiPage( WikiPage::factory( $curTitle ) ); + $this->assertTrue( $curTitle->equals( $context->getTitle() ), + "Title must be updated when a new WikiPage is provided." ); + + $curTitle = Title::newFromText( "C" ); + $context->setTitle( $curTitle ); + $this->assertTrue( + $curTitle->equals( $context->getWikiPage()->getTitle() ), + "When a title is updated the WikiPage should be purged " + . "and recreated on-demand with the new title." + ); + } + + /** + * @covers RequestContext::importScopedSession + */ + public function testImportScopedSession() { + $context = RequestContext::getMain(); + + $oInfo = $context->exportSession(); + $this->assertEquals( '127.0.0.1', $oInfo['ip'], "Correct initial IP address." ); + $this->assertEquals( 0, $oInfo['userId'], "Correct initial user ID." ); + + $user = User::newFromName( 'UnitTestContextUser' ); + $user->addToDatabase(); + + $sinfo = array( + 'sessionId' => 'd612ee607c87e749ef14da4983a702cd', + 'userId' => $user->getId(), + 'ip' => '192.0.2.0', + 'headers' => array( + 'USER-AGENT' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:18.0) Gecko/20100101 Firefox/18.0' + ) + ); + // importScopedSession() sets these variables + $this->setMwGlobals( array( + 'wgUser' => new User, + 'wgRequest' => new FauxRequest, + ) ); + $sc = RequestContext::importScopedSession( $sinfo ); // load new context + + $info = $context->exportSession(); + $this->assertEquals( $sinfo['ip'], $info['ip'], "Correct IP address." ); + $this->assertEquals( $sinfo['headers'], $info['headers'], "Correct headers." ); + $this->assertEquals( $sinfo['sessionId'], $info['sessionId'], "Correct session ID." ); + $this->assertEquals( $sinfo['userId'], $info['userId'], "Correct user ID." ); + $this->assertEquals( + $sinfo['ip'], + $context->getRequest()->getIP(), + "Correct context IP address." + ); + $this->assertEquals( + $sinfo['headers'], + $context->getRequest()->getAllHeaders(), + "Correct context headers." + ); + $this->assertEquals( $sinfo['sessionId'], session_id(), "Correct context session ID." ); + $this->assertEquals( true, $context->getUser()->isLoggedIn(), "Correct context user." ); + $this->assertEquals( $sinfo['userId'], $context->getUser()->getId(), "Correct context user ID." ); + $this->assertEquals( + 'UnitTestContextUser', + $context->getUser()->getName(), + "Correct context user name." + ); + + unset( $sc ); // restore previous context + + $info = $context->exportSession(); + $this->assertEquals( $oInfo['ip'], $info['ip'], "Correct restored IP address." ); + $this->assertEquals( $oInfo['headers'], $info['headers'], "Correct restored headers." ); + $this->assertEquals( $oInfo['sessionId'], $info['sessionId'], "Correct restored session ID." ); + $this->assertEquals( $oInfo['userId'], $info['userId'], "Correct restored user ID." ); + } +} diff --git a/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php b/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php index 55e48d13..b4292a60 100644 --- a/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php +++ b/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php @@ -2,7 +2,6 @@ /** * Holds tests for DatabaseMysqlBase MediaWiki class. * - * @section LICENSE * 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 diff --git a/tests/phpunit/includes/db/DatabaseSQLTest.php b/tests/phpunit/includes/db/DatabaseSQLTest.php index 5c2d4b70..b13751f3 100644 --- a/tests/phpunit/includes/db/DatabaseSQLTest.php +++ b/tests/phpunit/includes/db/DatabaseSQLTest.php @@ -722,4 +722,84 @@ class DatabaseSQLTest extends MediaWikiTestCase { $this->database->dropTable( 'non_existing', __METHOD__ ) ); } + + /** + * @dataProvider provideMakeList + * @covers DatabaseBase::makeList + */ + public function testMakeList( $list, $mode, $sqlText ) { + $this->assertEquals( trim( $this->database->makeList( + $list, $mode + ) ), $sqlText ); + } + + public static function provideMakeList() { + return array( + array( + array( 'value', 'value2' ), + LIST_COMMA, + "'value','value2'" + ), + array( + array( 'field', 'field2' ), + LIST_NAMES, + "field,field2" + ), + array( + array( 'field' => 'value', 'field2' => 'value2' ), + LIST_AND, + "field = 'value' AND field2 = 'value2'" + ), + array( + array( 'field' => null, "field2 != 'value2'" ), + LIST_AND, + "field IS NULL AND (field2 != 'value2')" + ), + array( + array( 'field' => array( 'value', null, 'value2' ), 'field2' => 'value2' ), + LIST_AND, + "(field IN ('value','value2') OR field IS NULL) AND field2 = 'value2'" + ), + array( + array( 'field' => array( null ), 'field2' => null ), + LIST_AND, + "field IS NULL AND field2 IS NULL" + ), + array( + array( 'field' => 'value', 'field2' => 'value2' ), + LIST_OR, + "field = 'value' OR field2 = 'value2'" + ), + array( + array( 'field' => 'value', 'field2' => null ), + LIST_OR, + "field = 'value' OR field2 IS NULL" + ), + array( + array( 'field' => array( 'value', 'value2' ), 'field2' => array( 'value' ) ), + LIST_OR, + "field IN ('value','value2') OR field2 = 'value'" + ), + array( + array( 'field' => array( null, 'value', null, 'value2' ), "field2 != 'value2'" ), + LIST_OR, + "(field IN ('value','value2') OR field IS NULL) OR (field2 != 'value2')" + ), + array( + array( 'field' => 'value', 'field2' => 'value2' ), + LIST_SET, + "field = 'value',field2 = 'value2'" + ), + array( + array( 'field' => 'value', 'field2' => null ), + LIST_SET, + "field = 'value',field2 = NULL" + ), + array( + array( 'field' => 'value', "field2 != 'value2'" ), + LIST_SET, + "field = 'value',field2 != 'value2'" + ), + ); + } } diff --git a/tests/phpunit/includes/db/DatabaseSqliteTest.php b/tests/phpunit/includes/db/DatabaseSqliteTest.php index 98b4ca04..645baf1f 100644 --- a/tests/phpunit/includes/db/DatabaseSqliteTest.php +++ b/tests/phpunit/includes/db/DatabaseSqliteTest.php @@ -1,10 +1,12 @@ markTestSkipped( 'No SQLite support detected' ); } - $this->db = new MockDatabaseSqlite(); + $this->db = MockDatabaseSqlite::newInstance(); if ( version_compare( $this->db->getServerVersion(), '3.6.0', '<' ) ) { $this->markTestSkipped( "SQLite at least 3.6 required, {$this->db->getServerVersion()} found" ); } @@ -89,7 +91,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { */ public function testAddQuotes( $value, $expected ) { // check quoting - $db = new DatabaseSqliteStandalone( ':memory:' ); + $db = DatabaseSqlite::newStandaloneInstance( ':memory:' ); $this->assertEquals( $expected, $db->addQuotes( $value ), 'string not quoted as expected' ); // ok, quoting works as expected, now try a round trip. @@ -172,7 +174,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { */ public function testTableName() { // @todo Moar! - $db = new DatabaseSqliteStandalone( ':memory:' ); + $db = DatabaseSqlite::newStandaloneInstance( ':memory:' ); $this->assertEquals( 'foo', $db->tableName( 'foo' ) ); $this->assertEquals( 'sqlite_master', $db->tableName( 'sqlite_master' ) ); $db->tablePrefix( 'foo' ); @@ -184,7 +186,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { * @covers DatabaseSqlite::duplicateTableStructure */ public function testDuplicateTableStructure() { - $db = new DatabaseSqliteStandalone( ':memory:' ); + $db = DatabaseSqlite::newStandaloneInstance( ':memory:' ); $db->query( 'CREATE TABLE foo(foo, barfoo)' ); $db->duplicateTableStructure( 'foo', 'bar' ); @@ -208,7 +210,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { * @covers DatabaseSqlite::duplicateTableStructure */ public function testDuplicateTableStructureVirtual() { - $db = new DatabaseSqliteStandalone( ':memory:' ); + $db = DatabaseSqlite::newStandaloneInstance( ':memory:' ); if ( $db->getFulltextSearchModule() != 'FTS3' ) { $this->markTestSkipped( 'FTS3 not supported, cannot create virtual tables' ); } @@ -231,7 +233,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { * @covers DatabaseSqlite::deleteJoin */ public function testDeleteJoin() { - $db = new DatabaseSqliteStandalone( ':memory:' ); + $db = DatabaseSqlite::newStandaloneInstance( ':memory:' ); $db->query( 'CREATE TABLE a (a_1)', __METHOD__ ); $db->query( 'CREATE TABLE b (b_1, b_2)', __METHOD__ ); $db->insert( 'a', array( @@ -272,7 +274,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { * @todo Currently only checks list of tables */ public function testUpgrades() { - global $IP, $wgVersion, $wgProfileToDatabase; + global $IP, $wgVersion, $wgProfiler; // Versions tested $versions = array( @@ -289,9 +291,20 @@ class DatabaseSqliteTest extends MediaWikiTestCase { 'user_newtalk.user_last_timestamp', // r84185 ); - $currentDB = new DatabaseSqliteStandalone( ':memory:' ); + $currentDB = DatabaseSqlite::newStandaloneInstance( ':memory:' ); $currentDB->sourceFile( "$IP/maintenance/tables.sql" ); - if ( $wgProfileToDatabase ) { + + $profileToDb = false; + if ( isset( $wgProfiler['output'] ) ) { + $out = $wgProfiler['output']; + if ( $out === 'db' ) { + $profileToDb = true; + } elseif ( is_array( $out ) && in_array( 'db', $out ) ) { + $profileToDb = true; + } + } + + if ( $profileToDb ) { $currentDB->sourceFile( "$IP/maintenance/sqlite/archives/patch-profiling.sql" ); } $currentTables = $this->getTables( $currentDB ); @@ -346,7 +359,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { * @covers DatabaseSqlite::insertId */ public function testInsertIdType() { - $db = new DatabaseSqliteStandalone( ':memory:' ); + $db = DatabaseSqlite::newStandaloneInstance( ':memory:' ); $databaseCreation = $db->query( 'CREATE TABLE a ( a_1 )', __METHOD__ ); $this->assertInstanceOf( 'ResultWrapper', $databaseCreation, "Database creation" ); @@ -366,7 +379,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { } global $IP; - $db = new DatabaseSqliteStandalone( ':memory:' ); + $db = DatabaseSqlite::newStandaloneInstance( ':memory:' ); $db->sourceFile( "$IP/tests/phpunit/data/db/sqlite/tables-$version.sql" ); $updater = DatabaseUpdater::newForDB( $db, false, $maint ); $updater->doUpdates( array( 'core' ) ); @@ -429,7 +442,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { public function testCaseInsensitiveLike() { // TODO: Test this for all databases - $db = new DatabaseSqliteStandalone( ':memory:' ); + $db = DatabaseSqlite::newStandaloneInstance( ':memory:' ); $res = $db->query( 'SELECT "a" LIKE "A" AS a' ); $row = $res->fetchRow(); $this->assertFalse( (bool)$row['a'] ); @@ -439,7 +452,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { * @covers DatabaseSqlite::numFields */ public function testNumFields() { - $db = new DatabaseSqliteStandalone( ':memory:' ); + $db = DatabaseSqlite::newStandaloneInstance( ':memory:' ); $databaseCreation = $db->query( 'CREATE TABLE a ( a_1 )', __METHOD__ ); $this->assertInstanceOf( 'ResultWrapper', $databaseCreation, "Failed to create table a" ); diff --git a/tests/phpunit/includes/db/LBFactoryTest.php b/tests/phpunit/includes/db/LBFactoryTest.php index 4c59f474..81d6840b 100644 --- a/tests/phpunit/includes/db/LBFactoryTest.php +++ b/tests/phpunit/includes/db/LBFactoryTest.php @@ -2,7 +2,6 @@ /** * Holds tests for LBFactory abstract MediaWiki class. * - * @section LICENSE * 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 diff --git a/tests/phpunit/includes/db/ORMRowTest.php b/tests/phpunit/includes/db/ORMRowTest.php index 447bf219..807bd14e 100644 --- a/tests/phpunit/includes/db/ORMRowTest.php +++ b/tests/phpunit/includes/db/ORMRowTest.php @@ -34,7 +34,6 @@ * that hold the first tests in a pending state awaiting access to the database. * @group medium * - * @licence GNU GPL v2+ * @author Jeroen De Dauw < jeroendedauw@gmail.com > */ abstract class ORMRowTest extends \MediaWikiTestCase { diff --git a/tests/phpunit/includes/db/ORMTableTest.php b/tests/phpunit/includes/db/ORMTableTest.php index 7171ee59..338d931f 100644 --- a/tests/phpunit/includes/db/ORMTableTest.php +++ b/tests/phpunit/includes/db/ORMTableTest.php @@ -25,14 +25,12 @@ * @group ORM * @group Database * - * @licence GNU GPL v2+ + * @covers PageORMTableForTesting + * * @author Jeroen De Dauw < jeroendedauw@gmail.com > * @author Daniel Kinzler */ -/** - * @covers PageORMTableForTesting - */ class ORMTableTest extends MediaWikiTestCase { /** @@ -99,6 +97,10 @@ class ORMTableTest extends MediaWikiTestCase { class PageORMTableForTesting extends ORMTable { + public function __construct() { + $this->fieldPrefix = 'page_'; + } + /** * @see ORMTable::getName * @@ -138,13 +140,4 @@ class PageORMTableForTesting extends ORMTable { 'title' => 'str', ); } - - /** - * @see ORMTable::getFieldPrefix - * - * @return string - */ - protected function getFieldPrefix() { - return 'page_'; - } } diff --git a/tests/phpunit/includes/db/TestORMRowTest.php b/tests/phpunit/includes/db/TestORMRowTest.php index c9459c90..04bb9f38 100644 --- a/tests/phpunit/includes/db/TestORMRowTest.php +++ b/tests/phpunit/includes/db/TestORMRowTest.php @@ -20,27 +20,23 @@ * http://www.gnu.org/copyleft/gpl.html * * @file - * @since 1.20 - * * @ingroup Test - * - * @group ORM - * + * @author Jeroen De Dauw < jeroendedauw@gmail.com > + */ + +/** * The database group has as a side effect that temporal database tables are created. This makes * it possible to test without poisoning a production database. - * @group Database * * Some of the tests takes more time, and needs therefor longer time before they can be aborted * as non-functional. The reason why tests are aborted is assumed to be set up of temporal databases * that hold the first tests in a pending state awaiting access to the database. - * @group medium * - * @licence GNU GPL v2+ - * @author Jeroen De Dauw < jeroendedauw@gmail.com > - */ -require_once __DIR__ . "/ORMRowTest.php"; - -/** + * @since 1.20 + * + * @group ORM + * @group Database + * @group medium * @covers TestORMRow */ class TestORMRowTest extends ORMRowTest { @@ -150,6 +146,10 @@ class TestORMRow extends ORMRow { class TestORMTable extends ORMTable { + public function __construct() { + $this->fieldPrefix = 'test_'; + } + /** * Returns the name of the database table objects of this type are stored in. * @@ -204,15 +204,4 @@ class TestORMTable extends ORMTable { 'time' => 'str', // TS_MW ); } - - /** - * Gets the db field prefix. - * - * @since 1.20 - * - * @return string - */ - protected function getFieldPrefix() { - return 'test_'; - } } diff --git a/tests/phpunit/includes/debug/MWDebugTest.php b/tests/phpunit/includes/debug/MWDebugTest.php index 6e41de75..1abb47e7 100644 --- a/tests/phpunit/includes/debug/MWDebugTest.php +++ b/tests/phpunit/includes/debug/MWDebugTest.php @@ -96,16 +96,15 @@ class MWDebugTest extends MediaWikiTestCase { $apiMain = new ApiMain( $context ); $result = new ApiResult( $apiMain ); - $result->setRawMode( true ); MWDebug::appendDebugInfoToApiResult( $context, $result ); $this->assertInstanceOf( 'ApiResult', $result ); - $data = $result->getData(); + $data = $result->getResultData(); $expectedKeys = array( 'mwVersion', 'phpEngine', 'phpVersion', 'gitRevision', 'gitBranch', 'gitViewUrl', 'time', 'log', 'debugLog', 'queries', 'request', 'memory', - 'memoryPeak', 'includes', 'profile', '_element' ); + 'memoryPeak', 'includes', '_element' ); foreach ( $expectedKeys as $expectedKey ) { $this->assertArrayHasKey( $expectedKey, $data['debuginfo'], "debuginfo has $expectedKey" ); diff --git a/tests/phpunit/includes/debug/logging/LegacyLoggerTest.php b/tests/phpunit/includes/debug/logging/LegacyLoggerTest.php new file mode 100644 index 00000000..415fa045 --- /dev/null +++ b/tests/phpunit/includes/debug/logging/LegacyLoggerTest.php @@ -0,0 +1,122 @@ +assertEquals( + $expect, LegacyLogger::interpolate( $message, $context ) ); + } + + public function provideInterpolate() { + return array( + array( + 'no-op', + array(), + 'no-op', + ), + array( + 'Hello {world}!', + array( + 'world' => 'World', + ), + 'Hello World!', + ), + array( + '{greeting} {user}', + array( + 'greeting' => 'Goodnight', + 'user' => 'Moon', + ), + 'Goodnight Moon', + ), + array( + 'Oops {key_not_set}', + array(), + 'Oops {key_not_set}', + ), + array( + '{ not interpolated }', + array( + 'not interpolated' => 'This should NOT show up in the message', + ), + '{ not interpolated }', + ), + ); + } + + /** + * @covers LegacyLogger::shouldEmit + * @dataProvider provideShouldEmit + */ + public function testShouldEmit( $level, $config, $expected ) { + $this->setMwGlobals( 'wgDebugLogGroups', array( 'fakechannel' => $config ) ); + $this->assertEquals( + $expected, + LegacyLogger::shouldEmit( 'fakechannel', 'some message', $level, array() ) + ); + } + + public static function provideShouldEmit() { + $dest = array( 'destination' => 'foobar' ); + $tests = array( + array( + LogLevel::DEBUG, + $dest, + true + ), + array( + LogLevel::WARNING, + $dest + array( 'level' => LogLevel::INFO ), + true, + ), + array( + LogLevel::INFO, + $dest + array( 'level' => LogLevel::CRITICAL ), + false, + ), + ); + + if ( class_exists( '\Monolog\Logger' ) ) { + $tests[] = array( + \Monolog\Logger::INFO, + $dest + array( 'level' => LogLevel::INFO ), + true, + ); + $tests[] = array( + \Monolog\Logger::WARNING, + $dest + array( 'level' => LogLevel::EMERGENCY ), + false, + ); + } + + return $tests; + } + +} diff --git a/tests/phpunit/includes/deferred/LinksUpdateTest.php b/tests/phpunit/includes/deferred/LinksUpdateTest.php new file mode 100644 index 00000000..02f6b2ab --- /dev/null +++ b/tests/phpunit/includes/deferred/LinksUpdateTest.php @@ -0,0 +1,266 @@ +tablesUsed = array_merge( $this->tablesUsed, + array( + 'interwiki', + 'page_props', + 'pagelinks', + 'categorylinks', + 'langlinks', + 'externallinks', + 'imagelinks', + 'templatelinks', + 'iwlinks' + ) + ); + } + + protected function setUp() { + parent::setUp(); + $dbw = wfGetDB( DB_MASTER ); + $dbw->replace( + 'interwiki', + array( 'iw_prefix' ), + array( + 'iw_prefix' => 'linksupdatetest', + 'iw_url' => 'http://testing.com/wiki/$1', + 'iw_api' => 'http://testing.com/w/api.php', + 'iw_local' => 0, + 'iw_trans' => 0, + 'iw_wikiid' => 'linksupdatetest', + ) + ); + } + + protected function makeTitleAndParserOutput( $name, $id ) { + $t = Title::newFromText( $name ); + $t->mArticleID = $id; # XXX: this is fugly + + $po = new ParserOutput(); + $po->setTitleText( $t->getPrefixedText() ); + + return array( $t, $po ); + } + + /** + * @covers ParserOutput::addLink + */ + public function testUpdate_pagelinks() { + /** @var ParserOutput $po */ + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + + $po->addLink( Title::newFromText( "Foo" ) ); + $po->addLink( Title::newFromText( "Special:Foo" ) ); // special namespace should be ignored + $po->addLink( Title::newFromText( "linksupdatetest:Foo" ) ); // interwiki link should be ignored + $po->addLink( Title::newFromText( "#Foo" ) ); // hash link should be ignored + + $update = $this->assertLinksUpdate( + $t, + $po, + 'pagelinks', + 'pl_namespace, + pl_title', + 'pl_from = 111', + array( array( NS_MAIN, 'Foo' ) ) + ); + $this->assertArrayEquals( array( + Title::makeTitle( NS_MAIN, 'Foo' ), // newFromText doesn't yield the same internal state.... + ), $update->getAddedLinks() ); + + $po = new ParserOutput(); + $po->setTitleText( $t->getPrefixedText() ); + + $po->addLink( Title::newFromText( "Bar" ) ); + $po->addLink( Title::newFromText( "Talk:Bar" ) ); + + $update = $this->assertLinksUpdate( + $t, + $po, + 'pagelinks', + 'pl_namespace, + pl_title', + 'pl_from = 111', + array( + array( NS_MAIN, 'Bar' ), + array( NS_TALK, 'Bar' ), + ) + ); + $this->assertArrayEquals( array( + Title::makeTitle( NS_MAIN, 'Bar' ), + Title::makeTitle( NS_TALK, 'Bar' ), + ), $update->getAddedLinks() ); + $this->assertArrayEquals( array( + Title::makeTitle( NS_MAIN, 'Foo' ), + ), $update->getRemovedLinks() ); + } + + /** + * @covers ParserOutput::addExternalLink + */ + public function testUpdate_externallinks() { + /** @var ParserOutput $po */ + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + + $po->addExternalLink( "http://testing.com/wiki/Foo" ); + + $this->assertLinksUpdate( $t, $po, 'externallinks', 'el_to, el_index', 'el_from = 111', array( + array( 'http://testing.com/wiki/Foo', 'http://com.testing./wiki/Foo' ), + ) ); + } + + /** + * @covers ParserOutput::addCategory + */ + public function testUpdate_categorylinks() { + /** @var ParserOutput $po */ + $this->setMwGlobals( 'wgCategoryCollation', 'uppercase' ); + + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + + $po->addCategory( "Foo", "FOO" ); + + $this->assertLinksUpdate( $t, $po, 'categorylinks', 'cl_to, cl_sortkey', 'cl_from = 111', array( + array( 'Foo', "FOO\nTESTING" ), + ) ); + } + + /** + * @covers ParserOutput::addInterwikiLink + */ + public function testUpdate_iwlinks() { + /** @var ParserOutput $po */ + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + + $target = Title::makeTitleSafe( NS_MAIN, "Foo", '', 'linksupdatetest' ); + $po->addInterwikiLink( $target ); + + $this->assertLinksUpdate( $t, $po, 'iwlinks', 'iwl_prefix, iwl_title', 'iwl_from = 111', array( + array( 'linksupdatetest', 'Foo' ), + ) ); + } + + /** + * @covers ParserOutput::addTemplate + */ + public function testUpdate_templatelinks() { + /** @var ParserOutput $po */ + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + + $po->addTemplate( Title::newFromText( "Template:Foo" ), 23, 42 ); + + $this->assertLinksUpdate( + $t, + $po, + 'templatelinks', + 'tl_namespace, + tl_title', + 'tl_from = 111', + array( array( NS_TEMPLATE, 'Foo' ) ) + ); + } + + /** + * @covers ParserOutput::addImage + */ + public function testUpdate_imagelinks() { + /** @var ParserOutput $po */ + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + + $po->addImage( "Foo.png" ); + + $this->assertLinksUpdate( $t, $po, 'imagelinks', 'il_to', 'il_from = 111', array( + array( 'Foo.png' ), + ) ); + } + + /** + * @covers ParserOutput::addLanguageLink + */ + public function testUpdate_langlinks() { + $this->setMwGlobals( array( + 'wgCapitalLinks' => true, + ) ); + + /** @var ParserOutput $po */ + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + + $po->addLanguageLink( Title::newFromText( "en:Foo" )->getFullText() ); + + $this->assertLinksUpdate( $t, $po, 'langlinks', 'll_lang, ll_title', 'll_from = 111', array( + array( 'En', 'Foo' ), + ) ); + } + + /** + * @covers ParserOutput::setProperty + */ + public function testUpdate_page_props() { + global $wgPagePropsHaveSortkey; + + /** @var ParserOutput $po */ + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); + + $fields = array( 'pp_propname', 'pp_value' ); + $expected = array(); + + $po->setProperty( "bool", true ); + $expected[] = array( "bool", true ); + + $po->setProperty( "float", 4.0 + 1.0 / 4.0 ); + $expected[] = array( "float", 4.0 + 1.0 / 4.0 ); + + $po->setProperty( "int", -7 ); + $expected[] = array( "int", -7 ); + + $po->setProperty( "string", "33 bar" ); + $expected[] = array( "string", "33 bar" ); + + // compute expected sortkey values + if ( $wgPagePropsHaveSortkey ) { + $fields[] = 'pp_sortkey'; + + foreach ( $expected as &$row ) { + $value = $row[1]; + + if ( is_int( $value ) || is_float( $value ) || is_bool( $value ) ) { + $row[] = floatval( $value ); + } else { + $row[] = null; + } + } + } + + $this->assertLinksUpdate( $t, $po, 'page_props', $fields, 'pp_page = 111', $expected ); + } + + public function testUpdate_page_props_without_sortkey() { + $this->setMwGlobals( 'wgPagePropsHaveSortkey', false ); + + $this->testUpdate_page_props(); + } + + // @todo test recursive, too! + + protected function assertLinksUpdate( Title $title, ParserOutput $parserOutput, + $table, $fields, $condition, array $expectedRows + ) { + $update = new LinksUpdate( $title, $parserOutput ); + + //NOTE: make sure LinksUpdate does not generate warnings when called inside a transaction. + $update->beginTransaction(); + $update->doUpdate(); + $update->commitTransaction(); + + $this->assertSelect( $table, $fields, $condition, $expectedRows ); + return $update; + } +} diff --git a/tests/phpunit/includes/deferred/SearchUpdateTest.php b/tests/phpunit/includes/deferred/SearchUpdateTest.php new file mode 100644 index 00000000..90438a04 --- /dev/null +++ b/tests/phpunit/includes/deferred/SearchUpdateTest.php @@ -0,0 +1,81 @@ +setMwGlobals( 'wgSearchType', 'MockSearch' ); + } + + public function updateText( $text ) { + return trim( SearchUpdate::updateText( $text ) ); + } + + /** + * @covers SearchUpdate::updateText + */ + public function testUpdateText() { + $this->assertEquals( + 'test', + $this->updateText( '
TeSt
' ), + 'HTML stripped, text lowercased' + ); + + $this->assertEquals( + 'foo bar boz quux', + $this->updateText( << +
foo
bar + bozquux + +EOT + ), 'Stripping HTML tables' ); + + $this->assertEquals( + 'a b', + $this->updateText( 'a > b' ), + 'Handle unclosed tags' + ); + + $text = str_pad( "foo assertNotEquals( + '', + $this->updateText( $text ), + 'Bug 18609' + ); + } + + /** + * @covers SearchUpdate::updateText + * Test bug 32712 + * Test if unicode quotes in article links make its search index empty + */ + public function testUnicodeLinkSearchIndexError() { + $text = "text „http://example.com“ text"; + $result = $this->updateText( $text ); + $processed = preg_replace( '/Q/u', 'Q', $result ); + $this->assertTrue( + $processed != '', + 'Link surrounded by unicode quotes should not fail UTF-8 validation' + ); + } +} diff --git a/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php b/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php index 188ad3fd..3bea9b31 100644 --- a/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php +++ b/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php @@ -1,7 +1,6 @@ */ class DifferenceEngineTest extends MediaWikiTestCase { diff --git a/tests/phpunit/includes/exception/BadTitleErrorTest.php b/tests/phpunit/includes/exception/BadTitleErrorTest.php index 003efd27..500b7e48 100644 --- a/tests/phpunit/includes/exception/BadTitleErrorTest.php +++ b/tests/phpunit/includes/exception/BadTitleErrorTest.php @@ -5,23 +5,8 @@ */ class BadTitleErrorTest extends MediaWikiTestCase { - protected $wgOut; - - protected function setUp() { - parent::setUp(); - global $wgOut; - $this->wgOut = clone $wgOut; - } - - protected function tearDown() { - parent::tearDown(); - global $wgOut; - $wgOut = $this->wgOut; - } - public function testExceptionSetsStatusCode() { - global $wgOut; - $wgOut = $this->getMockWgOut(); + $this->setMwGlobals( 'wgOut', $this->getMockWgOut() ); try { throw new BadTitleError(); } catch ( BadTitleError $e ) { diff --git a/tests/phpunit/includes/exception/ErrorPageErrorTest.php b/tests/phpunit/includes/exception/ErrorPageErrorTest.php index 13dcf33b..9c4e4a0b 100644 --- a/tests/phpunit/includes/exception/ErrorPageErrorTest.php +++ b/tests/phpunit/includes/exception/ErrorPageErrorTest.php @@ -6,20 +6,6 @@ */ class ErrorPageErrorTest extends MediaWikiTestCase { - private $wgOut; - - protected function setUp() { - parent::setUp(); - global $wgOut; - $this->wgOut = clone $wgOut; - } - - protected function tearDown() { - global $wgOut; - $wgOut = $this->wgOut; - parent::tearDown(); - } - private function getMockMessage() { $mockMessage = $this->getMockBuilder( 'Message' ) ->disableOriginalConstructor() @@ -48,20 +34,18 @@ class ErrorPageErrorTest extends MediaWikiTestCase { $title = 'Foo'; $params = array( 'Baz' ); - global $wgOut; - $wgOut = $this->getMockBuilder( 'OutputPage' ) + $mock = $this->getMockBuilder( 'OutputPage' ) ->disableOriginalConstructor() ->getMock(); - $wgOut->expects( $this->once() ) + $mock->expects( $this->once() ) ->method( 'showErrorPage' ) ->with( $title, $mockMessage, $params ); - $wgOut->expects( $this->once() ) + $mock->expects( $this->once() ) ->method( 'output' ); + $this->setMwGlobals( 'wgOut', $mock ); $e = new ErrorPageError( $title, $mockMessage, $params ); $e->report(); } - - } diff --git a/tests/phpunit/includes/exception/MWExceptionHandlerTest.php b/tests/phpunit/includes/exception/MWExceptionHandlerTest.php index dc5dc6aa..d73f17d9 100644 --- a/tests/phpunit/includes/exception/MWExceptionHandlerTest.php +++ b/tests/phpunit/includes/exception/MWExceptionHandlerTest.php @@ -15,7 +15,7 @@ class MWExceptionHandlerTest extends MediaWikiTestCase { $refvar = 'value'; try { $array = array( 'a', 'b' ); - $object = new StdClass(); + $object = new stdClass(); self::helperThrowAnException( $array, $object, $refvar ); } catch ( Exception $e ) { } diff --git a/tests/phpunit/includes/exception/ThrottledErrorTest.php b/tests/phpunit/includes/exception/ThrottledErrorTest.php index bdb143fa..a1cf84bc 100644 --- a/tests/phpunit/includes/exception/ThrottledErrorTest.php +++ b/tests/phpunit/includes/exception/ThrottledErrorTest.php @@ -6,23 +6,8 @@ */ class ThrottledErrorTest extends MediaWikiTestCase { - protected $wgOut; - - protected function setUp() { - parent::setUp(); - global $wgOut; - $this->wgOut = clone $wgOut; - } - - protected function tearDown() { - parent::tearDown(); - global $wgOut; - $wgOut = $this->wgOut; - } - public function testExceptionSetsStatusCode() { - global $wgOut; - $wgOut = $this->getMockWgOut(); + $this->setMwGlobals( 'wgOut', $this->getMockWgOut() ); try { throw new ThrottledError(); } catch ( ThrottledError $e ) { diff --git a/tests/phpunit/includes/externalstore/ExternalStoreTest.php b/tests/phpunit/includes/externalstore/ExternalStoreTest.php new file mode 100644 index 00000000..07c2957c --- /dev/null +++ b/tests/phpunit/includes/externalstore/ExternalStoreTest.php @@ -0,0 +1,87 @@ +setMwGlobals( 'wgExternalStores', false ); + + $this->assertFalse( + ExternalStore::fetchFromURL( 'FOO://cluster1/200' ), + 'Deny if wgExternalStores is not set to a non-empty array' + ); + + $this->setMwGlobals( 'wgExternalStores', array( 'FOO' ) ); + + $this->assertEquals( + ExternalStore::fetchFromURL( 'FOO://cluster1/200' ), + 'Hello', + 'Allow FOO://cluster1/200' + ); + $this->assertEquals( + ExternalStore::fetchFromURL( 'FOO://cluster1/300/0' ), + 'Hello', + 'Allow FOO://cluster1/300/0' + ); + # Assertions for r68900 + $this->assertFalse( + ExternalStore::fetchFromURL( 'ftp.example.org' ), + 'Deny domain ftp.example.org' + ); + $this->assertFalse( + ExternalStore::fetchFromURL( '/example.txt' ), + 'Deny path /example.txt' + ); + $this->assertFalse( + ExternalStore::fetchFromURL( 'http://' ), + 'Deny protocol http://' + ); + } +} + +class ExternalStoreFOO { + + protected $data = array( + 'cluster1' => array( + '200' => 'Hello', + '300' => array( + 'Hello', 'World', + ), + ), + ); + + /** + * Fetch data from given URL + * @param string $url An url of the form FOO://cluster/id or FOO://cluster/id/itemid. + * @return mixed + */ + function fetchFromURL( $url ) { + // Based on ExternalStoreDB + $path = explode( '/', $url ); + $cluster = $path[2]; + $id = $path[3]; + if ( isset( $path[4] ) ) { + $itemID = $path[4]; + } else { + $itemID = false; + } + + if ( !isset( $this->data[$cluster][$id] ) ) { + return null; + } + + if ( $itemID !== false + && is_array( $this->data[$cluster][$id] ) + && isset( $this->data[$cluster][$id][$itemID] ) + ) { + return $this->data[$cluster][$id][$itemID]; + } + + return $this->data[$cluster][$id]; + } +} diff --git a/tests/phpunit/includes/filebackend/FileBackendTest.php b/tests/phpunit/includes/filebackend/FileBackendTest.php index 9558cc7d..bfca75ad 100644 --- a/tests/phpunit/includes/filebackend/FileBackendTest.php +++ b/tests/phpunit/includes/filebackend/FileBackendTest.php @@ -13,14 +13,13 @@ class FileBackendTest extends MediaWikiTestCase { private $multiBackend; /** @var FSFileBackend */ public $singleBackend; - private $filesToPrune = array(); private static $backendToUse; protected function setUp() { global $wgFileBackends; parent::setUp(); $uniqueId = time() . '-' . mt_rand(); - $tmpPrefix = wfTempDir() . '/filebackend-unittest-' . $uniqueId; + $tmpDir = $this->getNewTempDirectory(); if ( $this->getCliArg( 'use-filebackend' ) ) { if ( self::$backendToUse ) { $this->singleBackend = self::$backendToUse; @@ -51,8 +50,8 @@ class FileBackendTest extends MediaWikiTestCase { 'lockManager' => LockManagerGroup::singleton()->get( 'fsLockManager' ), 'wikiId' => wfWikiID(), 'containerPaths' => array( - 'unittest-cont1' => "{$tmpPrefix}-localtesting-cont1", - 'unittest-cont2' => "{$tmpPrefix}-localtesting-cont2" ) + 'unittest-cont1' => "{$tmpDir}/localtesting-cont1", + 'unittest-cont2' => "{$tmpDir}/localtesting-cont2" ) ) ); } $this->multiBackend = new FileBackendMultiWrite( array( @@ -65,21 +64,20 @@ class FileBackendTest extends MediaWikiTestCase { 'name' => 'localmultitesting1', 'class' => 'FSFileBackend', 'containerPaths' => array( - 'unittest-cont1' => "{$tmpPrefix}-localtestingmulti1-cont1", - 'unittest-cont2' => "{$tmpPrefix}-localtestingmulti1-cont2" ), + 'unittest-cont1' => "{$tmpDir}/localtestingmulti1-cont1", + 'unittest-cont2' => "{$tmpDir}/localtestingmulti1-cont2" ), 'isMultiMaster' => false ), array( 'name' => 'localmultitesting2', 'class' => 'FSFileBackend', 'containerPaths' => array( - 'unittest-cont1' => "{$tmpPrefix}-localtestingmulti2-cont1", - 'unittest-cont2' => "{$tmpPrefix}-localtestingmulti2-cont2" ), + 'unittest-cont1' => "{$tmpDir}/localtestingmulti2-cont1", + 'unittest-cont2' => "{$tmpDir}/localtestingmulti2-cont2" ), 'isMultiMaster' => true ) ) ) ); - $this->filesToPrune = array(); } private static function baseStorePath() { @@ -214,7 +212,7 @@ class FileBackendTest extends MediaWikiTestCase { * @dataProvider provider_testStore */ public function testStore( $op ) { - $this->filesToPrune[] = $op['src']; + $this->addTmpFiles( $op['src'] ); $this->backend = $this->singleBackend; $this->tearDownFiles(); @@ -224,7 +222,6 @@ class FileBackendTest extends MediaWikiTestCase { $this->backend = $this->multiBackend; $this->tearDownFiles(); $this->doTestStore( $op ); - $this->filesToPrune[] = $op['src']; # avoid file leaking $this->tearDownFiles(); } @@ -275,27 +272,15 @@ class FileBackendTest extends MediaWikiTestCase { $tmpName = TempFSFile::factory( "unittests_", 'txt' )->getPath(); $toPath = self::baseStorePath() . '/unittest-cont1/e/fun/obj1.txt'; $op = array( 'op' => 'store', 'src' => $tmpName, 'dst' => $toPath ); - $cases[] = array( - $op, // operation - $tmpName, // source - $toPath, // dest - ); + $cases[] = array( $op ); $op2 = $op; $op2['overwrite'] = true; - $cases[] = array( - $op2, // operation - $tmpName, // source - $toPath, // dest - ); + $cases[] = array( $op2 ); - $op2 = $op; - $op2['overwriteSame'] = true; - $cases[] = array( - $op2, // operation - $tmpName, // source - $toPath, // dest - ); + $op3 = $op; + $op3['overwriteSame'] = true; + $cases[] = array( $op3 ); return $cases; } @@ -948,18 +933,14 @@ class FileBackendTest extends MediaWikiTestCase { * @dataProvider provider_testConcatenate */ public function testConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus ) { - $this->filesToPrune[] = $op['dst']; - $this->backend = $this->singleBackend; $this->tearDownFiles(); $this->doTestConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus ); - $this->filesToPrune[] = $op['dst']; # avoid file leaking $this->tearDownFiles(); $this->backend = $this->multiBackend; $this->tearDownFiles(); $this->doTestConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus ); - $this->filesToPrune[] = $op['dst']; # avoid file leaking $this->tearDownFiles(); } @@ -983,7 +964,7 @@ class FileBackendTest extends MediaWikiTestCase { $this->assertGoodStatus( $status, "Creation of source files succeeded ($backendName)." ); - $dest = $params['dst']; + $dest = $params['dst'] = $this->getNewTempFile(); if ( $alreadyExists ) { $ok = file_put_contents( $dest, 'blah...blah...waahwaah' ) !== false; $this->assertEquals( true, $ok, @@ -1029,8 +1010,6 @@ class FileBackendTest extends MediaWikiTestCase { public static function provider_testConcatenate() { $cases = array(); - $rand = mt_rand( 0, 2000000000 ) . time(); - $dest = wfTempDir() . "/randomfile!$rand.txt"; $srcs = array( self::baseStorePath() . '/unittest-cont1/e/file1.txt', self::baseStorePath() . '/unittest-cont1/e/file2.txt', @@ -1055,7 +1034,7 @@ class FileBackendTest extends MediaWikiTestCase { 'lkaem;a', 'legma' ); - $params = array( 'srcs' => $srcs, 'dst' => $dest ); + $params = array( 'srcs' => $srcs ); $cases[] = array( $params, // operation @@ -1499,7 +1478,7 @@ class FileBackendTest extends MediaWikiTestCase { $url = $this->backend->getFileHttpUrl( array( 'src' => $source ) ); if ( $url !== null ) { // supported - $data = Http::request( "GET", $url ); + $data = Http::request( "GET", $url, array(), __METHOD__ ); $this->assertEquals( $content, $data, "HTTP GET of URL has right contents ($backendName)." ); } @@ -1761,16 +1740,13 @@ class FileBackendTest extends MediaWikiTestCase { $fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag'; $tmpNameA = TempFSFile::factory( "unittests_", 'txt' )->getPath(); - file_put_contents( $tmpNameA, $fileAContents ); $tmpNameB = TempFSFile::factory( "unittests_", 'txt' )->getPath(); - file_put_contents( $tmpNameB, $fileBContents ); $tmpNameC = TempFSFile::factory( "unittests_", 'txt' )->getPath(); + $this->addTmpFiles( array( $tmpNameA, $tmpNameB, $tmpNameC ) ); + file_put_contents( $tmpNameA, $fileAContents ); + file_put_contents( $tmpNameB, $fileBContents ); file_put_contents( $tmpNameC, $fileCContents ); - $this->filesToPrune[] = $tmpNameA; # avoid file leaking - $this->filesToPrune[] = $tmpNameB; # avoid file leaking - $this->filesToPrune[] = $tmpNameC; # avoid file leaking - $fileA = "$base/unittest-cont1/e/a/b/fileA.txt"; $fileB = "$base/unittest-cont1/e/a/b/fileB.txt"; $fileC = "$base/unittest-cont1/e/a/b/fileC.txt"; @@ -2434,16 +2410,10 @@ class FileBackendTest extends MediaWikiTestCase { } function tearDownFiles() { - foreach ( $this->filesToPrune as $file ) { - if ( is_file( $file ) ) { - unlink( $file ); - } - } $containers = array( 'unittest-cont1', 'unittest-cont2', 'unittest-cont-bad' ); foreach ( $containers as $container ) { $this->deleteFiles( $container ); } - $this->filesToPrune = array(); } private function deleteFiles( $container ) { diff --git a/tests/phpunit/includes/filerepo/StoreBatchTest.php b/tests/phpunit/includes/filerepo/StoreBatchTest.php index 9cc2efbf..86bfe123 100644 --- a/tests/phpunit/includes/filerepo/StoreBatchTest.php +++ b/tests/phpunit/includes/filerepo/StoreBatchTest.php @@ -16,7 +16,7 @@ class StoreBatchTest extends MediaWikiTestCase { parent::setUp(); # Forge a FSRepo object to not have to rely on local wiki settings - $tmpPrefix = wfTempDir() . '/storebatch-test-' . time() . '-' . mt_rand(); + $tmpPrefix = $this->getNewTempDirectory(); if ( $this->getCliArg( 'use-filebackend' ) ) { $name = $this->getCliArg( 'use-filebackend' ); $useConfig = array(); @@ -35,10 +35,10 @@ class StoreBatchTest extends MediaWikiTestCase { 'name' => 'local-testing', 'wikiId' => wfWikiID(), 'containerPaths' => array( - 'unittests-public' => "{$tmpPrefix}-public", - 'unittests-thumb' => "{$tmpPrefix}-thumb", - 'unittests-temp' => "{$tmpPrefix}-temp", - 'unittests-deleted' => "{$tmpPrefix}-deleted", + 'unittests-public' => "{$tmpPrefix}/public", + 'unittests-thumb' => "{$tmpPrefix}/thumb", + 'unittests-temp' => "{$tmpPrefix}/temp", + 'unittests-deleted' => "{$tmpPrefix}/deleted", ) ) ); } @@ -52,13 +52,8 @@ class StoreBatchTest extends MediaWikiTestCase { } protected function tearDown() { - $this->repo->cleanupBatch( $this->createdFiles ); // delete files - foreach ( $this->createdFiles as $tmp ) { // delete dirs - $tmp = $this->repo->resolveVirtualUrl( $tmp ); - while ( $tmp = FileBackend::parentStoragePath( $tmp ) ) { - $this->repo->getBackend()->clean( array( 'dir' => $tmp ) ); - } - } + // Delete files + $this->repo->cleanupBatch( $this->createdFiles ); parent::tearDown(); } diff --git a/tests/phpunit/includes/filerepo/file/LocalFileTest.php b/tests/phpunit/includes/filerepo/file/LocalFileTest.php new file mode 100644 index 00000000..3c5754bf --- /dev/null +++ b/tests/phpunit/includes/filerepo/file/LocalFileTest.php @@ -0,0 +1,183 @@ +setMwGlobals( 'wgCapitalLinks', true ); + + $info = array( + 'name' => 'test', + 'directory' => '/testdir', + 'url' => '/testurl', + 'hashLevels' => 2, + 'transformVia404' => false, + 'backend' => new FSFileBackend( array( + 'name' => 'local-backend', + 'wikiId' => wfWikiId(), + 'containerPaths' => array( + 'cont1' => "/testdir/local-backend/tempimages/cont1", + 'cont2' => "/testdir/local-backend/tempimages/cont2" + ) + ) ) + ); + $this->repo_hl0 = new LocalRepo( array( 'hashLevels' => 0 ) + $info ); + $this->repo_hl2 = new LocalRepo( array( 'hashLevels' => 2 ) + $info ); + $this->repo_lc = new LocalRepo( array( 'initialCapital' => false ) + $info ); + $this->file_hl0 = $this->repo_hl0->newFile( 'test!' ); + $this->file_hl2 = $this->repo_hl2->newFile( 'test!' ); + $this->file_lc = $this->repo_lc->newFile( 'test!' ); + } + + /** + * @covers File::getHashPath + */ + public function testGetHashPath() { + $this->assertEquals( '', $this->file_hl0->getHashPath() ); + $this->assertEquals( 'a/a2/', $this->file_hl2->getHashPath() ); + $this->assertEquals( 'c/c4/', $this->file_lc->getHashPath() ); + } + + /** + * @covers File::getRel + */ + public function testGetRel() { + $this->assertEquals( 'Test!', $this->file_hl0->getRel() ); + $this->assertEquals( 'a/a2/Test!', $this->file_hl2->getRel() ); + $this->assertEquals( 'c/c4/test!', $this->file_lc->getRel() ); + } + + /** + * @covers File::getUrlRel + */ + public function testGetUrlRel() { + $this->assertEquals( 'Test%21', $this->file_hl0->getUrlRel() ); + $this->assertEquals( 'a/a2/Test%21', $this->file_hl2->getUrlRel() ); + $this->assertEquals( 'c/c4/test%21', $this->file_lc->getUrlRel() ); + } + + /** + * @covers File::getArchivePath + */ + public function testGetArchivePath() { + $this->assertEquals( + 'mwstore://local-backend/test-public/archive', + $this->file_hl0->getArchivePath() + ); + $this->assertEquals( + 'mwstore://local-backend/test-public/archive/a/a2', + $this->file_hl2->getArchivePath() + ); + $this->assertEquals( + 'mwstore://local-backend/test-public/archive/!', + $this->file_hl0->getArchivePath( '!' ) + ); + $this->assertEquals( + 'mwstore://local-backend/test-public/archive/a/a2/!', + $this->file_hl2->getArchivePath( '!' ) + ); + } + + /** + * @covers File::getThumbPath + */ + public function testGetThumbPath() { + $this->assertEquals( + 'mwstore://local-backend/test-thumb/Test!', + $this->file_hl0->getThumbPath() + ); + $this->assertEquals( + 'mwstore://local-backend/test-thumb/a/a2/Test!', + $this->file_hl2->getThumbPath() + ); + $this->assertEquals( + 'mwstore://local-backend/test-thumb/Test!/x', + $this->file_hl0->getThumbPath( 'x' ) + ); + $this->assertEquals( + 'mwstore://local-backend/test-thumb/a/a2/Test!/x', + $this->file_hl2->getThumbPath( 'x' ) + ); + } + + /** + * @covers File::getArchiveUrl + */ + public function testGetArchiveUrl() { + $this->assertEquals( '/testurl/archive', $this->file_hl0->getArchiveUrl() ); + $this->assertEquals( '/testurl/archive/a/a2', $this->file_hl2->getArchiveUrl() ); + $this->assertEquals( '/testurl/archive/%21', $this->file_hl0->getArchiveUrl( '!' ) ); + $this->assertEquals( '/testurl/archive/a/a2/%21', $this->file_hl2->getArchiveUrl( '!' ) ); + } + + /** + * @covers File::getThumbUrl + */ + public function testGetThumbUrl() { + $this->assertEquals( '/testurl/thumb/Test%21', $this->file_hl0->getThumbUrl() ); + $this->assertEquals( '/testurl/thumb/a/a2/Test%21', $this->file_hl2->getThumbUrl() ); + $this->assertEquals( '/testurl/thumb/Test%21/x', $this->file_hl0->getThumbUrl( 'x' ) ); + $this->assertEquals( '/testurl/thumb/a/a2/Test%21/x', $this->file_hl2->getThumbUrl( 'x' ) ); + } + + /** + * @covers File::getArchiveVirtualUrl + */ + public function testGetArchiveVirtualUrl() { + $this->assertEquals( 'mwrepo://test/public/archive', $this->file_hl0->getArchiveVirtualUrl() ); + $this->assertEquals( + 'mwrepo://test/public/archive/a/a2', + $this->file_hl2->getArchiveVirtualUrl() + ); + $this->assertEquals( + 'mwrepo://test/public/archive/%21', + $this->file_hl0->getArchiveVirtualUrl( '!' ) + ); + $this->assertEquals( + 'mwrepo://test/public/archive/a/a2/%21', + $this->file_hl2->getArchiveVirtualUrl( '!' ) + ); + } + + /** + * @covers File::getThumbVirtualUrl + */ + public function testGetThumbVirtualUrl() { + $this->assertEquals( 'mwrepo://test/thumb/Test%21', $this->file_hl0->getThumbVirtualUrl() ); + $this->assertEquals( 'mwrepo://test/thumb/a/a2/Test%21', $this->file_hl2->getThumbVirtualUrl() ); + $this->assertEquals( + 'mwrepo://test/thumb/Test%21/%21', + $this->file_hl0->getThumbVirtualUrl( '!' ) + ); + $this->assertEquals( + 'mwrepo://test/thumb/a/a2/Test%21/%21', + $this->file_hl2->getThumbVirtualUrl( '!' ) + ); + } + + /** + * @covers File::getUrl + */ + public function testGetUrl() { + $this->assertEquals( '/testurl/Test%21', $this->file_hl0->getUrl() ); + $this->assertEquals( '/testurl/a/a2/Test%21', $this->file_hl2->getUrl() ); + } + + /** + * @covers ::wfLocalFile + */ + public function testWfLocalFile() { + $file = wfLocalFile( "File:Some_file_that_probably_doesn't exist.png" ); + $this->assertThat( + $file, + $this->isInstanceOf( 'LocalFile' ), + 'wfLocalFile() returns LocalFile for valid Titles' + ); + } +} diff --git a/tests/phpunit/includes/installer/DatabaseUpdaterTest.php b/tests/phpunit/includes/installer/DatabaseUpdaterTest.php new file mode 100644 index 00000000..abff3e68 --- /dev/null +++ b/tests/phpunit/includes/installer/DatabaseUpdaterTest.php @@ -0,0 +1,279 @@ +setAppliedUpdates( "test", array() ); + $expected = "updatelist-test-" . time() . "0"; + $actual = $db->lastInsertData['ul_key']; + $this->assertEquals( $expected, $actual, var_export( $db->lastInsertData, true ) ); + $dbu->setAppliedUpdates( "test", array() ); + $expected = "updatelist-test-" . time() . "1"; + $actual = $db->lastInsertData['ul_key']; + $this->assertEquals( $expected, $actual, var_export( $db->lastInsertData, true ) ); + } +} + +class FakeDatabase extends DatabaseBase { + public $lastInsertTable; + public $lastInsertData; + + function __construct() { + } + + function clearFlag( $arg ) { + } + + function setFlag( $arg ) { + } + + public function insert( $table, $a, $fname = __METHOD__, $options = array() ) { + $this->lastInsertTable = $table; + $this->lastInsertData = $a; + } + + /** + * Get the type of the DBMS, as it appears in $wgDBtype. + * + * @return string + */ + function getType() { + // TODO: Implement getType() method. + } + + /** + * Open a connection to the database. Usually aborts on failure + * + * @param string $server Database server host + * @param string $user Database user name + * @param string $password Database user password + * @param string $dbName Database name + * @return bool + * @throws DBConnectionError + */ + function open( $server, $user, $password, $dbName ) { + // TODO: Implement open() method. + } + + /** + * Fetch the next row from the given result object, in object form. + * Fields can be retrieved with $row->fieldname, with fields acting like + * member variables. + * If no more rows are available, false is returned. + * + * @param ResultWrapper|stdClass $res Object as returned from DatabaseBase::query(), etc. + * @return stdClass|bool + * @throws DBUnexpectedError Thrown if the database returns an error + */ + function fetchObject( $res ) { + // TODO: Implement fetchObject() method. + } + + /** + * Fetch the next row from the given result object, in associative array + * form. Fields are retrieved with $row['fieldname']. + * If no more rows are available, false is returned. + * + * @param ResultWrapper $res Result object as returned from DatabaseBase::query(), etc. + * @return array|bool + * @throws DBUnexpectedError Thrown if the database returns an error + */ + function fetchRow( $res ) { + // TODO: Implement fetchRow() method. + } + + /** + * Get the number of rows in a result object + * + * @param mixed $res A SQL result + * @return int + */ + function numRows( $res ) { + // TODO: Implement numRows() method. + } + + /** + * Get the number of fields in a result object + * @see http://www.php.net/mysql_num_fields + * + * @param mixed $res A SQL result + * @return int + */ + function numFields( $res ) { + // TODO: Implement numFields() method. + } + + /** + * Get a field name in a result object + * @see http://www.php.net/mysql_field_name + * + * @param mixed $res A SQL result + * @param int $n + * @return string + */ + function fieldName( $res, $n ) { + // TODO: Implement fieldName() method. + } + + /** + * Get the inserted value of an auto-increment row + * + * The value inserted should be fetched from nextSequenceValue() + * + * Example: + * $id = $dbw->nextSequenceValue( 'page_page_id_seq' ); + * $dbw->insert( 'page', array( 'page_id' => $id ) ); + * $id = $dbw->insertId(); + * + * @return int + */ + function insertId() { + // TODO: Implement insertId() method. + } + + /** + * Change the position of the cursor in a result object + * @see http://www.php.net/mysql_data_seek + * + * @param mixed $res A SQL result + * @param int $row + */ + function dataSeek( $res, $row ) { + // TODO: Implement dataSeek() method. + } + + /** + * Get the last error number + * @see http://www.php.net/mysql_errno + * + * @return int + */ + function lastErrno() { + // TODO: Implement lastErrno() method. + } + + /** + * Get a description of the last error + * @see http://www.php.net/mysql_error + * + * @return string + */ + function lastError() { + // TODO: Implement lastError() method. + } + + /** + * mysql_fetch_field() wrapper + * Returns false if the field doesn't exist + * + * @param string $table Table name + * @param string $field Field name + * + * @return Field + */ + function fieldInfo( $table, $field ) { + // TODO: Implement fieldInfo() method. + } + + /** + * Get information about an index into an object + * @param string $table Table name + * @param string $index Index name + * @param string $fname Calling function name + * @return mixed Database-specific index description class or false if the index does not exist + */ + function indexInfo( $table, $index, $fname = __METHOD__ ) { + // TODO: Implement indexInfo() method. + } + + /** + * Get the number of rows affected by the last write query + * @see http://www.php.net/mysql_affected_rows + * + * @return int + */ + function affectedRows() { + // TODO: Implement affectedRows() method. + } + + /** + * Wrapper for addslashes() + * + * @param string $s String to be slashed. + * @return string Slashed string. + */ + function strencode( $s ) { + // TODO: Implement strencode() method. + } + + /** + * Returns a wikitext link to the DB's website, e.g., + * return "[http://www.mysql.com/ MySQL]"; + * Should at least contain plain text, if for some reason + * your database has no website. + * + * @return string Wikitext of a link to the server software's web site + */ + function getSoftwareLink() { + // TODO: Implement getSoftwareLink() method. + } + + /** + * A string describing the current software version, like from + * mysql_get_server_info(). + * + * @return string Version information from the database server. + */ + function getServerVersion() { + // TODO: Implement getServerVersion() method. + } + + /** + * Closes underlying database connection + * @since 1.20 + * @return bool Whether connection was closed successfully + */ + protected function closeConnection() { + // TODO: Implement closeConnection() method. + } + + /** + * The DBMS-dependent part of query() + * + * @param string $sql SQL query. + * @return ResultWrapper|bool Result object to feed to fetchObject, + * fetchRow, ...; or false on failure + */ + protected function doQuery( $sql ) { + // TODO: Implement doQuery() method. + } +} + +class FakeDatabaseUpdater extends DatabaseUpdater { + function __construct( $db ) { + $this->db = $db; + self::$updateCounter = 0; + } + + /** + * Get an array of updates to perform on the database. Should return a + * multi-dimensional array. The main key is the MediaWiki version (1.12, + * 1.13...) with the values being arrays of updates, identical to how + * updaters.inc did it (for now) + * + * @return array + */ + protected function getCoreUpdateList() { + return array(); + } + + public function canUseNewUpdatelog() { + return true; + } + + public function setAppliedUpdates( $version, $updates = array() ) { + parent::setAppliedUpdates( $version, $updates ); + } +} diff --git a/tests/phpunit/includes/installer/InstallDocFormatterTest.php b/tests/phpunit/includes/installer/InstallDocFormatterTest.php index 064d5185..724f0c88 100644 --- a/tests/phpunit/includes/installer/InstallDocFormatterTest.php +++ b/tests/phpunit/includes/installer/InstallDocFormatterTest.php @@ -34,6 +34,21 @@ class InstallDocFormatterTest extends MediaWikiTestCase { array( ':One indentation', "\tOne indentation", 'Replacing a single \t' ), array( '::Two indentations', "\t\tTwo indentations", 'Replacing 2 x \t' ), + # Transform 'T123' links + array( + '[https://phabricator.wikimedia.org/T123 T123]', + 'T123', 'Testing T123 links' ), + array( + 'bug [https://phabricator.wikimedia.org/T123 T123]', + 'bug T123', 'Testing bug T123 links' ), + array( + '([https://phabricator.wikimedia.org/T987654 T987654])', + '(T987654)', 'Testing (T987654) links' ), + + # "Tabc" shouldn't work + array( 'Tfoobar', 'Tfoobar', "Don't match T followed by non-digits" ), + array( 'T!!fakefake!!', 'T!!fakefake!!', "Don't match T followed by non-digits" ), + # Transform 'bug 123' links array( '[https://bugzilla.wikimedia.org/123 bug 123]', diff --git a/tests/phpunit/includes/jobqueue/JobQueueTest.php b/tests/phpunit/includes/jobqueue/JobQueueTest.php index 69e40068..ea1a4f63 100644 --- a/tests/phpunit/includes/jobqueue/JobQueueTest.php +++ b/tests/phpunit/includes/jobqueue/JobQueueTest.php @@ -247,8 +247,13 @@ class JobQueueTest extends MediaWikiTestCase { $this->assertNull( $queue->push( $this->newJob( 0, $root1 ) ), "Push worked ($desc)" ); } $queue->deduplicateRootJob( $this->newJob( 0, $root1 ) ); - sleep( 1 ); // roo job timestamp will increase - $root2 = Job::newRootJobParams( "nulljobspam:$id" ); // task ID/timestamp + + $root2 = $root1; + # Add a second to UNIX epoch and format back to TS_MW + $root2_ts = strtotime( $root2['rootJobTimestamp'] ); + $root2_ts++; + $root2['rootJobTimestamp'] = wfTimestamp( TS_MW, $root2_ts ); + $this->assertNotEquals( $root1['rootJobTimestamp'], $root2['rootJobTimestamp'], "Root job signatures have different timestamps." ); for ( $i = 0; $i < 5; ++$i ) { diff --git a/tests/phpunit/includes/jobqueue/JobTest.php b/tests/phpunit/includes/jobqueue/JobTest.php new file mode 100644 index 00000000..93069d2e --- /dev/null +++ b/tests/phpunit/includes/jobqueue/JobTest.php @@ -0,0 +1,67 @@ +assertEquals( $expected, $job->toString() ); + } + + public function provideTestToString() { + $mockToStringObj = $this->getMock( 'stdClass', array( '__toString' ) ); + $mockToStringObj->expects( $this->any() ) + ->method( '__toString' ) + ->will( $this->returnValue( '{STRING_OBJ_VAL}' ) ); + + return array( + array( + $this->getMockJob( false ), + 'someCommand ' + ), + array( + $this->getMockJob( array( 'key' => 'val' ) ), + 'someCommand key=val' + ), + array( + $this->getMockJob( array( 'key' => array( 'inkey' => 'inval' ) ) ), + 'someCommand key={"inkey":"inval"}' + ), + array( + $this->getMockJob( array( 'val1' ) ), + 'someCommand 0=val1' + ), + array( + $this->getMockJob( array( 'val1', 'val2' ) ), + 'someCommand 0=val1 1=val2' + ), + array( + $this->getMockJob( array( new stdClass() ) ), + 'someCommand 0=object(stdClass)' + ), + array( + $this->getMockJob( array( $mockToStringObj ) ), + 'someCommand 0={STRING_OBJ_VAL}' + ), + ); + } + + public function getMockJob( $params ) { + $mock = $this->getMockForAbstractClass( + 'Job', + array( 'someCommand', new Title(), $params ), + 'SomeJob' + ); + return $mock; + } + +} diff --git a/tests/phpunit/includes/json/FormatJsonTest.php b/tests/phpunit/includes/json/FormatJsonTest.php index af68ab03..f0ac6acc 100644 --- a/tests/phpunit/includes/json/FormatJsonTest.php +++ b/tests/phpunit/includes/json/FormatJsonTest.php @@ -169,12 +169,30 @@ class FormatJsonTest extends MediaWikiTestCase { $this->assertEquals( $value, $st->getValue() ); } + /** + * Test data for testParseTryFixing. + * + * Some PHP interpreters use json-c rather than the JSON.org cannonical + * 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 array( - array( "[,]", '[]' ), - array( "[ , ]", '[]' ), + array( "[,]", '[]', false ), + array( "[ , ]", '[]', false ), array( "[ , }", false ), - array( '[1],', false ), + array( '[1],', false, true, '[1]' ), array( "[1,]", '[1]' ), array( "[1\n,]", '[1]' ), array( "[1,\n]", '[1]' ), @@ -182,24 +200,44 @@ class FormatJsonTest extends MediaWikiTestCase { array( "[1\n,\n]\n", '[1]' ), array( '["a,",]', '["a,"]' ), array( "[[1,]\n,[2,\n],[3\n,]]", '[[1],[2],[3]]' ), - array( '[[1,],[2,],[3,]]', false ), // I wish we could parse this, but would need quote parsing - array( '[1,,]', false ), + // I wish we could parse this, but would need quote parsing + array( '[[1,],[2,],[3,]]', false, true, '[[1],[2],[3]]' ), + array( '[1,,]', false, false, '[1]' ), ); } /** * @dataProvider provideParseTryFixing * @param string $value - * @param string|bool $expected + * @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 ) { + 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->assertType( 'Status', $st ); if ( $expected === false ) { - $this->assertFalse( $st->isOK() ); + $this->assertFalse( $st->isOK(), 'Expected isOK() == false' ); } else { - $this->assertFalse( $st->isGood() ); - $this->assertTrue( $st->isOK() ); + $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 ); } @@ -222,6 +260,64 @@ class FormatJsonTest extends MediaWikiTestCase { $this->assertFalse( $st->isOK() ); } + public function provideStripComments() { + return array( + array( '{"a":"b"}', '{"a":"b"}' ), + array( "{\"a\":\"b\"}\n", "{\"a\":\"b\"}\n" ), + array( '/*c*/{"c":"b"}', '{"c":"b"}' ), + array( '{"a":"c"}/*c*/', '{"a":"c"}' ), + array( '/*c//d*/{"c":"b"}', '{"c":"b"}' ), + array( '{/*c*/"c":"b"}', '{"c":"b"}' ), + array( "/*\nc\r\n*/{\"c\":\"b\"}", '{"c":"b"}' ), + array( "//c\n{\"c\":\"b\"}", '{"c":"b"}' ), + array( "//c\r\n{\"c\":\"b\"}", '{"c":"b"}' ), + array( '{"a":"c"}//c', '{"a":"c"}' ), + array( "{\"a-c\"://c\n\"b\"}", '{"a-c":"b"}' ), + array( '{"/*a":"b"}', '{"/*a":"b"}' ), + array( '{"a":"//b"}', '{"a":"//b"}' ), + array( '{"a":"b/*c*/"}', '{"a":"b/*c*/"}' ), + array( "{\"\\\"/*a\":\"b\"}", "{\"\\\"/*a\":\"b\"}" ), + array( '', '' ), + array( '/*c', '' ), + array( '//c', '' ), + array( '"http://example.com"', '"http://example.com"' ), + array( "\0", "\0" ), + array( '"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 array( + array( '/* blah */true', true ), + array( "// blah \ntrue", true ), + array( '[ "a" , /* blah */ "b" ]', array( '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->assertType( 'Status', $st ); + $this->assertTrue( $st->isGood() ); + $this->assertEquals( $expect, $st->getValue() ); + } + /** * Generate a set of test cases for a particular combination of encoder options. * diff --git a/tests/phpunit/includes/libs/ArrayUtilsTest.php b/tests/phpunit/includes/libs/ArrayUtilsTest.php new file mode 100644 index 00000000..b5ea7b72 --- /dev/null +++ b/tests/phpunit/includes/libs/ArrayUtilsTest.php @@ -0,0 +1,311 @@ +assertSame( + ArrayUtils::findLowerBound( + $valueCallback, $valueCount, $comparisonCallback, $target + ), $expected + ); + } + + function provideFindLowerBound() { + $self = $this; + $indexValueCallback = function ( $size ) use ( $self ) { + return function ( $val ) use ( $self, $size ) { + $self->assertTrue( $val >= 0 ); + $self->assertTrue( $val < $size ); + return $val; + }; + }; + $comparisonCallback = function ( $a, $b ) { + return $a - $b; + }; + + return array( + array( + $indexValueCallback( 0 ), + 0, + $comparisonCallback, + 1, + false, + ), + array( + $indexValueCallback( 1 ), + 1, + $comparisonCallback, + -1, + false, + ), + array( + $indexValueCallback( 1 ), + 1, + $comparisonCallback, + 0, + 0, + ), + array( + $indexValueCallback( 1 ), + 1, + $comparisonCallback, + 1, + 0, + ), + array( + $indexValueCallback( 2 ), + 2, + $comparisonCallback, + -1, + false, + ), + array( + $indexValueCallback( 2 ), + 2, + $comparisonCallback, + 0, + 0, + ), + array( + $indexValueCallback( 2 ), + 2, + $comparisonCallback, + 0.5, + 0, + ), + array( + $indexValueCallback( 2 ), + 2, + $comparisonCallback, + 1, + 1, + ), + array( + $indexValueCallback( 2 ), + 2, + $comparisonCallback, + 1.5, + 1, + ), + array( + $indexValueCallback( 3 ), + 3, + $comparisonCallback, + 1, + 1, + ), + array( + $indexValueCallback( 3 ), + 3, + $comparisonCallback, + 1.5, + 1, + ), + array( + $indexValueCallback( 3 ), + 3, + $comparisonCallback, + 2, + 2, + ), + array( + $indexValueCallback( 3 ), + 3, + $comparisonCallback, + 3, + 2, + ), + ); + } + + /** + * @covers ArrayUtils::arrayDiffAssocRecursive + * @dataProvider provideArrayDiffAssocRecursive + */ + function testArrayDiffAssocRecursive( $expected ) { + $args = func_get_args(); + array_shift( $args ); + $this->assertEquals( call_user_func_array( + 'ArrayUtils::arrayDiffAssocRecursive', $args + ), $expected ); + } + + function provideArrayDiffAssocRecursive() { + return array( + array( + array(), + array(), + array(), + ), + array( + array(), + array(), + array(), + array(), + ), + array( + array( 1 ), + array( 1 ), + array(), + ), + array( + array( 1 ), + array( 1 ), + array(), + array(), + ), + array( + array(), + array(), + array( 1 ), + ), + array( + array(), + array(), + array( 1 ), + array( 2 ), + ), + array( + array( '' => 1 ), + array( '' => 1 ), + array(), + ), + array( + array(), + array(), + array( '' => 1 ), + ), + array( + array( 1 ), + array( 1 ), + array( 2 ), + ), + array( + array(), + array( 1 ), + array( 2 ), + array( 1 ), + ), + array( + array(), + array( 1 ), + array( 1, 2 ), + ), + array( + array( 1 => 1 ), + array( 1 => 1 ), + array( 1 ), + ), + array( + array(), + array( 1 => 1 ), + array( 1 ), + array( 1 => 1), + ), + array( + array(), + array( 1 => 1 ), + array( 1, 1, 1 ), + ), + array( + array(), + array( array() ), + array(), + ), + array( + array(), + array( array( array() ) ), + array(), + ), + array( + array( 1, array( 1 ) ), + array( 1, array( 1 ) ), + array(), + ), + array( + array( 1 ), + array( 1, array( 1 ) ), + array( 2, array( 1 ) ), + ), + array( + array(), + array( 1, array( 1 ) ), + array( 2, array( 1 ) ), + array( 1, array( 2 ) ), + ), + array( + array( 1 ), + array( 1, array() ), + array( 2 ), + ), + array( + array(), + array( 1, array() ), + array( 2 ), + array( 1 ), + ), + array( + array( 1, array( 1 => 2 ) ), + array( 1, array( 1, 2 ) ), + array( 2, array( 1 ) ), + ), + array( + array( 1 ), + array( 1, array( 1, 2 ) ), + array( 2, array( 1 ) ), + array( 2, array( 1 => 2 ) ), + ), + array( + array( 1 => array( 1, 2 ) ), + array( 1, array( 1, 2 ) ), + array( 1, array( 2 ) ), + ), + array( + array( 1 => array( array( 2, 3 ), 2 ) ), + array( 1, array( array( 2, 3 ), 2 ) ), + array( 1, array( 2 ) ), + ), + array( + array( 1 => array( array( 2 ), 2 ) ), + array( 1, array( array( 2, 3 ), 2 ) ), + array( 1, array( array( 1 => 3 ) ) ), + ), + array( + array( 1 => array( 1 => 2 ) ), + array( 1, array( array( 2, 3 ), 2 ) ), + array( 1, array( array( 1 => 3, 0 => 2 ) ) ), + ), + array( + array( 1 => array( 1 => 2 ) ), + array( 1, array( array( 2, 3 ), 2 ) ), + array( 1, array( array( 1 => 3 ) ) ), + array( 1 => array( array( 2 ) ) ), + ), + array( + array(), + array( 1, array( array( 2, 3 ), 2 ) ), + array( 1 => array( 1 => 2, 0 => array( 1 => 3, 0 => 2 ) ), 0 => 1 ), + ), + array( + array(), + array( 1, array( array( 2, 3 ), 2 ) ), + array( 1 => array( 1 => 2 ) ), + array( 1 => array( array( 1 => 3 ) ) ), + array( 1 => array( array( 2 ) ) ), + array( 1 ), + ), + ); + } +} diff --git a/tests/phpunit/includes/libs/CSSMinTest.php b/tests/phpunit/includes/libs/CSSMinTest.php index 43c50869..6142f967 100644 --- a/tests/phpunit/includes/libs/CSSMinTest.php +++ b/tests/phpunit/includes/libs/CSSMinTest.php @@ -141,15 +141,57 @@ class CSSMinTest extends MediaWikiTestCase { ); } + public static function provideIsRemoteUrl() { + return array( + array( true, 'http://localhost/w/red.gif?123' ), + array( true, 'https://example.org/x.png' ), + array( true, '//example.org/x.y.z/image.png' ), + array( true, '//localhost/styles.css?query=yes' ), + array( true, 'data:image/gif;base64,R0lGODlhAQABAIAAAP8AADAAACwAAAAAAQABAAACAkQBADs=' ), + array( false, 'x.gif' ), + array( false, '/x.gif' ), + array( false, './x.gif' ), + array( false, '../x.gif' ), + ); + } + + /** + * @dataProvider provideIsRemoteUrl + * @cover CSSMin::isRemoteUrl + */ + public function testIsRemoteUrl( $expect, $url ) { + $this->assertEquals( CSSMin::isRemoteUrl( $url ), $expect ); + } + + public static function provideIsLocalUrls() { + return array( + array( false, 'x.gif' ), + array( true, '/x.gif' ), + array( false, './x.gif' ), + array( false, '../x.gif' ), + ); + } + + /** + * @dataProvider provideIsLocalUrls + * @cover CSSMin::isLocalUrl + */ + public function testIsLocalUrl( $expect, $url ) { + $this->assertEquals( CSSMin::isLocalUrl( $url ), $expect ); + } + public static function provideRemapRemappingCases() { // red.gif and green.gif are one-pixel 35-byte GIFs. // large.png is a 35K PNG that should be non-embeddable. // Full paths start with http://localhost/w/. // Timestamps in output are replaced with 'timestamp'. - // data: URIs for red.gif and green.gif + // data: URIs for red.gif, green.gif, circle.svg $red = 'data:image/gif;base64,R0lGODlhAQABAIAAAP8AADAAACwAAAAAAQABAAACAkQBADs='; $green = 'data:image/gif;base64,R0lGODlhAQABAIAAAACAADAAACwAAAAAAQABAAACAkQBADs='; + $svg = 'data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%0A' + . '%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%228%22%20height%3D' + . '%228%22%3E%0A%3Ccircle%20cx%3D%224%22%20cy%3D%224%22%20r%3D%222%22%2F%3E%0A%3C%2Fsvg%3E%0A'; return array( array( @@ -233,6 +275,11 @@ class CSSMinTest extends MediaWikiTestCase { 'foo { /* @embed */ background: url(large.png); }', "foo { background: url(http://localhost/w/large.png?timestamp); }", ), + array( + 'SVG files are embedded without base64 encoding and unnecessary IE 6 and 7 fallback', + 'foo { /* @embed */ background: url(circle.svg); }', + "foo { background: url($svg); }", + ), array( 'Two regular files in one rule', 'foo { background: url(red.gif), url(green.gif); }', diff --git a/tests/phpunit/includes/libs/DeferredStringifierTest.php b/tests/phpunit/includes/libs/DeferredStringifierTest.php new file mode 100644 index 00000000..8b7610ae --- /dev/null +++ b/tests/phpunit/includes/libs/DeferredStringifierTest.php @@ -0,0 +1,50 @@ +newInstanceArgs( $params ); + $this->assertEquals( $expected, (string)$ds ); + } + + public static function provideToString() { + return array( + // No args + array( + array( + function() { + return 'foo'; + } + ), + 'foo' + ), + // Has args + array( + array( + function( $i ) { + return $i; + }, + 'bar' + ), + 'bar' + ), + ); + } + + /** + * Verify that the callback is not called if + * it is never converted to a string + */ + public function testCallbackNotCalled() { + $ds = new DeferredStringifier( function() { + throw new Exception( 'This should not be reached!' ); + } ); + // No exception was thrown + $this->assertTrue( true ); + } +} diff --git a/tests/phpunit/includes/libs/GenericArrayObjectTest.php b/tests/phpunit/includes/libs/GenericArrayObjectTest.php index 4911f73a..315bc7ed 100644 --- a/tests/phpunit/includes/libs/GenericArrayObjectTest.php +++ b/tests/phpunit/includes/libs/GenericArrayObjectTest.php @@ -24,10 +24,9 @@ * @ingroup Test * @group GenericArrayObject * - * @licence GNU GPL v2+ * @author Jeroen De Dauw < jeroendedauw@gmail.com > */ -abstract class GenericArrayObjectTest extends MediaWikiTestCase { +abstract class GenericArrayObjectTest extends PHPUnit_Framework_TestCase { /** * Returns objects that can serve as elements in the concrete diff --git a/tests/phpunit/includes/libs/HashRingTest.php b/tests/phpunit/includes/libs/HashRingTest.php index 68dfea1f..b51eb3f4 100644 --- a/tests/phpunit/includes/libs/HashRingTest.php +++ b/tests/phpunit/includes/libs/HashRingTest.php @@ -3,7 +3,7 @@ /** * @group HashRing */ -class HashRingTest extends MediaWikiTestCase { +class HashRingTest extends PHPUnit_Framework_TestCase { /** * @covers HashRing */ diff --git a/tests/phpunit/includes/libs/IEUrlExtensionTest.php b/tests/phpunit/includes/libs/IEUrlExtensionTest.php index b7071230..e96953ee 100644 --- a/tests/phpunit/includes/libs/IEUrlExtensionTest.php +++ b/tests/phpunit/includes/libs/IEUrlExtensionTest.php @@ -5,7 +5,7 @@ * @todo tests below for findIE6Extension should be split into... * ...a dataprovider and test method. */ -class IEUrlExtensionTest extends MediaWikiTestCase { +class IEUrlExtensionTest extends PHPUnit_Framework_TestCase { /** * @covers IEUrlExtension::findIE6Extension */ diff --git a/tests/phpunit/includes/libs/IPSetTest.php b/tests/phpunit/includes/libs/IPSetTest.php index d4e5214a..5bbacef4 100644 --- a/tests/phpunit/includes/libs/IPSetTest.php +++ b/tests/phpunit/includes/libs/IPSetTest.php @@ -3,7 +3,7 @@ /** * @group IPSet */ -class IPSetTest extends MediaWikiTestCase { +class IPSetTest extends PHPUnit_Framework_TestCase { /** * Provides test cases for IPSetTest::testIPSet * diff --git a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php index c8795b2e..149a28c1 100644 --- a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php +++ b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php @@ -1,6 +1,6 @@ 'ObjectFactoryTest_Fixture', + 'args' => array( function (){ return 'unwrapped'; }, ), + 'closure_expansion' => false, + ) ); + $this->assertInstanceOf( 'Closure', $obj->args[0] ); + $this->assertSame( 'unwrapped', $obj->args[0]() ); + } + + /** + * @covers ObjectFactory::getObjectFromSpec + */ + public function testClosureExpansionEnabled() { + $obj = ObjectFactory::getObjectFromSpec( array( + 'class' => 'ObjectFactoryTest_Fixture', + 'args' => array( function (){ return 'unwrapped'; }, ), + 'closure_expansion' => true, + ) ); + $this->assertInternalType( 'string', $obj->args[0] ); + $this->assertSame( 'unwrapped', $obj->args[0] ); + + $obj = ObjectFactory::getObjectFromSpec( array( + 'class' => 'ObjectFactoryTest_Fixture', + 'args' => array( function (){ return 'unwrapped'; }, ), + ) ); + $this->assertInternalType( 'string', $obj->args[0] ); + $this->assertSame( 'unwrapped', $obj->args[0] ); + } +} + +class ObjectFactoryTest_Fixture { + public $args; + public function __construct( /*...*/ ) { $this->args = func_get_args(); } +} diff --git a/tests/phpunit/includes/libs/ProcessCacheLRUTest.php b/tests/phpunit/includes/libs/ProcessCacheLRUTest.php index 1a8a1e56..43001979 100644 --- a/tests/phpunit/includes/libs/ProcessCacheLRUTest.php +++ b/tests/phpunit/includes/libs/ProcessCacheLRUTest.php @@ -9,20 +9,20 @@ * * @group Cache */ -class ProcessCacheLRUTest extends MediaWikiTestCase { +class ProcessCacheLRUTest extends PHPUnit_Framework_TestCase { /** * Helper to verify emptiness of a cache object. * Compare against an array so we get the cache content difference. */ - function assertCacheEmpty( $cache, $msg = 'Cache should be empty' ) { + protected function assertCacheEmpty( $cache, $msg = 'Cache should be empty' ) { $this->assertAttributeEquals( array(), 'cache', $cache, $msg ); } /** * Helper to fill a cache object passed by reference */ - function fillCache( &$cache, $numEntries ) { + protected function fillCache( &$cache, $numEntries ) { // Fill cache with three values for ( $i = 1; $i <= $numEntries; $i++ ) { $cache->set( "cache-key-$i", "prop-$i", "value-$i" ); @@ -33,17 +33,17 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { * Generates an array of what would be expected in cache for a given cache * size and a number of entries filled in sequentially */ - function getExpectedCache( $cacheMaxEntries, $entryToFill ) { + protected function getExpectedCache( $cacheMaxEntries, $entryToFill ) { $expected = array(); if ( $entryToFill === 0 ) { - # The cache is empty! + // The cache is empty! return array(); } elseif ( $entryToFill <= $cacheMaxEntries ) { - # Cache is not fully filled + // Cache is not fully filled $firstKey = 1; } else { - # Cache overflowed + // Cache overflowed $firstKey = 1 + $entryToFill - $cacheMaxEntries; } @@ -62,13 +62,16 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { public function testPhpUnitArrayEquality() { $one = array( 'A' => 1, 'B' => 2 ); $two = array( 'B' => 2, 'A' => 1 ); - $this->assertEquals( $one, $two ); // == - $this->assertNotSame( $one, $two ); // === + // == + $this->assertEquals( $one, $two ); + // === + $this->assertNotSame( $one, $two ); } /** * @dataProvider provideInvalidConstructorArg * @expectedException UnexpectedValueException + * @covers ProcessCacheLRU::__construct */ public function testConstructorGivenInvalidValue( $maxSize ) { new ProcessCacheLRUTestable( $maxSize ); @@ -88,6 +91,11 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { ); } + /** + * @covers ProcessCacheLRU::get + * @covers ProcessCacheLRU::set + * @covers ProcessCacheLRU::het + */ public function testAddAndGetAKey() { $oneCache = new ProcessCacheLRUTestable( 1 ); $this->assertCacheEmpty( $oneCache ); @@ -99,6 +107,10 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { $this->assertEquals( 'value1', $oneCache->get( 'cache-key', 'prop1' ) ); } + /** + * @covers ProcessCacheLRU::set + * @covers ProcessCacheLRU::get + */ public function testDeleteOldKey() { $oneCache = new ProcessCacheLRUTestable( 1 ); $this->assertCacheEmpty( $oneCache ); @@ -113,6 +125,7 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { * a sequence of always different cache-keys. Meant to verify we correclty * delete the older key. * + * @covers ProcessCacheLRU::set * @dataProvider provideCacheFilling * @param int $cacheMaxEntries Maximum entry the created cache will hold * @param int $entryToFill Number of entries to insert in the created cache. @@ -136,14 +149,18 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { return array( array( 1, 0 ), array( 1, 1 ), - array( 1, 2 ), # overflow - array( 5, 33 ), # overflow + // overflow + array( 1, 2 ), + // overflow + array( 5, 33 ), ); } /** * Create a cache with only one remaining entry then update * the first inserted entry. Should bump it to the top. + * + * @covers ProcessCacheLRU::set */ public function testReplaceExistingKeyShouldBumpEntryToTop() { $maxEntries = 3; @@ -164,6 +181,11 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { ); } + /** + * @covers ProcessCacheLRU::get + * @covers ProcessCacheLRU::set + * @covers ProcessCacheLRU::het + */ public function testRecentlyAccessedKeyStickIn() { $cache = new ProcessCacheLRUTestable( 2 ); $cache->set( 'first', 'prop1', 'value1' ); @@ -182,6 +204,9 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { * filled entry. * Given a cache having 1,2,3 as key, updating 2 should bump 2 to * the top of the queue with the new value: 1,3,2* (* = updated). + * + * @covers ProcessCacheLRU::set + * @covers ProcessCacheLRU::get */ public function testReplaceExistingKeyInAFullCacheShouldBumpToTop() { $maxEntries = 3; @@ -204,6 +229,9 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { ); } + /** + * @covers ProcessCacheLRU::set + */ public function testBumpExistingKeyToTop() { $cache = new ProcessCacheLRUTestable( 3 ); $this->fillCache( $cache, 3 ); diff --git a/tests/phpunit/includes/libs/RunningStatTest.php b/tests/phpunit/includes/libs/RunningStatTest.php index dc5db82c..edfaf162 100644 --- a/tests/phpunit/includes/libs/RunningStatTest.php +++ b/tests/phpunit/includes/libs/RunningStatTest.php @@ -3,7 +3,7 @@ * PHP Unit tests for RunningStat class. * @covers RunningStat */ -class RunningStatTest extends MediaWikiTestCase { +class RunningStatTest extends PHPUnit_Framework_TestCase { public $points = array( 49.7168, 74.3804, 7.0115, 96.5769, 34.9458, diff --git a/tests/phpunit/includes/libs/StringUtilsTest.php b/tests/phpunit/includes/libs/StringUtilsTest.php new file mode 100644 index 00000000..7c24fae6 --- /dev/null +++ b/tests/phpunit/includes/libs/StringUtilsTest.php @@ -0,0 +1,149 @@ +markTestSkipped( 'Test requires the mbstring PHP extension' ); + } + $this->assertEquals( $expected, + StringUtils::isUtf8( $string ), + 'Testing string "' . $this->escaped( $string ) . '" with mb_check_encoding' + ); + } + + /** + * This tests StringUtils::isUtf8 making sure we use the pure PHP + * implementation used as a fallback when mb_check_encoding() is + * not available. + * + * @covers StringUtils::isUtf8 + * @dataProvider provideStringsForIsUtf8Check + */ + public function testIsUtf8WithPhpFallbackImplementation( $expected, $string ) { + $this->assertEquals( $expected, + StringUtils::isUtf8( $string, /** disable mbstring: */true ), + 'Testing string "' . $this->escaped( $string ) . '" with pure PHP implementation' + ); + } + + /** + * Print high range characters as a hexadecimal + * @param string $string + * @return string + */ + function escaped( $string ) { + $escaped = ''; + $length = strlen( $string ); + for ( $i = 0; $i < $length; $i++ ) { + $char = $string[$i]; + $val = ord( $char ); + if ( $val > 127 ) { + $escaped .= '\x' . dechex( $val ); + } else { + $escaped .= $char; + } + } + + return $escaped; + } + + /** + * See also "UTF-8 decoder capability and stress test" by + * Markus Kuhn: + * http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt + */ + public static function provideStringsForIsUtf8Check() { + // Expected return values for StringUtils::isUtf8() + $PASS = true; + $FAIL = false; + + return array( + 'some ASCII' => array( $PASS, 'Some ASCII' ), + 'euro sign' => array( $PASS, "Euro sign €" ), + + 'first possible sequence 1 byte' => array( $PASS, "\x00" ), + 'first possible sequence 2 bytes' => array( $PASS, "\xc2\x80" ), + 'first possible sequence 3 bytes' => array( $PASS, "\xe0\xa0\x80" ), + 'first possible sequence 4 bytes' => array( $PASS, "\xf0\x90\x80\x80" ), + 'first possible sequence 5 bytes' => array( $FAIL, "\xf8\x88\x80\x80\x80" ), + 'first possible sequence 6 bytes' => array( $FAIL, "\xfc\x84\x80\x80\x80\x80" ), + + 'last possible sequence 1 byte' => array( $PASS, "\x7f" ), + 'last possible sequence 2 bytes' => array( $PASS, "\xdf\xbf" ), + 'last possible sequence 3 bytes' => array( $PASS, "\xef\xbf\xbf" ), + 'last possible sequence 4 bytes (U+1FFFFF)' => array( $FAIL, "\xf7\xbf\xbf\xbf" ), + 'last possible sequence 5 bytes' => array( $FAIL, "\xfb\xbf\xbf\xbf\xbf" ), + 'last possible sequence 6 bytes' => array( $FAIL, "\xfd\xbf\xbf\xbf\xbf\xbf" ), + + 'boundary 1' => array( $PASS, "\xed\x9f\xbf" ), + 'boundary 2' => array( $PASS, "\xee\x80\x80" ), + 'boundary 3' => array( $PASS, "\xef\xbf\xbd" ), + 'boundary 4' => array( $PASS, "\xf2\x80\x80\x80" ), + 'boundary 5 (U+FFFFF)' => array( $PASS, "\xf3\xbf\xbf\xbf" ), + 'boundary 6 (U+100000)' => array( $PASS, "\xf4\x80\x80\x80" ), + 'boundary 7 (U+10FFFF)' => array( $PASS, "\xf4\x8f\xbf\xbf" ), + 'boundary 8 (U+110000)' => array( $FAIL, "\xf4\x90\x80\x80" ), + + 'malformed 1' => array( $FAIL, "\x80" ), + 'malformed 2' => array( $FAIL, "\xbf" ), + 'malformed 3' => array( $FAIL, "\x80\xbf" ), + 'malformed 4' => array( $FAIL, "\x80\xbf\x80" ), + 'malformed 5' => array( $FAIL, "\x80\xbf\x80\xbf" ), + 'malformed 6' => array( $FAIL, "\x80\xbf\x80\xbf\x80" ), + 'malformed 7' => array( $FAIL, "\x80\xbf\x80\xbf\x80\xbf" ), + 'malformed 8' => array( $FAIL, "\x80\xbf\x80\xbf\x80\xbf\x80" ), + + 'last byte missing 1' => array( $FAIL, "\xc0" ), + 'last byte missing 2' => array( $FAIL, "\xe0\x80" ), + 'last byte missing 3' => array( $FAIL, "\xf0\x80\x80" ), + 'last byte missing 4' => array( $FAIL, "\xf8\x80\x80\x80" ), + 'last byte missing 5' => array( $FAIL, "\xfc\x80\x80\x80\x80" ), + 'last byte missing 6' => array( $FAIL, "\xdf" ), + 'last byte missing 7' => array( $FAIL, "\xef\xbf" ), + 'last byte missing 8' => array( $FAIL, "\xf7\xbf\xbf" ), + 'last byte missing 9' => array( $FAIL, "\xfb\xbf\xbf\xbf" ), + 'last byte missing 10' => array( $FAIL, "\xfd\xbf\xbf\xbf\xbf" ), + + 'extra continuation byte 1' => array( $FAIL, "e\xaf" ), + 'extra continuation byte 2' => array( $FAIL, "\xc3\x89\xaf" ), + 'extra continuation byte 3' => array( $FAIL, "\xef\xbc\xa5\xaf" ), + 'extra continuation byte 4' => array( $FAIL, "\xf0\x9d\x99\xb4\xaf" ), + + 'impossible bytes 1' => array( $FAIL, "\xfe" ), + 'impossible bytes 2' => array( $FAIL, "\xff" ), + 'impossible bytes 3' => array( $FAIL, "\xfe\xfe\xff\xff" ), + + 'overlong sequences 1' => array( $FAIL, "\xc0\xaf" ), + 'overlong sequences 2' => array( $FAIL, "\xc1\xaf" ), + 'overlong sequences 3' => array( $FAIL, "\xe0\x80\xaf" ), + 'overlong sequences 4' => array( $FAIL, "\xf0\x80\x80\xaf" ), + 'overlong sequences 5' => array( $FAIL, "\xf8\x80\x80\x80\xaf" ), + 'overlong sequences 6' => array( $FAIL, "\xfc\x80\x80\x80\x80\xaf" ), + + 'maximum overlong sequences 1' => array( $FAIL, "\xc1\xbf" ), + 'maximum overlong sequences 2' => array( $FAIL, "\xe0\x9f\xbf" ), + 'maximum overlong sequences 3' => array( $FAIL, "\xf0\x8f\xbf\xbf" ), + 'maximum overlong sequences 4' => array( $FAIL, "\xf8\x87\xbf\xbf" ), + 'maximum overlong sequences 5' => array( $FAIL, "\xfc\x83\xbf\xbf\xbf\xbf" ), + + 'surrogates 1 (U+D799)' => array( $PASS, "\xed\x9f\xbf" ), + 'surrogates 2 (U+E000)' => array( $PASS, "\xee\x80\x80" ), + 'surrogates 3 (U+D800)' => array( $FAIL, "\xed\xa0\x80" ), + 'surrogates 4 (U+DBFF)' => array( $FAIL, "\xed\xaf\xbf" ), + 'surrogates 5 (U+DC00)' => array( $FAIL, "\xed\xb0\x80" ), + 'surrogates 6 (U+DFFF)' => array( $FAIL, "\xed\xbf\xbf" ), + 'surrogates 7 (U+D800 U+DC00)' => array( $FAIL, "\xed\xa0\x80\xed\xb0\x80" ), + + 'noncharacters 1' => array( $PASS, "\xef\xbf\xbe" ), + 'noncharacters 2' => array( $PASS, "\xef\xbf\xbf" ), + ); + } +} diff --git a/tests/phpunit/includes/libs/XhprofTest.php b/tests/phpunit/includes/libs/XhprofTest.php new file mode 100644 index 00000000..2440fc08 --- /dev/null +++ b/tests/phpunit/includes/libs/XhprofTest.php @@ -0,0 +1,320 @@ + + * @copyright © 2014 Bryan Davis and Wikimedia Foundation. + * @since 1.25 + */ +class XhprofTest extends PHPUnit_Framework_TestCase { + + public function setUp() { + if ( !function_exists( 'xhprof_enable' ) ) { + $this->markTestSkipped( 'No xhprof support detected.' ); + } + } + + /** + * @covers Xhprof::splitKey + * @dataProvider provideSplitKey + */ + public function testSplitKey( $key, $expect ) { + $this->assertSame( $expect, Xhprof::splitKey( $key ) ); + } + + public function provideSplitKey() { + return array( + array( 'main()', array( null, 'main()' ) ), + array( 'foo==>bar', array( 'foo', 'bar' ) ), + array( 'bar@1==>bar@2', array( 'bar@1', 'bar@2' ) ), + array( 'foo==>bar==>baz', array( 'foo', 'bar==>baz' ) ), + array( '==>bar', array( '', 'bar' ) ), + array( '', array( null, '' ) ), + ); + } + + /** + * @covers Xhprof::__construct + * @covers Xhprof::stop + * @covers Xhprof::getRawData + * @dataProvider provideRawData + */ + public function testRawData( $flags, $keys ) { + $xhprof = new Xhprof( array( 'flags' => $flags ) ); + $raw = $xhprof->getRawData(); + $this->assertArrayHasKey( 'main()', $raw ); + foreach ( $keys as $key ) { + $this->assertArrayHasKey( $key, $raw['main()'] ); + } + } + + public function provideRawData() { + $tests = array( + array( 0, array( 'ct', 'wt' ) ), + ); + + if ( defined( 'XHPROF_FLAGS_CPU' ) && defined( 'XHPROF_FLAGS_CPU' ) ) { + $tests[] = array( XHPROF_FLAGS_MEMORY, array( + 'ct', 'wt', 'mu', 'pmu', + ) ); + $tests[] = array( XHPROF_FLAGS_CPU, array( + 'ct', 'wt', 'cpu', + ) ); + $tests[] = array( XHPROF_FLAGS_MEMORY | XHPROF_FLAGS_CPU, array( + 'ct', 'wt', 'mu', 'pmu', 'cpu', + ) ); + } + + return $tests; + } + + /** + * @covers Xhprof::pruneData + */ + public function testInclude() { + $xhprof = $this->getXhprofFixture( array( + 'include' => array( 'main()' ), + ) ); + $raw = $xhprof->getRawData(); + $this->assertArrayHasKey( 'main()', $raw ); + $this->assertArrayHasKey( 'main()==>foo', $raw ); + $this->assertArrayHasKey( 'main()==>xhprof_disable', $raw ); + $this->assertSame( 3, count( $raw ) ); + } + + /** + * Validate the structure of data returned by + * Xhprof::getInclusiveMetrics(). This acts as a guard against unexpected + * structural changes to the returned data in lieu of using a more heavy + * weight typed response object. + * + * @covers Xhprof::getInclusiveMetrics + */ + public function testInclusiveMetricsStructure() { + $metricStruct = array( + 'ct' => 'int', + 'wt' => 'array', + 'cpu' => 'array', + 'mu' => 'array', + 'pmu' => 'array', + ); + $statStruct = array( + 'total' => 'numeric', + 'min' => 'numeric', + 'mean' => 'numeric', + 'max' => 'numeric', + 'variance' => 'numeric', + 'percent' => 'numeric', + ); + + $xhprof = $this->getXhprofFixture(); + $metrics = $xhprof->getInclusiveMetrics(); + + foreach ( $metrics as $name => $metric ) { + $this->assertArrayStructure( $metricStruct, $metric ); + + foreach ( $metricStruct as $key => $type ) { + if ( $type === 'array' ) { + $this->assertArrayStructure( $statStruct, $metric[$key] ); + if ( $name === 'main()' ) { + $this->assertEquals( 100, $metric[$key]['percent'] ); + } + } + } + } + } + + /** + * Validate the structure of data returned by + * Xhprof::getCompleteMetrics(). This acts as a guard against unexpected + * structural changes to the returned data in lieu of using a more heavy + * weight typed response object. + * + * @covers Xhprof::getCompleteMetrics + */ + public function testCompleteMetricsStructure() { + $metricStruct = array( + 'ct' => 'int', + 'wt' => 'array', + 'cpu' => 'array', + 'mu' => 'array', + 'pmu' => 'array', + 'calls' => 'array', + 'subcalls' => 'array', + ); + $statsMetrics = array( 'wt', 'cpu', 'mu', 'pmu' ); + $statStruct = array( + 'total' => 'numeric', + 'min' => 'numeric', + 'mean' => 'numeric', + 'max' => 'numeric', + 'variance' => 'numeric', + 'percent' => 'numeric', + 'exclusive' => 'numeric', + ); + + $xhprof = $this->getXhprofFixture(); + $metrics = $xhprof->getCompleteMetrics(); + + foreach ( $metrics as $name => $metric ) { + $this->assertArrayStructure( $metricStruct, $metric, $name ); + + foreach ( $metricStruct as $key => $type ) { + if ( in_array( $key, $statsMetrics ) ) { + $this->assertArrayStructure( + $statStruct, $metric[$key], $key + ); + $this->assertLessThanOrEqual( + $metric[$key]['total'], $metric[$key]['exclusive'] + ); + } + } + } + } + + /** + * @covers Xhprof::getCallers + * @covers Xhprof::getCallees + * @uses Xhprof + */ + public function testEdges() { + $xhprof = $this->getXhprofFixture(); + $this->assertSame( array(), $xhprof->getCallers( 'main()' ) ); + $this->assertSame( array( 'foo', 'xhprof_disable' ), + $xhprof->getCallees( 'main()' ) + ); + $this->assertSame( array( 'main()' ), + $xhprof->getCallers( 'foo' ) + ); + $this->assertSame( array(), $xhprof->getCallees( 'strlen' ) ); + } + + /** + * @covers Xhprof::getCriticalPath + * @uses Xhprof + */ + public function testCriticalPath() { + $xhprof = $this->getXhprofFixture(); + $path = $xhprof->getCriticalPath(); + + $last = null; + foreach ( $path as $key => $value ) { + list( $func, $call ) = Xhprof::splitKey( $key ); + $this->assertSame( $last, $func ); + $last = $call; + } + $this->assertSame( $last, 'bar@1' ); + } + + /** + * Get an Xhprof instance that has been primed with a set of known testing + * data. Tests for the Xhprof class should laregly be concerned with + * evaluating the manipulations of the data collected by xhprof rather + * than the data collection process itself. + * + * The returned Xhprof instance primed will be with a data set created by + * running this trivial program using the PECL xhprof implementation: + * @code + * function bar( $x ) { + * if ( $x > 0 ) { + * bar($x - 1); + * } + * } + * function foo() { + * for ( $idx = 0; $idx < 2; $idx++ ) { + * bar( $idx ); + * $x = strlen( 'abc' ); + * } + * } + * xhprof_enable( XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY ); + * foo(); + * $x = xhprof_disable(); + * var_export( $x ); + * @endcode + * + * @return Xhprof + */ + protected function getXhprofFixture( array $opts = array() ) { + $xhprof = new Xhprof( $opts ); + $xhprof->loadRawData( array ( + 'foo==>bar' => array ( + 'ct' => 2, + 'wt' => 57, + 'cpu' => 92, + 'mu' => 1896, + 'pmu' => 0, + ), + 'foo==>strlen' => array ( + 'ct' => 2, + 'wt' => 21, + 'cpu' => 141, + 'mu' => 752, + 'pmu' => 0, + ), + 'bar==>bar@1' => array ( + 'ct' => 1, + 'wt' => 18, + 'cpu' => 19, + 'mu' => 752, + 'pmu' => 0, + ), + 'main()==>foo' => array ( + 'ct' => 1, + 'wt' => 304, + 'cpu' => 307, + 'mu' => 4008, + 'pmu' => 0, + ), + 'main()==>xhprof_disable' => array ( + 'ct' => 1, + 'wt' => 8, + 'cpu' => 10, + 'mu' => 768, + 'pmu' => 392, + ), + 'main()' => array ( + 'ct' => 1, + 'wt' => 353, + 'cpu' => 351, + 'mu' => 6112, + 'pmu' => 1424, + ), + ) ); + return $xhprof; + } + + /** + * Assert that the given array has the described structure. + * + * @param array $struct Array of key => type mappings + * @param array $actual Array to check + * @param string $label + */ + protected function assertArrayStructure( $struct, $actual, $label = null ) { + $this->assertInternalType( 'array', $actual, $label ); + $this->assertCount( count($struct), $actual, $label ); + foreach ( $struct as $key => $type ) { + $this->assertArrayHasKey( $key, $actual ); + $this->assertInternalType( $type, $actual[$key] ); + } + } +} diff --git a/tests/phpunit/includes/libs/XmlTypeCheckTest.php b/tests/phpunit/includes/libs/XmlTypeCheckTest.php new file mode 100644 index 00000000..f0ba934e --- /dev/null +++ b/tests/phpunit/includes/libs/XmlTypeCheckTest.php @@ -0,0 +1,49 @@ +"; + const MAL_FORMED_XML = ""; + 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 ); + } + + /** + * @covers XMLTypeCheck::processingInstructionHandler + */ + public function testProcessingInstructionHandler() { + $called = false; + $testXML = new XmlTypeCheck( + self::XML_WITH_PIH, + null, + false, + array( + 'processing_instruction_handler' => function() use ( &$called ) { + $called = true; + } + ) + ); + $this->assertTrue( $called ); + } + +} diff --git a/tests/phpunit/includes/libs/composer/ComposerJsonTest.php b/tests/phpunit/includes/libs/composer/ComposerJsonTest.php new file mode 100644 index 00000000..0c58b65a --- /dev/null +++ b/tests/phpunit/includes/libs/composer/ComposerJsonTest.php @@ -0,0 +1,57 @@ +json = "$IP/tests/phpunit/data/composer/composer.json"; + $this->json2 = "$IP/tests/phpunit/data/composer/new-composer.json"; + } + + public static function provideGetHash() { + return array( + array( 'json', 'cc6e7fc565b246cb30b0cac103a2b31e' ), + array( 'json2', '19921dd1fc457f1b00561da932432001' ), + ); + } + + /** + * @dataProvider provideGetHash + * @covers ComposerJson::getHash + */ + public function testIsHashUpToDate( $file, $expected ) { + $json = new ComposerJson( $this->$file ); + $this->assertEquals( $expected, $json->getHash() ); + } + + /** + * @covers ComposerJson::getRequiredDependencies + */ + public function testGetRequiredDependencies() { + $json = new ComposerJson( $this->json ); + $this->assertArrayEquals( array( + 'cdb/cdb' => '1.0.0', + 'cssjanus/cssjanus' => '1.1.1', + 'leafo/lessphp' => '0.5.0', + 'psr/log' => '1.0.0', + ), $json->getRequiredDependencies(), false, true ); + } + + public static function provideNormalizeVersion() { + return array( + array( 'v1.0.0', '1.0.0' ), + array( '0.0.5', '0.0.5' ), + ); + } + + /** + * @dataProvider provideNormalizeVersion + * @covers ComposerJson::normalizeVersion + */ + public function testNormalizeVersion( $input, $expected ) { + $this->assertEquals( $expected, ComposerJson::normalizeVersion( $input ) ); + } +} diff --git a/tests/phpunit/includes/libs/composer/ComposerLockTest.php b/tests/phpunit/includes/libs/composer/ComposerLockTest.php new file mode 100644 index 00000000..b5fd5f6e --- /dev/null +++ b/tests/phpunit/includes/libs/composer/ComposerLockTest.php @@ -0,0 +1,62 @@ +lock = "$IP/tests/phpunit/data/composer/composer.lock"; + } + + /** + * @covers ComposerLock::getHash + */ + public function testGetHash() { + $lock = new ComposerLock( $this->lock ); + $this->assertEquals( 'a3bb80b0ac4c4a31e52574d48c032923', $lock->getHash() ); + } + + /** + * @covers ComposerLock::getInstalledDependencies + */ + public function testGetInstalledDependencies() { + $lock = new ComposerLock( $this->lock ); + $this->assertArrayEquals( array( + 'wikimedia/cdb' => array( + 'version' => '1.0.1', + 'type' => 'library', + ), + 'cssjanus/cssjanus' => array( + 'version' => '1.1.1', + 'type' => 'library', + ), + 'leafo/lessphp' => array( + 'version' => '0.5.0', + 'type' => 'library', + ), + 'psr/log' => array( + 'version' => '1.0.0', + 'type' => 'library', + ), + 'oojs/oojs-ui' => array( + 'version' => '0.6.0', + 'type' => 'library', + ), + 'composer/installers' => array( + 'version' => '1.0.19', + 'type' => 'composer-installer', + ), + 'mediawiki/translate' => array( + 'version' => '2014.12', + 'type' => 'mediawiki-extension', + ), + 'mediawiki/universal-language-selector' => array( + 'version' => '2014.12', + 'type' => 'mediawiki-extension', + ), + ), $lock->getInstalledDependencies(), false, true ); + } + +} diff --git a/tests/phpunit/includes/logging/LogFormatterTest.php b/tests/phpunit/includes/logging/LogFormatterTest.php index 6210d098..515990e6 100644 --- a/tests/phpunit/includes/logging/LogFormatterTest.php +++ b/tests/phpunit/includes/logging/LogFormatterTest.php @@ -239,4 +239,57 @@ class LogFormatterTest extends MediaWikiLangTestCase { $this->assertEquals( $comment, $formatter->getComment() ); } + + /** + * @dataProvider provideApiParamFormatting + * @covers LogFormatter::formatParametersForApi + * @covers LogFormatter::formatParameterValueForApi + */ + public function testApiParamFormatting( $key, $value, $expected ) { + $entry = $this->newLogEntry( 'param', array( $key => $value ) ); + $formatter = LogFormatter::newFromEntry( $entry ); + $formatter->setContext( $this->context ); + + ApiResult::setIndexedTagName( $expected, 'param' ); + ApiResult::setArrayType( $expected, 'assoc' ); + + $this->assertEquals( $expected, $formatter->formatParametersForApi() ); + } + + public static function provideApiParamFormatting() { + return array( + array( 0, 'value', array( 'value' ) ), + array( 'named', 'value', array( 'named' => 'value' ) ), + array( '::key', 'value', array( 'key' => 'value' ) ), + array( '4::key', 'value', array( 'key' => 'value' ) ), + array( '4:raw:key', 'value', array( 'key' => 'value' ) ), + array( '4:plain:key', 'value', array( 'key' => 'value' ) ), + array( '4:bool:key', '1', array( 'key' => true ) ), + array( '4:bool:key', '0', array( 'key' => false ) ), + array( '4:number:key', '123', array( 'key' => 123 ) ), + array( '4:number:key', '123.5', array( 'key' => 123.5 ) ), + array( '4:array:key', array(), array( 'key' => array( ApiResult::META_TYPE => 'array' ) ) ), + array( '4:assoc:key', array(), array( 'key' => array( ApiResult::META_TYPE => 'assoc' ) ) ), + array( '4:kvp:key', array(), array( 'key' => array( ApiResult::META_TYPE => 'kvp' ) ) ), + array( '4:timestamp:key', '20150102030405', array( 'key' => '2015-01-02T03:04:05Z' ) ), + array( '4:msg:key', 'parentheses', array( + 'key_key' => 'parentheses', + 'key_text' => wfMessage( 'parentheses' )->text(), + ) ), + array( '4:msg-content:key', 'parentheses', array( + 'key_key' => 'parentheses', + 'key_text' => wfMessage( 'parentheses' )->inContentLanguage()->text(), + ) ), + array( '4:title:key', 'project:foo', array( + 'key_ns' => NS_PROJECT, + 'key_title' => Title::newFromText( 'project:foo' )->getFullText(), + ) ), + array( '4:title-link:key', 'project:foo', array( + 'key_ns' => NS_PROJECT, + 'key_title' => Title::newFromText( 'project:foo' )->getFullText(), + ) ), + array( '4:user:key', 'foo', array( 'key' => 'Foo' ) ), + array( '4:user-link:key', 'foo', array( 'key' => 'Foo' ) ), + ); + } } diff --git a/tests/phpunit/includes/mail/MailAddressTest.php b/tests/phpunit/includes/mail/MailAddressTest.php index 2d078120..18d2acdf 100644 --- a/tests/phpunit/includes/mail/MailAddressTest.php +++ b/tests/phpunit/includes/mail/MailAddressTest.php @@ -14,6 +14,9 @@ class MailAddressTest extends MediaWikiTestCase { * @covers MailAddress::newFromUser */ public function testNewFromUser() { + if ( wfIsWindows() ) { + $this->markTestSkipped( 'This test only works on non-Windows platforms' ); + } $user = $this->getMock( 'User' ); $user->expects( $this->any() )->method( 'getName' )->will( $this->returnValue( 'UserName' ) ); $user->expects( $this->any() )->method( 'getEmail' )->will( $this->returnValue( 'foo@bar.baz' ) ); @@ -49,6 +52,7 @@ class MailAddressTest extends MediaWikiTestCase { array( false, 'foo@bar.baz', 'AUserName', 'Some real name', 'AUserName ' ), array( false, 'foo@bar.baz', '', '', 'foo@bar.baz' ), array( true, 'foo@bar.baz', '', '', 'foo@bar.baz' ), + array( true, '', '', '', '' ), ); } @@ -59,5 +63,4 @@ class MailAddressTest extends MediaWikiTestCase { $ma = new MailAddress( 'some@email.com', 'UserName', 'A real name' ); $this->assertEquals( $ma->toString(), (string)$ma ); } - -} \ No newline at end of file +} diff --git a/tests/phpunit/includes/media/BitmapScalingTest.php b/tests/phpunit/includes/media/BitmapScalingTest.php index 1972c969..e4415ece 100644 --- a/tests/phpunit/includes/media/BitmapScalingTest.php +++ b/tests/phpunit/includes/media/BitmapScalingTest.php @@ -113,7 +113,7 @@ class BitmapScalingTest extends MediaWikiTestCase { $file = new FakeDimensionFile( array( 4000, 4000 ) ); $handler = new BitmapHandler; $params = array( 'width' => '3700' ); // Still bigger than max size. - $this->assertEquals( 'TransformParameterError', + $this->assertEquals( 'TransformTooBigImageAreaError', get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) ); } @@ -125,7 +125,7 @@ class BitmapScalingTest extends MediaWikiTestCase { $file->mustRender = true; $handler = new BitmapHandler; $params = array( 'width' => '5000' ); // Still bigger than max size. - $this->assertEquals( 'TransformParameterError', + $this->assertEquals( 'TransformTooBigImageAreaError', get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) ); } diff --git a/tests/phpunit/includes/media/FormatMetadataTest.php b/tests/phpunit/includes/media/FormatMetadataTest.php index 002e2cb9..54758f94 100644 --- a/tests/phpunit/includes/media/FormatMetadataTest.php +++ b/tests/phpunit/includes/media/FormatMetadataTest.php @@ -68,4 +68,36 @@ class FormatMetadataTest extends MediaWikiMediaTestCase { // TODO: more test cases ); } + + /** + * @param mixed $input + * @param mixed $output + * @dataProvider provideResolveMultivalueValue + * @covers FormatMetadata::resolveMultivalueValue + */ + public function testResolveMultivalueValue( $input, $output ) { + $formatMetadata = new FormatMetadata(); + $class = new ReflectionClass( 'FormatMetadata' ); + $method = $class->getMethod( 'resolveMultivalueValue' ); + $method->setAccessible( true ); + $actualInput = $method->invoke( $formatMetadata, $input ); + $this->assertEquals( $output, $actualInput ); + } + + public function provideResolveMultivalueValue() { + return array( + 'nonArray' => array( 'foo', 'foo' ), + 'multiValue' => array( array( 'first', 'second', 'third', '_type' => 'ol' ), 'first' ), + 'noType' => array( array( 'first', 'second', 'third' ), 'first' ), + 'typeFirst' => array( array( '_type' => 'ol', 'first', 'second', 'third' ), 'first' ), + 'multilang' => array( + array( 'en' => 'first', 'de' => 'Erste', '_type' => 'lang' ), + array( 'en' => 'first', 'de' => 'Erste', '_type' => 'lang' ), + ), + 'multilang-multivalue' => array( + array( 'en' => array( 'first', 'second' ), 'de' => array( 'Erste', 'Zweite' ), '_type' => 'lang' ), + array( 'en' => 'first', 'de' => 'Erste', '_type' => 'lang' ), + ), + ); + } } diff --git a/tests/phpunit/includes/media/MediaHandlerTest.php b/tests/phpunit/includes/media/MediaHandlerTest.php index d8cfcc45..78ea9530 100644 --- a/tests/phpunit/includes/media/MediaHandlerTest.php +++ b/tests/phpunit/includes/media/MediaHandlerTest.php @@ -7,50 +7,62 @@ class MediaHandlerTest extends MediaWikiTestCase { /** * @covers MediaHandler::fitBoxWidth - * @todo split into a dataprovider and test method + * + * @dataProvider provideTestFitBoxWidth */ - public function testFitBoxWidth() { - $vals = array( - array( - 'width' => 50, - 'height' => 50, - 'tests' => array( + public function testFitBoxWidth( $width, $height, $max, $expected ) { + $y = round( $expected * $height / $width ); + $result = MediaHandler::fitBoxWidth( $width, $height, $max ); + $y2 = round( $result * $height / $width ); + $this->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, array( 50 => 50, 17 => 17, - 18 => 18 ) ), - array( - 'width' => 366, - 'height' => 300, - 'tests' => array( + 18 => 18 ) + ), + static::generateTestFitBoxWidthData( 366, 300, array( 50 => 61, 17 => 21, - 18 => 22 ) ), - array( - 'width' => 300, - 'height' => 366, - 'tests' => array( + 18 => 22 ) + ), + static::generateTestFitBoxWidthData( 300, 366, array( 50 => 41, 17 => 14, - 18 => 15 ) ), - array( - 'width' => 100, - 'height' => 400, - 'tests' => array( + 18 => 15 ) + ), + static::generateTestFitBoxWidthData( 100, 400, array( 50 => 12, 17 => 4, - 18 => 4 ) ) ); - foreach ( $vals as $row ) { - $tests = $row['tests']; - $height = $row['height']; - $width = $row['width']; - foreach ( $tests as $max => $expected ) { - $y = round( $expected * $height / $width ); - $result = MediaHandler::fitBoxWidth( $width, $height, $max ); - $y2 = round( $result * $height / $width ); - $this->assertEquals( $expected, - $result, - "($width, $height, $max) wanted: {$expected}x$y, got: {$result}x$y2" ); - } + 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 $width int + * @param $height int + * @param $tests array associative array of $max => $expected values + * @return array + */ + private static function generateTestFitBoxWidthData( $width, $height, $tests ) { + $result = array(); + foreach ( $tests as $max => $expected ) { + $result[] = array( $width, $height, $max, $expected ); } + return $result; } } diff --git a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php index ab33d1c2..0241aec4 100644 --- a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php +++ b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php @@ -6,11 +6,6 @@ */ class SVGMetadataExtractorTest extends MediaWikiTestCase { - protected function setUp() { - parent::setUp(); - AutoLoader::loadClass( 'SVGMetadataExtractorTest' ); - } - /** * @dataProvider provideSvgFiles */ diff --git a/tests/phpunit/includes/normal/CleanUpTest.php b/tests/phpunit/includes/normal/CleanUpTest.php deleted file mode 100644 index f4b469b8..00000000 --- a/tests/phpunit/includes/normal/CleanUpTest.php +++ /dev/null @@ -1,409 +0,0 @@ - - * https://www.mediawiki.org/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - */ - -/** - * Additional tests for UtfNormal::cleanUp() function, inclusion - * regression checks for known problems. - * Requires PHPUnit. - * - * @ingroup UtfNormal - * @group Large - * - * @todo covers tags, will be UtfNormal::cleanUp once the below is resolved - * @todo split me into test methods and providers per the below comment - * - * We ignore code coverage for this test suite until they are rewritten - * to use data providers (bug 46561). - * @codeCoverageIgnore - */ -class CleanUpTest extends MediaWikiTestCase { - /** @todo document */ - public function testAscii() { - $text = 'This is plain ASCII text.'; - $this->assertEquals( $text, UtfNormal::cleanUp( $text ) ); - } - - /** @todo document */ - public function testNull() { - $text = "a \x00 null"; - $expect = "a \xef\xbf\xbd null"; - $this->assertEquals( - bin2hex( $expect ), - bin2hex( UtfNormal::cleanUp( $text ) ) ); - } - - /** @todo document */ - public function testLatin() { - $text = "L'\xc3\xa9cole"; - $this->assertEquals( $text, UtfNormal::cleanUp( $text ) ); - } - - /** @todo document */ - public function testLatinNormal() { - $text = "L'e\xcc\x81cole"; - $expect = "L'\xc3\xa9cole"; - $this->assertEquals( $expect, UtfNormal::cleanUp( $text ) ); - } - - /** - * This test is *very* expensive! - * @todo document - */ - function XtestAllChars() { - $rep = UTF8_REPLACEMENT; - for ( $i = 0x0; $i < UNICODE_MAX; $i++ ) { - $char = codepointToUtf8( $i ); - $clean = UtfNormal::cleanUp( $char ); - $x = sprintf( "%04X", $i ); - - if ( $i % 0x1000 == 0 ) { - echo "U+$x\n"; - } - - if ( $i == 0x0009 || - $i == 0x000a || - $i == 0x000d || - ( $i > 0x001f && $i < UNICODE_SURROGATE_FIRST ) || - ( $i > UNICODE_SURROGATE_LAST && $i < 0xfffe ) || - ( $i > 0xffff && $i <= UNICODE_MAX ) - ) { - if ( isset( UtfNormal::$utfCanonicalComp[$char] ) - || isset( UtfNormal::$utfCanonicalDecomp[$char] ) - ) { - $comp = UtfNormal::NFC( $char ); - $this->assertEquals( - bin2hex( $comp ), - bin2hex( $clean ), - "U+$x should be decomposed" ); - } else { - $this->assertEquals( - bin2hex( $char ), - bin2hex( $clean ), - "U+$x should be intact" ); - } - } else { - $this->assertEquals( bin2hex( $rep ), bin2hex( $clean ), $x ); - } - } - } - - /** @todo document */ - public static function provideAllBytes() { - return array( - array( '', '' ), - array( 'x', '' ), - array( '', 'x' ), - array( 'x', 'x' ), - ); - } - - /** - * @dataProvider provideAllBytes - * @todo document - */ - function testBytes( $head, $tail ) { - for ( $i = 0x0; $i < 256; $i++ ) { - $char = $head . chr( $i ) . $tail; - $clean = UtfNormal::cleanUp( $char ); - $x = sprintf( "%02X", $i ); - - if ( $i == 0x0009 || - $i == 0x000a || - $i == 0x000d || - ( $i > 0x001f && $i < 0x80 ) - ) { - $this->assertEquals( - bin2hex( $char ), - bin2hex( $clean ), - "ASCII byte $x should be intact" ); - if ( $char != $clean ) { - return; - } - } else { - $norm = $head . UTF8_REPLACEMENT . $tail; - $this->assertEquals( - bin2hex( $norm ), - bin2hex( $clean ), - "Forbidden byte $x should be rejected" ); - if ( $norm != $clean ) { - return; - } - } - } - } - - /** - * @dataProvider provideAllBytes - * @todo document - */ - function testDoubleBytes( $head, $tail ) { - for ( $first = 0xc0; $first < 0x100; $first += 2 ) { - for ( $second = 0x80; $second < 0x100; $second += 2 ) { - $char = $head . chr( $first ) . chr( $second ) . $tail; - $clean = UtfNormal::cleanUp( $char ); - $x = sprintf( "%02X,%02X", $first, $second ); - if ( $first > 0xc1 && - $first < 0xe0 && - $second < 0xc0 - ) { - $norm = UtfNormal::NFC( $char ); - $this->assertEquals( - bin2hex( $norm ), - bin2hex( $clean ), - "Pair $x should be intact" ); - if ( $norm != $clean ) { - return; - } - } elseif ( $first > 0xfd || $second > 0xbf ) { - # fe and ff are not legal head bytes -- expect two replacement chars - $norm = $head . UTF8_REPLACEMENT . UTF8_REPLACEMENT . $tail; - $this->assertEquals( - bin2hex( $norm ), - bin2hex( $clean ), - "Forbidden pair $x should be rejected" ); - if ( $norm != $clean ) { - return; - } - } else { - $norm = $head . UTF8_REPLACEMENT . $tail; - $this->assertEquals( - bin2hex( $norm ), - bin2hex( $clean ), - "Forbidden pair $x should be rejected" ); - if ( $norm != $clean ) { - return; - } - } - } - } - } - - /** - * @dataProvider provideAllBytes - * @todo document - */ - function testTripleBytes( $head, $tail ) { - for ( $first = 0xc0; $first < 0x100; $first += 2 ) { - for ( $second = 0x80; $second < 0x100; $second += 2 ) { - #for( $third = 0x80; $third < 0x100; $third++ ) { - for ( $third = 0x80; $third < 0x81; $third++ ) { - $char = $head . chr( $first ) . chr( $second ) . chr( $third ) . $tail; - $clean = UtfNormal::cleanUp( $char ); - $x = sprintf( "%02X,%02X,%02X", $first, $second, $third ); - - if ( $first >= 0xe0 && - $first < 0xf0 && - $second < 0xc0 && - $third < 0xc0 - ) { - if ( $first == 0xe0 && $second < 0xa0 ) { - $this->assertEquals( - bin2hex( $head . UTF8_REPLACEMENT . $tail ), - bin2hex( $clean ), - "Overlong triplet $x should be rejected" ); - } elseif ( $first == 0xed && - ( chr( $first ) . chr( $second ) . chr( $third ) ) >= UTF8_SURROGATE_FIRST - ) { - $this->assertEquals( - bin2hex( $head . UTF8_REPLACEMENT . $tail ), - bin2hex( $clean ), - "Surrogate triplet $x should be rejected" ); - } else { - $this->assertEquals( - bin2hex( UtfNormal::NFC( $char ) ), - bin2hex( $clean ), - "Triplet $x should be intact" ); - } - } elseif ( $first > 0xc1 && $first < 0xe0 && $second < 0xc0 ) { - $this->assertEquals( - bin2hex( UtfNormal::NFC( $head . chr( $first ) . - chr( $second ) ) . UTF8_REPLACEMENT . $tail ), - bin2hex( $clean ), - "Valid 2-byte $x + broken tail" ); - } elseif ( $second > 0xc1 && $second < 0xe0 && $third < 0xc0 ) { - $this->assertEquals( - bin2hex( $head . UTF8_REPLACEMENT . - UtfNormal::NFC( chr( $second ) . chr( $third ) . $tail ) ), - bin2hex( $clean ), - "Broken head + valid 2-byte $x" ); - } elseif ( ( $first > 0xfd || $second > 0xfd ) && - ( ( $second > 0xbf && $third > 0xbf ) || - ( $second < 0xc0 && $third < 0xc0 ) || - ( $second > 0xfd ) || - ( $third > 0xfd ) ) - ) { - # fe and ff are not legal head bytes -- expect three replacement chars - $this->assertEquals( - bin2hex( $head . UTF8_REPLACEMENT . UTF8_REPLACEMENT . UTF8_REPLACEMENT . $tail ), - bin2hex( $clean ), - "Forbidden triplet $x should be rejected" ); - } elseif ( $first > 0xc2 && $second < 0xc0 && $third < 0xc0 ) { - $this->assertEquals( - bin2hex( $head . UTF8_REPLACEMENT . $tail ), - bin2hex( $clean ), - "Forbidden triplet $x should be rejected" ); - } else { - $this->assertEquals( - bin2hex( $head . UTF8_REPLACEMENT . UTF8_REPLACEMENT . $tail ), - bin2hex( $clean ), - "Forbidden triplet $x should be rejected" ); - } - } - } - } - } - - /** @todo document */ - public function testChunkRegression() { - # Check for regression against a chunking bug - $text = "\x46\x55\xb8" . - "\xdc\x96" . - "\xee" . - "\xe7" . - "\x44" . - "\xaa" . - "\x2f\x25"; - $expect = "\x46\x55\xef\xbf\xbd" . - "\xdc\x96" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\x44" . - "\xef\xbf\xbd" . - "\x2f\x25"; - - $this->assertEquals( - bin2hex( $expect ), - bin2hex( UtfNormal::cleanUp( $text ) ) ); - } - - /** @todo document */ - public function testInterposeRegression() { - $text = "\x4e\x30" . - "\xb1" . # bad tail - "\x3a" . - "\x92" . # bad tail - "\x62\x3a" . - "\x84" . # bad tail - "\x43" . - "\xc6" . # bad head - "\x3f" . - "\x92" . # bad tail - "\xad" . # bad tail - "\x7d" . - "\xd9\x95"; - - $expect = "\x4e\x30" . - "\xef\xbf\xbd" . - "\x3a" . - "\xef\xbf\xbd" . - "\x62\x3a" . - "\xef\xbf\xbd" . - "\x43" . - "\xef\xbf\xbd" . - "\x3f" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\x7d" . - "\xd9\x95"; - - $this->assertEquals( - bin2hex( $expect ), - bin2hex( UtfNormal::cleanUp( $text ) ) ); - } - - /** @todo document */ - public function testOverlongRegression() { - $text = "\x67" . - "\x1a" . # forbidden ascii - "\xea" . # bad head - "\xc1\xa6" . # overlong sequence - "\xad" . # bad tail - "\x1c" . # forbidden ascii - "\xb0" . # bad tail - "\x3c" . - "\x9e"; # bad tail - $expect = "\x67" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\x3c" . - "\xef\xbf\xbd"; - $this->assertEquals( - bin2hex( $expect ), - bin2hex( UtfNormal::cleanUp( $text ) ) ); - } - - /** @todo document */ - public function testSurrogateRegression() { - $text = "\xed\xb4\x96" . # surrogate 0xDD16 - "\x83" . # bad tail - "\xb4" . # bad tail - "\xac"; # bad head - $expect = "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd"; - $this->assertEquals( - bin2hex( $expect ), - bin2hex( UtfNormal::cleanUp( $text ) ) ); - } - - /** @todo document */ - public function testBomRegression() { - $text = "\xef\xbf\xbe" . # U+FFFE, illegal char - "\xb2" . # bad tail - "\xef" . # bad head - "\x59"; - $expect = "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\x59"; - $this->assertEquals( - bin2hex( $expect ), - bin2hex( UtfNormal::cleanUp( $text ) ) ); - } - - /** @todo document */ - public function testForbiddenRegression() { - $text = "\xef\xbf\xbf"; # U+FFFF, illegal char - $expect = "\xef\xbf\xbd"; - $this->assertEquals( - bin2hex( $expect ), - bin2hex( UtfNormal::cleanUp( $text ) ) ); - } - - /** @todo document */ - public function testHangulRegression() { - $text = "\xed\x9c\xaf" . # Hangul char - "\xe1\x87\x81"; # followed by another final jamo - $expect = $text; # Should *not* change. - $this->assertEquals( - bin2hex( $expect ), - bin2hex( UtfNormal::cleanUp( $text ) ) ); - } -} diff --git a/tests/phpunit/includes/objectcache/BagOStuffTest.php b/tests/phpunit/includes/objectcache/BagOStuffTest.php index 987b6e64..4516bb4e 100644 --- a/tests/phpunit/includes/objectcache/BagOStuffTest.php +++ b/tests/phpunit/includes/objectcache/BagOStuffTest.php @@ -1,8 +1,6 @@ + * @author Matthias Mullie */ class BagOStuffTest extends MediaWikiTestCase { private $cache; @@ -23,6 +21,10 @@ class BagOStuffTest extends MediaWikiTestCase { $this->cache->delete( wfMemcKey( 'test' ) ); } + /** + * @covers BagOStuff::merge + * @covers BagOStuff::mergeViaLock + */ public function testMerge() { $key = wfMemcKey( 'test' ); @@ -100,6 +102,9 @@ class BagOStuffTest extends MediaWikiTestCase { } } + /** + * @covers BagOStuff::add + */ public function testAdd() { $key = wfMemcKey( 'test' ); $this->assertTrue( $this->cache->add( $key, 'test' ) ); @@ -125,6 +130,9 @@ class BagOStuffTest extends MediaWikiTestCase { $this->assertEquals( $expectedValue, $actualValue, 'Value should be 1 after incrementing' ); } + /** + * @covers BagOStuff::getMulti + */ public function testGetMulti() { $value1 = array( 'this' => 'is', 'a' => 'test' ); $value2 = array( 'this' => 'is', 'another' => 'test' ); diff --git a/tests/phpunit/includes/page/ArticleTablesTest.php b/tests/phpunit/includes/page/ArticleTablesTest.php new file mode 100644 index 00000000..9f2b7a05 --- /dev/null +++ b/tests/phpunit/includes/page/ArticleTablesTest.php @@ -0,0 +1,53 @@ +mRights = array( 'createpage', 'edit', 'purge' ); + $this->setMwGlobals( 'wgLanguageCode', 'es' ); + $this->setMwGlobals( 'wgContLang', Language::factory( 'es' ) ); + $this->setMwGlobals( 'wgLang', Language::factory( 'fr' ) ); + + $page->doEditContent( + new WikitextContent( '{{:{{int:history}}}}' ), + 'Test code for bug 14404', + 0, + false, + $user + ); + $templates1 = $title->getTemplateLinksFrom(); + + $this->setMwGlobals( 'wgLang', Language::factory( 'de' ) ); + $page = WikiPage::factory( $title ); // In order to force the re-rendering of the same wikitext + + // We need an edit, a purge is not enough to regenerate the tables + $page->doEditContent( + new WikitextContent( '{{:{{int:history}}}}' ), + 'Test code for bug 14404', + EDIT_UPDATE, + false, + $user + ); + $templates2 = $title->getTemplateLinksFrom(); + + /** + * @var Title[] $templates1 + * @var Title[] $templates2 + */ + $this->assertEquals( $templates1, $templates2 ); + $this->assertEquals( $templates1[0]->getFullText(), 'Historial' ); + } +} diff --git a/tests/phpunit/includes/page/ArticleTest.php b/tests/phpunit/includes/page/ArticleTest.php new file mode 100644 index 00000000..ae069eaf --- /dev/null +++ b/tests/phpunit/includes/page/ArticleTest.php @@ -0,0 +1,95 @@ +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" ); + } + + /** + * @depends testImplementsSetMagic + * @covers Article::__call + */ + public function testImplementsCallMagic() { + $this->article->mLatest = 33; + $this->article->mDataLoaded = true; + $this->assertEquals( 33, $this->article->getLatest(), "Article __call 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" ); + } + + /** + * Checks for the existence of the backwards compatibility static functions + * (forwarders to WikiPage class) + * + * @covers Article::selectFields + * @covers Article::onArticleCreate + * @covers Article::onArticleDelete + * @covers Article::onArticleEdit + * @covers Article::getAutosummary + */ + public function testStaticFunctions() { + $this->hideDeprecated( 'Article::selectFields' ); + $this->hideDeprecated( 'Article::getAutosummary' ); + $this->hideDeprecated( 'WikiPage::getAutosummary' ); + $this->hideDeprecated( 'CategoryPage::getAutosummary' ); // Inherited from Article + + $this->assertEquals( WikiPage::selectFields(), Article::selectFields(), + "Article static functions" ); + $this->assertEquals( true, is_callable( "Article::onArticleCreate" ), + "Article static functions" ); + $this->assertEquals( true, is_callable( "Article::onArticleDelete" ), + "Article static functions" ); + $this->assertEquals( true, is_callable( "ImagePage::onArticleEdit" ), + "Article static functions" ); + $this->assertTrue( is_string( CategoryPage::getAutosummary( '', '', 0 ) ), + "Article static functions" ); + } +} diff --git a/tests/phpunit/includes/page/ImagePage404Test.php b/tests/phpunit/includes/page/ImagePage404Test.php new file mode 100644 index 00000000..197a2b32 --- /dev/null +++ b/tests/phpunit/includes/page/ImagePage404Test.php @@ -0,0 +1,53 @@ + true ); + } + + function setUp() { + $this->setMwGlobals( 'wgImageLimits', array( + array( 320, 240 ), + array( 640, 480 ), + array( 800, 600 ), + array( 1024, 768 ), + array( 1280, 1024 ) + ) ); + parent::setUp(); + } + + function getImagePage( $filename ) { + $title = Title::makeTitleSafe( NS_FILE, $filename ); + $file = $this->dataFile( $filename ); + $iPage = new ImagePage( $title ); + $iPage->setFile( $file ); + return $iPage; + } + + /** + * @dataProvider providerGetThumbSizes + * @param string $filename + * @param int $expectedNumberThumbs How many thumbnails to show + */ + function testGetThumbSizes( $filename, $expectedNumberThumbs ) { + $iPage = $this->getImagePage( $filename ); + $reflection = new ReflectionClass( $iPage ); + $reflMethod = $reflection->getMethod( 'getThumbSizes' ); + $reflMethod->setAccessible( true ); + + $actual = $reflMethod->invoke( $iPage, 545, 700 ); + $this->assertEquals( count( $actual ), $expectedNumberThumbs ); + } + + function providerGetThumbSizes() { + return array( + array( 'animated.gif', 6 ), + array( 'Toll_Texas_1.svg', 6 ), + array( '80x60-Greyscale.xcf', 6 ), + array( 'jpeg-comment-binary.jpg', 6 ), + ); + } +} diff --git a/tests/phpunit/includes/page/ImagePageTest.php b/tests/phpunit/includes/page/ImagePageTest.php new file mode 100644 index 00000000..3c255b5f --- /dev/null +++ b/tests/phpunit/includes/page/ImagePageTest.php @@ -0,0 +1,90 @@ +setMwGlobals( 'wgImageLimits', array( + array( 320, 240 ), + array( 640, 480 ), + array( 800, 600 ), + array( 1024, 768 ), + array( 1280, 1024 ) + ) ); + parent::setUp(); + } + + function getImagePage( $filename ) { + $title = Title::makeTitleSafe( NS_FILE, $filename ); + $file = $this->dataFile( $filename ); + $iPage = new ImagePage( $title ); + $iPage->setFile( $file ); + return $iPage; + } + + /** + * @dataProvider providerGetDisplayWidthHeight + * @param array $dim Array [maxWidth, maxHeight, width, height] + * @param array $expected Array [width, height] The width and height we expect to display at + */ + function testGetDisplayWidthHeight( $dim, $expected ) { + $iPage = $this->getImagePage( 'animated.gif' ); + $reflection = new ReflectionClass( $iPage ); + $reflMethod = $reflection->getMethod( 'getDisplayWidthHeight' ); + $reflMethod->setAccessible( true ); + + $actual = $reflMethod->invoke( $iPage, $dim[0], $dim[1], $dim[2], $dim[3] ); + $this->assertEquals( $actual, $expected ); + } + + function providerGetDisplayWidthHeight() { + return array( + array( + array( 1024.0, 768.0, 600.0, 600.0 ), + array( 600.0, 600.0 ) + ), + array( + array( 1024.0, 768.0, 1600.0, 600.0 ), + array( 1024.0, 384.0 ) + ), + array( + array( 1024.0, 768.0, 1024.0, 768.0 ), + array( 1024.0, 768.0 ) + ), + array( + array( 1024.0, 768.0, 800.0, 1000.0 ), + array( 614.0, 768.0 ) + ), + array( + array( 1024.0, 768.0, 0, 1000 ), + array( 0, 0 ) + ), + array( + array( 1024.0, 768.0, 2000, 0 ), + array( 0, 0 ) + ), + ); + } + + /** + * @dataProvider providerGetThumbSizes + * @param string $filename + * @param int $expectedNumberThumbs How many thumbnails to show + */ + function testGetThumbSizes( $filename, $expectedNumberThumbs ) { + $iPage = $this->getImagePage( $filename ); + $reflection = new ReflectionClass( $iPage ); + $reflMethod = $reflection->getMethod( 'getThumbSizes' ); + $reflMethod->setAccessible( true ); + + $actual = $reflMethod->invoke( $iPage, 545, 700 ); + $this->assertEquals( count( $actual ), $expectedNumberThumbs ); + } + + function providerGetThumbSizes() { + return array( + array( 'animated.gif', 2 ), + array( 'Toll_Texas_1.svg', 1 ), + array( '80x60-Greyscale.xcf', 1 ), + array( 'jpeg-comment-binary.jpg', 2 ), + ); + } +} diff --git a/tests/phpunit/includes/page/WikiPageTest.php b/tests/phpunit/includes/page/WikiPageTest.php new file mode 100644 index 00000000..c011e9a9 --- /dev/null +++ b/tests/phpunit/includes/page/WikiPageTest.php @@ -0,0 +1,1301 @@ +tablesUsed = array_merge( + $this->tablesUsed, + array( 'page', + 'revision', + 'text', + + 'recentchanges', + 'logging', + + 'page_props', + 'pagelinks', + 'categorylinks', + 'langlinks', + 'externallinks', + 'imagelinks', + 'templatelinks', + 'iwlinks' ) ); + } + + protected function setUp() { + parent::setUp(); + $this->pages_to_delete = array(); + + LinkCache::singleton()->clear(); # avoid cached redirect status, etc + } + + protected function tearDown() { + foreach ( $this->pages_to_delete as $p ) { + /* @var $p WikiPage */ + + try { + if ( $p->exists() ) { + $p->doDeleteArticle( "testing done." ); + } + } catch ( MWException $ex ) { + // fail silently + } + } + parent::tearDown(); + } + + /** + * @param Title|string $title + * @param string|null $model + * @return WikiPage + */ + protected function newPage( $title, $model = null ) { + if ( is_string( $title ) ) { + $ns = $this->getDefaultWikitextNS(); + $title = Title::newFromText( $title, $ns ); + } + + $p = new WikiPage( $title ); + + $this->pages_to_delete[] = $p; + + return $p; + } + + /** + * @param string|Title|WikiPage $page + * @param string $text + * @param int $model + * + * @return WikiPage + */ + protected function createPage( $page, $text, $model = null ) { + if ( is_string( $page ) || $page instanceof Title ) { + $page = $this->newPage( $page, $model ); + } + + $content = ContentHandler::makeContent( $text, $page->getTitle(), $model ); + $page->doEditContent( $content, "testing", EDIT_NEW ); + + return $page; + } + + /** + * @covers WikiPage::doEditContent + */ + public function testDoEditContent() { + $page = $this->newPage( "WikiPageTest_testDoEditContent" ); + $title = $page->getTitle(); + + $content = ContentHandler::makeContent( + "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam " + . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.", + $title, + CONTENT_MODEL_WIKITEXT + ); + + $page->doEditContent( $content, "[[testing]] 1" ); + + $this->assertTrue( $title->getArticleID() > 0, "Title object should have new page id" ); + $this->assertTrue( $page->getId() > 0, "WikiPage should have new page id" ); + $this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" ); + $this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" ); + + $id = $page->getId(); + + # ------------------------ + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); + $n = $res->numRows(); + $res->free(); + + $this->assertEquals( 1, $n, 'pagelinks should contain one link from the page' ); + + # ------------------------ + $page = new WikiPage( $title ); + + $retrieved = $page->getContent(); + $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' ); + + # ------------------------ + $content = ContentHandler::makeContent( + "At vero eos et accusam et justo duo [[dolores]] et ea rebum. " + . "Stet clita kasd [[gubergren]], no sea takimata sanctus est.", + $title, + CONTENT_MODEL_WIKITEXT + ); + + $page->doEditContent( $content, "testing 2" ); + + # ------------------------ + $page = new WikiPage( $title ); + + $retrieved = $page->getContent(); + $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' ); + + # ------------------------ + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); + $n = $res->numRows(); + $res->free(); + + $this->assertEquals( 2, $n, 'pagelinks should contain two links from the page' ); + } + + /** + * @covers WikiPage::doEdit + */ + public function testDoEdit() { + $this->hideDeprecated( "WikiPage::doEdit" ); + $this->hideDeprecated( "WikiPage::getText" ); + $this->hideDeprecated( "Revision::getText" ); + + //NOTE: assume help namespace will default to wikitext + $title = Title::newFromText( "Help:WikiPageTest_testDoEdit" ); + + $page = $this->newPage( $title ); + + $text = "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam " + . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat."; + + $page->doEdit( $text, "[[testing]] 1" ); + + $this->assertTrue( $title->getArticleID() > 0, "Title object should have new page id" ); + $this->assertTrue( $page->getId() > 0, "WikiPage should have new page id" ); + $this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" ); + $this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" ); + + $id = $page->getId(); + + # ------------------------ + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); + $n = $res->numRows(); + $res->free(); + + $this->assertEquals( 1, $n, 'pagelinks should contain one link from the page' ); + + # ------------------------ + $page = new WikiPage( $title ); + + $retrieved = $page->getText(); + $this->assertEquals( $text, $retrieved, 'retrieved text doesn\'t equal original' ); + + # ------------------------ + $text = "At vero eos et accusam et justo duo [[dolores]] et ea rebum. " + . "Stet clita kasd [[gubergren]], no sea takimata sanctus est."; + + $page->doEdit( $text, "testing 2" ); + + # ------------------------ + $page = new WikiPage( $title ); + + $retrieved = $page->getText(); + $this->assertEquals( $text, $retrieved, 'retrieved text doesn\'t equal original' ); + + # ------------------------ + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); + $n = $res->numRows(); + $res->free(); + + $this->assertEquals( 2, $n, 'pagelinks should contain two links from the page' ); + } + + /** + * @covers WikiPage::doQuickEdit + */ + public function testDoQuickEdit() { + global $wgUser; + + $this->hideDeprecated( "WikiPage::doQuickEdit" ); + + //NOTE: assume help namespace will default to wikitext + $page = $this->createPage( "Help:WikiPageTest_testDoQuickEdit", "original text" ); + + $text = "quick text"; + $page->doQuickEdit( $text, $wgUser, "testing q" ); + + # --------------------- + $page = new WikiPage( $page->getTitle() ); + $this->assertEquals( $text, $page->getText() ); + } + + /** + * @covers WikiPage::doQuickEditContent + */ + public function testDoQuickEditContent() { + global $wgUser; + + $page = $this->createPage( + "WikiPageTest_testDoQuickEditContent", + "original text", + CONTENT_MODEL_WIKITEXT + ); + + $content = ContentHandler::makeContent( + "quick text", + $page->getTitle(), + CONTENT_MODEL_WIKITEXT + ); + $page->doQuickEditContent( $content, $wgUser, "testing q" ); + + # --------------------- + $page = new WikiPage( $page->getTitle() ); + $this->assertTrue( $content->equals( $page->getContent() ) ); + } + + /** + * @covers WikiPage::doDeleteArticle + */ + public function testDoDeleteArticle() { + $page = $this->createPage( + "WikiPageTest_testDoDeleteArticle", + "[[original text]] foo", + CONTENT_MODEL_WIKITEXT + ); + $id = $page->getId(); + + $page->doDeleteArticle( "testing deletion" ); + + $this->assertFalse( + $page->getTitle()->getArticleID() > 0, + "Title object should now have page id 0" + ); + $this->assertFalse( $page->getId() > 0, "WikiPage should now have page id 0" ); + $this->assertFalse( + $page->exists(), + "WikiPage::exists should return false after page was deleted" + ); + $this->assertNull( + $page->getContent(), + "WikiPage::getContent should return null after page was deleted" + ); + $this->assertFalse( + $page->getText(), + "WikiPage::getText should return false after page was deleted" + ); + + $t = Title::newFromText( $page->getTitle()->getPrefixedText() ); + $this->assertFalse( + $t->exists(), + "Title::exists should return false after page was deleted" + ); + + # ------------------------ + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); + $n = $res->numRows(); + $res->free(); + + $this->assertEquals( 0, $n, 'pagelinks should contain no more links from the page' ); + } + + /** + * @covers WikiPage::doDeleteUpdates + */ + public function testDoDeleteUpdates() { + $page = $this->createPage( + "WikiPageTest_testDoDeleteArticle", + "[[original text]] foo", + CONTENT_MODEL_WIKITEXT + ); + $id = $page->getId(); + + $page->doDeleteUpdates( $id ); + + # ------------------------ + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); + $n = $res->numRows(); + $res->free(); + + $this->assertEquals( 0, $n, 'pagelinks should contain no more links from the page' ); + } + + /** + * @covers WikiPage::getRevision + */ + public function testGetRevision() { + $page = $this->newPage( "WikiPageTest_testGetRevision" ); + + $rev = $page->getRevision(); + $this->assertNull( $rev ); + + # ----------------- + $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); + + $rev = $page->getRevision(); + + $this->assertEquals( $page->getLatest(), $rev->getId() ); + $this->assertEquals( "some text", $rev->getContent()->getNativeData() ); + } + + /** + * @covers WikiPage::getContent + */ + public function testGetContent() { + $page = $this->newPage( "WikiPageTest_testGetContent" ); + + $content = $page->getContent(); + $this->assertNull( $content ); + + # ----------------- + $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); + + $content = $page->getContent(); + $this->assertEquals( "some text", $content->getNativeData() ); + } + + /** + * @covers WikiPage::getText + */ + public function testGetText() { + $this->hideDeprecated( "WikiPage::getText" ); + + $page = $this->newPage( "WikiPageTest_testGetText" ); + + $text = $page->getText(); + $this->assertFalse( $text ); + + # ----------------- + $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); + + $text = $page->getText(); + $this->assertEquals( "some text", $text ); + } + + /** + * @covers WikiPage::getRawText + */ + public function testGetRawText() { + $this->hideDeprecated( "WikiPage::getRawText" ); + + $page = $this->newPage( "WikiPageTest_testGetRawText" ); + + $text = $page->getRawText(); + $this->assertFalse( $text ); + + # ----------------- + $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); + + $text = $page->getRawText(); + $this->assertEquals( "some text", $text ); + } + + /** + * @covers WikiPage::getContentModel + */ + public function testGetContentModel() { + global $wgContentHandlerUseDB; + + if ( !$wgContentHandlerUseDB ) { + $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' ); + } + + $page = $this->createPage( + "WikiPageTest_testGetContentModel", + "some text", + CONTENT_MODEL_JAVASCRIPT + ); + + $page = new WikiPage( $page->getTitle() ); + $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $page->getContentModel() ); + } + + /** + * @covers WikiPage::getContentHandler + */ + public function testGetContentHandler() { + global $wgContentHandlerUseDB; + + if ( !$wgContentHandlerUseDB ) { + $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' ); + } + + $page = $this->createPage( + "WikiPageTest_testGetContentHandler", + "some text", + CONTENT_MODEL_JAVASCRIPT + ); + + $page = new WikiPage( $page->getTitle() ); + $this->assertEquals( 'JavaScriptContentHandler', get_class( $page->getContentHandler() ) ); + } + + /** + * @covers WikiPage::exists + */ + public function testExists() { + $page = $this->newPage( "WikiPageTest_testExists" ); + $this->assertFalse( $page->exists() ); + + # ----------------- + $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); + $this->assertTrue( $page->exists() ); + + $page = new WikiPage( $page->getTitle() ); + $this->assertTrue( $page->exists() ); + + # ----------------- + $page->doDeleteArticle( "done testing" ); + $this->assertFalse( $page->exists() ); + + $page = new WikiPage( $page->getTitle() ); + $this->assertFalse( $page->exists() ); + } + + public static function provideHasViewableContent() { + return array( + array( 'WikiPageTest_testHasViewableContent', false, true ), + array( 'Special:WikiPageTest_testHasViewableContent', false ), + array( 'MediaWiki:WikiPageTest_testHasViewableContent', false ), + array( 'Special:Userlogin', true ), + array( 'MediaWiki:help', true ), + ); + } + + /** + * @dataProvider provideHasViewableContent + * @covers WikiPage::hasViewableContent + */ + public function testHasViewableContent( $title, $viewable, $create = false ) { + $page = $this->newPage( $title ); + $this->assertEquals( $viewable, $page->hasViewableContent() ); + + if ( $create ) { + $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); + $this->assertTrue( $page->hasViewableContent() ); + + $page = new WikiPage( $page->getTitle() ); + $this->assertTrue( $page->hasViewableContent() ); + } + } + + public static function provideGetRedirectTarget() { + return array( + array( 'WikiPageTest_testGetRedirectTarget_1', CONTENT_MODEL_WIKITEXT, "hello world", null ), + array( + 'WikiPageTest_testGetRedirectTarget_2', + CONTENT_MODEL_WIKITEXT, + "#REDIRECT [[hello world]]", + "Hello world" + ), + ); + } + + /** + * @dataProvider provideGetRedirectTarget + * @covers WikiPage::getRedirectTarget + */ + public function testGetRedirectTarget( $title, $model, $text, $target ) { + $this->setMwGlobals( array( + 'wgCapitalLinks' => true, + ) ); + + $page = $this->createPage( $title, $text, $model ); + + # sanity check, because this test seems to fail for no reason for some people. + $c = $page->getContent(); + $this->assertEquals( 'WikitextContent', get_class( $c ) ); + + # now, test the actual redirect + $t = $page->getRedirectTarget(); + $this->assertEquals( $target, is_null( $t ) ? null : $t->getPrefixedText() ); + } + + /** + * @dataProvider provideGetRedirectTarget + * @covers WikiPage::isRedirect + */ + public function testIsRedirect( $title, $model, $text, $target ) { + $page = $this->createPage( $title, $text, $model ); + $this->assertEquals( !is_null( $target ), $page->isRedirect() ); + } + + public static function provideIsCountable() { + return array( + + // any + array( 'WikiPageTest_testIsCountable', + CONTENT_MODEL_WIKITEXT, + '', + 'any', + true + ), + array( 'WikiPageTest_testIsCountable', + CONTENT_MODEL_WIKITEXT, + 'Foo', + 'any', + true + ), + + // comma + array( 'WikiPageTest_testIsCountable', + CONTENT_MODEL_WIKITEXT, + 'Foo', + 'comma', + false + ), + array( 'WikiPageTest_testIsCountable', + CONTENT_MODEL_WIKITEXT, + 'Foo, bar', + 'comma', + true + ), + + // link + array( 'WikiPageTest_testIsCountable', + CONTENT_MODEL_WIKITEXT, + 'Foo', + 'link', + false + ), + array( 'WikiPageTest_testIsCountable', + CONTENT_MODEL_WIKITEXT, + 'Foo [[bar]]', + 'link', + true + ), + + // redirects + array( 'WikiPageTest_testIsCountable', + CONTENT_MODEL_WIKITEXT, + '#REDIRECT [[bar]]', + 'any', + false + ), + array( 'WikiPageTest_testIsCountable', + CONTENT_MODEL_WIKITEXT, + '#REDIRECT [[bar]]', + 'comma', + false + ), + array( 'WikiPageTest_testIsCountable', + CONTENT_MODEL_WIKITEXT, + '#REDIRECT [[bar]]', + 'link', + false + ), + + // not a content namespace + array( 'Talk:WikiPageTest_testIsCountable', + CONTENT_MODEL_WIKITEXT, + 'Foo', + 'any', + false + ), + array( 'Talk:WikiPageTest_testIsCountable', + CONTENT_MODEL_WIKITEXT, + 'Foo, bar', + 'comma', + false + ), + array( 'Talk:WikiPageTest_testIsCountable', + CONTENT_MODEL_WIKITEXT, + 'Foo [[bar]]', + 'link', + false + ), + + // not a content namespace, different model + array( 'MediaWiki:WikiPageTest_testIsCountable.js', + null, + 'Foo', + 'any', + false + ), + array( 'MediaWiki:WikiPageTest_testIsCountable.js', + null, + 'Foo, bar', + 'comma', + false + ), + array( 'MediaWiki:WikiPageTest_testIsCountable.js', + null, + 'Foo [[bar]]', + 'link', + false + ), + ); + } + + /** + * @dataProvider provideIsCountable + * @covers WikiPage::isCountable + */ + public function testIsCountable( $title, $model, $text, $mode, $expected ) { + global $wgContentHandlerUseDB; + + $this->setMwGlobals( 'wgArticleCountMethod', $mode ); + + $title = Title::newFromText( $title ); + + if ( !$wgContentHandlerUseDB + && $model + && ContentHandler::getDefaultModelFor( $title ) != $model + ) { + $this->markTestSkipped( "Can not use non-default content model $model for " + . $title->getPrefixedDBkey() . " with \$wgContentHandlerUseDB disabled." ); + } + + $page = $this->createPage( $title, $text, $model ); + + $editInfo = $page->prepareContentForEdit( $page->getContent() ); + + $v = $page->isCountable(); + $w = $page->isCountable( $editInfo ); + + $this->assertEquals( + $expected, + $v, + "isCountable( null ) returned unexpected value " . var_export( $v, true ) + . " instead of " . var_export( $expected, true ) + . " in mode `$mode` for text \"$text\"" + ); + + $this->assertEquals( + $expected, + $w, + "isCountable( \$editInfo ) returned unexpected value " . var_export( $v, true ) + . " instead of " . var_export( $expected, true ) + . " in mode `$mode` for text \"$text\"" + ); + } + + public static function provideGetParserOutput() { + return array( + array( CONTENT_MODEL_WIKITEXT, "hello ''world''\n", "

hello world

" ), + // @todo more...? + ); + } + + /** + * @dataProvider provideGetParserOutput + * @covers WikiPage::getParserOutput + */ + public function testGetParserOutput( $model, $text, $expectedHtml ) { + $page = $this->createPage( 'WikiPageTest_testGetParserOutput', $text, $model ); + + $opt = $page->makeParserOptions( 'canonical' ); + $po = $page->getParserOutput( $opt ); + $text = $po->getText(); + + $text = trim( preg_replace( '//sm', '', $text ) ); # strip injected comments + $text = preg_replace( '!\s*(

)!sm', '\1', $text ); # don't let tidy confuse us + + $this->assertEquals( $expectedHtml, $text ); + + return $po; + } + + /** + * @covers WikiPage::getParserOutput + */ + public function testGetParserOutput_nonexisting() { + static $count = 0; + $count++; + + $page = new WikiPage( new Title( "WikiPageTest_testGetParserOutput_nonexisting_$count" ) ); + + $opt = new ParserOptions(); + $po = $page->getParserOutput( $opt ); + + $this->assertFalse( $po, "getParserOutput() shall return false for non-existing pages." ); + } + + /** + * @covers WikiPage::getParserOutput + */ + public function testGetParserOutput_badrev() { + $page = $this->createPage( 'WikiPageTest_testGetParserOutput', "dummy", CONTENT_MODEL_WIKITEXT ); + + $opt = new ParserOptions(); + $po = $page->getParserOutput( $opt, $page->getLatest() + 1234 ); + + // @todo would be neat to also test deleted revision + + $this->assertFalse( $po, "getParserOutput() shall return false for non-existing revisions." ); + } + + public static $sections = + + "Intro + +== stuff == +hello world + +== test == +just a test + +== foo == +more stuff +"; + + public function dataReplaceSection() { + //NOTE: assume the Help namespace to contain wikitext + return array( + array( 'Help:WikiPageTest_testReplaceSection', + CONTENT_MODEL_WIKITEXT, + WikiPageTest::$sections, + "0", + "No more", + null, + trim( preg_replace( '/^Intro/sm', 'No more', WikiPageTest::$sections ) ) + ), + array( 'Help:WikiPageTest_testReplaceSection', + CONTENT_MODEL_WIKITEXT, + WikiPageTest::$sections, + "", + "No more", + null, + "No more" + ), + array( 'Help:WikiPageTest_testReplaceSection', + CONTENT_MODEL_WIKITEXT, + WikiPageTest::$sections, + "2", + "== TEST ==\nmore fun", + null, + trim( preg_replace( '/^== test ==.*== foo ==/sm', + "== TEST ==\nmore fun\n\n== foo ==", + WikiPageTest::$sections ) ) + ), + array( 'Help:WikiPageTest_testReplaceSection', + CONTENT_MODEL_WIKITEXT, + WikiPageTest::$sections, + "8", + "No more", + null, + trim( WikiPageTest::$sections ) + ), + array( 'Help:WikiPageTest_testReplaceSection', + CONTENT_MODEL_WIKITEXT, + WikiPageTest::$sections, + "new", + "No more", + "New", + trim( WikiPageTest::$sections ) . "\n\n== New ==\n\nNo more" + ), + ); + } + + /** + * @dataProvider dataReplaceSection + * @covers WikiPage::replaceSection + */ + public function testReplaceSection( $title, $model, $text, $section, $with, + $sectionTitle, $expected + ) { + $this->hideDeprecated( "WikiPage::replaceSection" ); + + $page = $this->createPage( $title, $text, $model ); + $text = $page->replaceSection( $section, $with, $sectionTitle ); + $text = trim( $text ); + + $this->assertEquals( $expected, $text ); + } + + /** + * @dataProvider dataReplaceSection + * @covers WikiPage::replaceSectionContent + */ + public function testReplaceSectionContent( $title, $model, $text, $section, + $with, $sectionTitle, $expected + ) { + $page = $this->createPage( $title, $text, $model ); + + $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() ); + $c = $page->replaceSectionContent( $section, $content, $sectionTitle ); + + $this->assertEquals( $expected, is_null( $c ) ? null : trim( $c->getNativeData() ) ); + } + + /** + * @dataProvider dataReplaceSection + * @covers WikiPage::replaceSectionAtRev + */ + public function testReplaceSectionAtRev( $title, $model, $text, $section, + $with, $sectionTitle, $expected + ) { + $page = $this->createPage( $title, $text, $model ); + $baseRevId = $page->getLatest(); + + $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() ); + $c = $page->replaceSectionAtRev( $section, $content, $sectionTitle, $baseRevId ); + + $this->assertEquals( $expected, is_null( $c ) ? null : trim( $c->getNativeData() ) ); + } + + /* @todo FIXME: fix this! + public function testGetUndoText() { + $this->checkHasDiff3(); + + $text = "one"; + $page = $this->createPage( "WikiPageTest_testGetUndoText", $text ); + $rev1 = $page->getRevision(); + + $text .= "\n\ntwo"; + $page->doEditContent( + ContentHandler::makeContent( $text, $page->getTitle() ), + "adding section two" + ); + $rev2 = $page->getRevision(); + + $text .= "\n\nthree"; + $page->doEditContent( + ContentHandler::makeContent( $text, $page->getTitle() ), + "adding section three" + ); + $rev3 = $page->getRevision(); + + $text .= "\n\nfour"; + $page->doEditContent( + ContentHandler::makeContent( $text, $page->getTitle() ), + "adding section four" + ); + $rev4 = $page->getRevision(); + + $text .= "\n\nfive"; + $page->doEditContent( + ContentHandler::makeContent( $text, $page->getTitle() ), + "adding section five" + ); + $rev5 = $page->getRevision(); + + $text .= "\n\nsix"; + $page->doEditContent( + ContentHandler::makeContent( $text, $page->getTitle() ), + "adding section six" + ); + $rev6 = $page->getRevision(); + + $undo6 = $page->getUndoText( $rev6 ); + if ( $undo6 === false ) $this->fail( "getUndoText failed for rev6" ); + $this->assertEquals( "one\n\ntwo\n\nthree\n\nfour\n\nfive", $undo6 ); + + $undo3 = $page->getUndoText( $rev4, $rev2 ); + if ( $undo3 === false ) $this->fail( "getUndoText failed for rev4..rev2" ); + $this->assertEquals( "one\n\ntwo\n\nfive", $undo3 ); + + $undo2 = $page->getUndoText( $rev2 ); + if ( $undo2 === false ) $this->fail( "getUndoText failed for rev2" ); + $this->assertEquals( "one\n\nfive", $undo2 ); + } + */ + + /** + * @todo FIXME: this is a better rollback test than the one below, but it + * keeps failing in jenkins for some reason. + */ + public function broken_testDoRollback() { + $admin = new User(); + $admin->setName( "Admin" ); + + $text = "one"; + $page = $this->newPage( "WikiPageTest_testDoRollback" ); + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), + "section one", EDIT_NEW, false, $admin ); + + $user1 = new User(); + $user1->setName( "127.0.1.11" ); + $text .= "\n\ntwo"; + $page = new WikiPage( $page->getTitle() ); + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), + "adding section two", 0, false, $user1 ); + + $user2 = new User(); + $user2->setName( "127.0.2.13" ); + $text .= "\n\nthree"; + $page = new WikiPage( $page->getTitle() ); + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), + "adding section three", 0, false, $user2 ); + + # we are having issues with doRollback spuriously failing. Apparently + # the last revision somehow goes missing or not committed under some + # circumstances. So, make sure the last revision has the right user name. + $dbr = wfGetDB( DB_SLAVE ); + $this->assertEquals( 3, Revision::countByPageId( $dbr, $page->getId() ) ); + + $page = new WikiPage( $page->getTitle() ); + $rev3 = $page->getRevision(); + $this->assertEquals( '127.0.2.13', $rev3->getUserText() ); + + $rev2 = $rev3->getPrevious(); + $this->assertEquals( '127.0.1.11', $rev2->getUserText() ); + + $rev1 = $rev2->getPrevious(); + $this->assertEquals( 'Admin', $rev1->getUserText() ); + + # now, try the actual rollback + $admin->addGroup( "sysop" ); #XXX: make the test user a sysop... + $token = $admin->getEditToken( + array( $page->getTitle()->getPrefixedText(), $user2->getName() ), + null + ); + $errors = $page->doRollback( + $user2->getName(), + "testing revert", + $token, + false, + $details, + $admin + ); + + if ( $errors ) { + $this->fail( "Rollback failed:\n" . print_r( $errors, true ) + . ";\n" . print_r( $details, true ) ); + } + + $page = new WikiPage( $page->getTitle() ); + $this->assertEquals( $rev2->getSha1(), $page->getRevision()->getSha1(), + "rollback did not revert to the correct revision" ); + $this->assertEquals( "one\n\ntwo", $page->getContent()->getNativeData() ); + } + + /** + * @todo FIXME: the above rollback test is better, but it keeps failing in jenkins for some reason. + * @covers WikiPage::doRollback + */ + public function testDoRollback() { + $admin = new User(); + $admin->setName( "Admin" ); + + $text = "one"; + $page = $this->newPage( "WikiPageTest_testDoRollback" ); + $page->doEditContent( + ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ), + "section one", + EDIT_NEW, + false, + $admin + ); + $rev1 = $page->getRevision(); + + $user1 = new User(); + $user1->setName( "127.0.1.11" ); + $text .= "\n\ntwo"; + $page = new WikiPage( $page->getTitle() ); + $page->doEditContent( + ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ), + "adding section two", + 0, + false, + $user1 + ); + + # now, try the rollback + $admin->addGroup( "sysop" ); #XXX: make the test user a sysop... + $token = $admin->getEditToken( + array( $page->getTitle()->getPrefixedText(), $user1->getName() ), + null + ); + $errors = $page->doRollback( + $user1->getName(), + "testing revert", + $token, + false, + $details, + $admin + ); + + if ( $errors ) { + $this->fail( "Rollback failed:\n" . print_r( $errors, true ) + . ";\n" . print_r( $details, true ) ); + } + + $page = new WikiPage( $page->getTitle() ); + $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(), + "rollback did not revert to the correct revision" ); + $this->assertEquals( "one", $page->getContent()->getNativeData() ); + } + + /** + * @covers WikiPage::doRollback + */ + public function testDoRollbackFailureSameContent() { + $admin = new User(); + $admin->setName( "Admin" ); + $admin->addGroup( "sysop" ); #XXX: make the test user a sysop... + + $text = "one"; + $page = $this->newPage( "WikiPageTest_testDoRollback" ); + $page->doEditContent( + ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ), + "section one", + EDIT_NEW, + false, + $admin + ); + $rev1 = $page->getRevision(); + + $user1 = new User(); + $user1->setName( "127.0.1.11" ); + $user1->addGroup( "sysop" ); #XXX: make the test user a sysop... + $text .= "\n\ntwo"; + $page = new WikiPage( $page->getTitle() ); + $page->doEditContent( + ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ), + "adding section two", + 0, + false, + $user1 + ); + + # now, do a the rollback from the same user was doing the edit before + $resultDetails = array(); + $token = $user1->getEditToken( + array( $page->getTitle()->getPrefixedText(), $user1->getName() ), + null + ); + $errors = $page->doRollback( + $user1->getName(), + "testing revert same user", + $token, + false, + $resultDetails, + $admin + ); + + $this->assertEquals( array(), $errors, "Rollback failed same user" ); + + # now, try the rollback + $resultDetails = array(); + $token = $admin->getEditToken( + array( $page->getTitle()->getPrefixedText(), $user1->getName() ), + null + ); + $errors = $page->doRollback( + $user1->getName(), + "testing revert", + $token, + false, + $resultDetails, + $admin + ); + + $this->assertEquals( array( array( 'alreadyrolled', 'WikiPageTest testDoRollback', + '127.0.1.11', 'Admin' ) ), $errors, "Rollback not failed" ); + + $page = new WikiPage( $page->getTitle() ); + $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(), + "rollback did not revert to the correct revision" ); + $this->assertEquals( "one", $page->getContent()->getNativeData() ); + } + + public static function provideGetAutosummary() { + return array( + array( + 'Hello there, world!', + '#REDIRECT [[Foo]]', + 0, + '/^Redirected page .*Foo/' + ), + + array( + null, + 'Hello world!', + EDIT_NEW, + '/^Created page .*Hello/' + ), + + array( + 'Hello there, world!', + '', + 0, + '/^Blanked/' + ), + + array( + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy + eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam + voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet + clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.', + 'Hello world!', + 0, + '/^Replaced .*Hello/' + ), + + array( + 'foo', + 'bar', + 0, + '/^$/' + ), + ); + } + + /** + * @dataProvider provideGetAutoSummary + * @covers WikiPage::getAutosummary + */ + public function testGetAutosummary( $old, $new, $flags, $expected ) { + $this->hideDeprecated( "WikiPage::getAutosummary" ); + + $page = $this->newPage( "WikiPageTest_testGetAutosummary" ); + + $summary = $page->getAutosummary( $old, $new, $flags ); + + $this->assertTrue( (bool)preg_match( $expected, $summary ), + "Autosummary didn't match expected pattern $expected: $summary" ); + } + + public static function provideGetAutoDeleteReason() { + return array( + array( + array(), + false, + false + ), + + array( + array( + array( "first edit", null ), + ), + "/first edit.*only contributor/", + false + ), + + array( + array( + array( "first edit", null ), + array( "second edit", null ), + ), + "/second edit.*only contributor/", + true + ), + + array( + array( + array( "first edit", "127.0.2.22" ), + array( "second edit", "127.0.3.33" ), + ), + "/second edit/", + true + ), + + array( + array( + array( + "first edit: " + . "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam " + . " nonumy eirmod tempor invidunt ut labore et dolore magna " + . "aliquyam erat, sed diam voluptua. At vero eos et accusam " + . "et justo duo dolores et ea rebum. Stet clita kasd gubergren, " + . "no sea takimata sanctus est Lorem ipsum dolor sit amet.'", + null + ), + ), + '/first edit:.*\.\.\."/', + false + ), + + array( + array( + array( "first edit", "127.0.2.22" ), + array( "", "127.0.3.33" ), + ), + "/before blanking.*first edit/", + true + ), + + ); + } + + /** + * @dataProvider provideGetAutoDeleteReason + * @covers WikiPage::getAutoDeleteReason + */ + public function testGetAutoDeleteReason( $edits, $expectedResult, $expectedHistory ) { + global $wgUser; + + //NOTE: assume Help namespace to contain wikitext + $page = $this->newPage( "Help:WikiPageTest_testGetAutoDeleteReason" ); + + $c = 1; + + foreach ( $edits as $edit ) { + $user = new User(); + + if ( !empty( $edit[1] ) ) { + $user->setName( $edit[1] ); + } else { + $user = $wgUser; + } + + $content = ContentHandler::makeContent( $edit[0], $page->getTitle(), $page->getContentModel() ); + + $page->doEditContent( $content, "test edit $c", $c < 2 ? EDIT_NEW : 0, false, $user ); + + $c += 1; + } + + $reason = $page->getAutoDeleteReason( $hasHistory ); + + if ( is_bool( $expectedResult ) || is_null( $expectedResult ) ) { + $this->assertEquals( $expectedResult, $reason ); + } else { + $this->assertTrue( (bool)preg_match( $expectedResult, $reason ), + "Autosummary didn't match expected pattern $expectedResult: $reason" ); + } + + $this->assertEquals( $expectedHistory, $hasHistory, + "expected \$hasHistory to be " . var_export( $expectedHistory, true ) ); + + $page->doDeleteArticle( "done" ); + } + + public static function providePreSaveTransform() { + return array( + array( 'hello this is ~~~', + "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]", + ), + array( 'hello \'\'this\'\' is ~~~', + 'hello \'\'this\'\' is ~~~', + ), + ); + } + + /** + * @dataProvider providePreSaveTransform + * @covers WikiPage::preSaveTransform + */ + public function testPreSaveTransform( $text, $expected ) { + $this->hideDeprecated( 'WikiPage::preSaveTransform' ); + $user = new User(); + $user->setName( "127.0.0.1" ); + + //NOTE: assume Help namespace to contain wikitext + $page = $this->newPage( "Help:WikiPageTest_testPreloadTransform" ); + $text = $page->preSaveTransform( $text, $user ); + + $this->assertEquals( $expected, $text ); + } + + /** + * @covers WikiPage::factory + */ + public function testWikiPageFactory() { + $title = Title::makeTitle( NS_FILE, 'Someimage.png' ); + $page = WikiPage::factory( $title ); + $this->assertEquals( 'WikiFilePage', get_class( $page ) ); + + $title = Title::makeTitle( NS_CATEGORY, 'SomeCategory' ); + $page = WikiPage::factory( $title ); + $this->assertEquals( 'WikiCategoryPage', get_class( $page ) ); + + $title = Title::makeTitle( NS_MAIN, 'SomePage' ); + $page = WikiPage::factory( $title ); + $this->assertEquals( 'WikiPage', get_class( $page ) ); + } +} diff --git a/tests/phpunit/includes/page/WikiPageTestContentHandlerUseDB.php b/tests/phpunit/includes/page/WikiPageTestContentHandlerUseDB.php new file mode 100644 index 00000000..3db76280 --- /dev/null +++ b/tests/phpunit/includes/page/WikiPageTestContentHandlerUseDB.php @@ -0,0 +1,61 @@ +setMwGlobals( 'wgContentHandlerUseDB', false ); + + $dbw = wfGetDB( DB_MASTER ); + + $page_table = $dbw->tableName( 'page' ); + $revision_table = $dbw->tableName( 'revision' ); + $archive_table = $dbw->tableName( 'archive' ); + + if ( $dbw->fieldExists( $page_table, 'page_content_model' ) ) { + $dbw->query( "alter table $page_table drop column page_content_model" ); + $dbw->query( "alter table $revision_table drop column rev_content_model" ); + $dbw->query( "alter table $revision_table drop column rev_content_format" ); + $dbw->query( "alter table $archive_table drop column ar_content_model" ); + $dbw->query( "alter table $archive_table drop column ar_content_format" ); + } + } + + /** + * @covers WikiPage::getContentModel + */ + public function testGetContentModel() { + $page = $this->createPage( + "WikiPageTest_testGetContentModel", + "some text", + CONTENT_MODEL_JAVASCRIPT + ); + + $page = new WikiPage( $page->getTitle() ); + + // NOTE: since the content model is not recorded in the database, + // we expect to get the default, namely CONTENT_MODEL_WIKITEXT + $this->assertEquals( CONTENT_MODEL_WIKITEXT, $page->getContentModel() ); + } + + /** + * @covers WikiPage::getContentHandler + */ + public function testGetContentHandler() { + $page = $this->createPage( + "WikiPageTest_testGetContentHandler", + "some text", + CONTENT_MODEL_JAVASCRIPT + ); + + // NOTE: since the content model is not recorded in the database, + // we expect to get the default, namely CONTENT_MODEL_WIKITEXT + $page = new WikiPage( $page->getTitle() ); + $this->assertEquals( 'WikitextContentHandler', get_class( $page->getContentHandler() ) ); + } +} diff --git a/tests/phpunit/includes/parser/NewParserTest.php b/tests/phpunit/includes/parser/NewParserTest.php index 0df52f5e..91aad10c 100644 --- a/tests/phpunit/includes/parser/NewParserTest.php +++ b/tests/phpunit/includes/parser/NewParserTest.php @@ -124,7 +124,7 @@ class NewParserTest extends MediaWikiTestCase { $tmpGlobals['wgFileExtensions'][] = 'svg'; $tmpGlobals['wgSVGConverter'] = 'rsvg'; $tmpGlobals['wgSVGConverters']['rsvg'] = - '$path/rsvg-convert -w $width -h $height $input -o $output'; + '$path/rsvg-convert -w $width -h $height -o $output $input'; if ( $GLOBALS['wgStyleDirectory'] === false ) { $tmpGlobals['wgStyleDirectory'] = "$IP/skins"; @@ -160,9 +160,6 @@ class NewParserTest extends MediaWikiTestCase { $this->djVuSupport = new DjVuSupport(); // Tidy support $this->tidySupport = new TidySupport(); - // We always set 'wgUseTidy' to false when parsing, but certain - // test-running modes still use tidy if available, so ensure - // that the tidy-related options are all set to their defaults. $tmpGlobals['wgUseTidy'] = false; $tmpGlobals['wgAlwaysUseTidy'] = false; $tmpGlobals['wgDebugTidy'] = false; @@ -419,6 +416,7 @@ class NewParserTest extends MediaWikiTestCase { 'wgMathDirectory' => $uploadDir . '/math', 'wgDefaultLanguageVariant' => $variant, 'wgLinkHolderBatchSize' => $linkHolderBatchSize, + 'wgUseTidy' => isset( $opts['tidy'] ), ); if ( $config ) { @@ -434,7 +432,7 @@ class NewParserTest extends MediaWikiTestCase { $this->savedGlobals = array(); /** @since 1.20 */ - wfRunHooks( 'ParserTestGlobals', array( &$settings ) ); + Hooks::run( 'ParserTestGlobals', array( &$settings ) ); $langObj = Language::factory( $lang ); $settings['wgContLang'] = $langObj; @@ -480,16 +478,16 @@ class NewParserTest extends MediaWikiTestCase { */ protected function getUploadDir() { if ( $this->keepUploads ) { + // Don't use getNewTempDirectory() as this is meant to persist $dir = wfTempDir() . '/mwParser-images'; if ( is_dir( $dir ) ) { return $dir; } } else { - $dir = wfTempDir() . "/mwParser-" . mt_rand() . "-images"; + $dir = $this->getNewTempDirectory(); } - // wfDebug( "Creating upload directory $dir\n" ); if ( file_exists( $dir ) ) { wfDebug( "Already exists!\n" ); @@ -727,12 +725,21 @@ class NewParserTest extends MediaWikiTestCase { . "Current configuration is:\n\$wgTexvc = '$wgTexvc'" ); } } + if ( isset( $opts['djvu'] ) ) { if ( !$this->djVuSupport->isEnabled() ) { $this->markTestSkipped( "SKIPPED: djvu binaries do not exist or are not executable.\n" ); } } + if ( isset( $opts['tidy'] ) ) { + if ( !$this->tidySupport->isEnabled() ) { + $this->markTestSkipped( "SKIPPED: tidy extension is not installed.\n" ); + } else { + $options->setTidy( true ); + } + } + if ( isset( $opts['pst'] ) ) { $out = $parser->preSaveTransform( $input, $title, $user, $options ); } elseif ( isset( $opts['msg'] ) ) { @@ -753,12 +760,7 @@ class NewParserTest extends MediaWikiTestCase { $output->setTOCEnabled( !isset( $opts['notoc'] ) ); $out = $output->getText(); if ( isset( $opts['tidy'] ) ) { - if ( !$this->tidySupport->isEnabled() ) { - $this->markTestSkipped( "SKIPPED: tidy extension is not installed.\n" ); - } else { - $out = MWTidy::tidy( $out ); - $out = preg_replace( '/\s+$/', '', $out ); - } + $out = preg_replace( '/\s+$/', '', $out ); } if ( isset( $opts['showtitle'] ) ) { @@ -769,6 +771,14 @@ class NewParserTest extends MediaWikiTestCase { $out = "$title\n$out"; } + if ( isset( $opts['showindicators'] ) ) { + $indicators = ''; + foreach ( $output->getIndicators() as $id => $content ) { + $indicators .= "$id=$content\n"; + } + $out = $indicators . $out; + } + if ( isset( $opts['ill'] ) ) { $out = implode( ' ', $output->getLanguageLinks() ); } elseif ( isset( $opts['cat'] ) ) { @@ -939,7 +949,7 @@ class NewParserTest extends MediaWikiTestCase { $class = $wgParserConf['class']; $parser = new $class( array( 'preprocessorClass' => $preprocessor ) + $wgParserConf ); - wfRunHooks( 'ParserTestParser', array( &$parser ) ); + Hooks::run( 'ParserTestParser', array( &$parser ) ); return $parser; } diff --git a/tests/phpunit/includes/parser/ParserOutputTest.php b/tests/phpunit/includes/parser/ParserOutputTest.php index c024cee5..e660e096 100644 --- a/tests/phpunit/includes/parser/ParserOutputTest.php +++ b/tests/phpunit/includes/parser/ParserOutputTest.php @@ -1,5 +1,9 @@ assertEquals( $po->getProperty( 'foo' ), false ); $this->assertArrayNotHasKey( 'foo', $properties ); } + } diff --git a/tests/phpunit/includes/parser/TagHooksTest.php b/tests/phpunit/includes/parser/TagHooksTest.php index e3c4cc84..251da471 100644 --- a/tests/phpunit/includes/parser/TagHooksTest.php +++ b/tests/phpunit/includes/parser/TagHooksTest.php @@ -89,7 +89,7 @@ class TagHookTest extends MediaWikiTestCase { global $wgParserConf, $wgContLang; $parser = new Parser( $wgParserConf ); - $parser->setFunctionTagHook( $tag, array( $this, 'functionTagCallback' ), SFH_OBJECT_ARGS ); + $parser->setFunctionTagHook( $tag, array( $this, 'functionTagCallback' ), Parser::SFH_OBJECT_ARGS ); $parser->parse( "Foo<$tag>BarBaz", Title::newFromText( 'Test' ), diff --git a/tests/phpunit/includes/password/PasswordTest.php b/tests/phpunit/includes/password/PasswordTest.php new file mode 100644 index 00000000..5ad8aca6 --- /dev/null +++ b/tests/phpunit/includes/password/PasswordTest.php @@ -0,0 +1,39 @@ +newFromCiphertext( null ); + $invalid2 = User::getPasswordFactory()->newFromCiphertext( null ); + + $this->assertFalse( $invalid1->equals( $invalid2 ) ); + } + + public function testInvalidPlaintext() { + $invalid = User::getPasswordFactory()->newFromPlaintext( null ); + + $this->assertInstanceOf( 'InvalidPassword', $invalid ); + } +} diff --git a/tests/phpunit/includes/password/PasswordTestCase.php b/tests/phpunit/includes/password/PasswordTestCase.php index ef16f1c4..9a142cbc 100644 --- a/tests/phpunit/includes/password/PasswordTestCase.php +++ b/tests/phpunit/includes/password/PasswordTestCase.php @@ -49,10 +49,13 @@ abstract class PasswordTestCase extends MediaWikiTestCase { * An array of tests in the form of (bool, string, string), where the first * element is whether the second parameter (a password hash) and the third * parameter (a password) should match. - * * @return array + * @throws MWException + * @abstract */ - abstract public static function providePasswordTests(); + public static function providePasswordTests() { + throw new MWException( "Not implemented" ); + } /** * @dataProvider providePasswordTests diff --git a/tests/phpunit/includes/registration/ExtensionProcessorTest.php b/tests/phpunit/includes/registration/ExtensionProcessorTest.php new file mode 100644 index 00000000..8715711f --- /dev/null +++ b/tests/phpunit/includes/registration/ExtensionProcessorTest.php @@ -0,0 +1,374 @@ +dir = __DIR__ . '/FooBar/extension.json'; + } + + /** + * 'name' is absolutely required + * + * @var array + */ + static $default = array( + 'name' => 'FooBar', + ); + + /** + * @covers ExtensionProcessor::extractInfo + */ + public function testExtractInfo() { + // Test that attributes that begin with @ are ignored + $processor = new ExtensionProcessor(); + $processor->extractInfo( $this->dir, self::$default + array( + '@metadata' => array( 'foobarbaz' ), + 'AnAttribute' => array( 'omg' ), + 'AutoloadClasses' => array( 'FooBar' => 'includes/FooBar.php' ), + ) ); + + $extracted = $processor->getExtractedInfo(); + $attributes = $extracted['attributes']; + $this->assertArrayHasKey( 'AnAttribute', $attributes ); + $this->assertArrayNotHasKey( '@metadata', $attributes ); + $this->assertArrayNotHasKey( 'AutoloadClasses', $attributes ); + } + + public static function provideRegisterHooks() { + return array( + // No hooks + array( + array(), + self::$default, + array(), + ), + // No current hooks, adding one for "FooBaz" + array( + array(), + array( 'Hooks' => array( 'FooBaz' => 'FooBazCallback' ) ) + self::$default, + array( 'FooBaz' => array( 'FooBazCallback' ) ), + ), + // Hook for "FooBaz", adding another one + array( + array( 'FooBaz' => array( 'PriorCallback' ) ), + array( 'Hooks' => array( 'FooBaz' => 'FooBazCallback' ) ) + self::$default, + array( 'FooBaz' => array( 'PriorCallback', 'FooBazCallback' ) ), + ), + // Hook for "BarBaz", adding one for "FooBaz" + array( + array( 'BarBaz' => array( 'BarBazCallback' ) ), + array( 'Hooks' => array( 'FooBaz' => 'FooBazCallback' ) ) + self::$default, + array( + 'BarBaz' => array( 'BarBazCallback' ), + 'FooBaz' => array( 'FooBazCallback' ), + ), + ), + ); + } + + /** + * @covers ExtensionProcessor::extractHooks + * @dataProvider provideRegisterHooks + */ + public function testRegisterHooks( $pre, $info, $expected ) { + $processor = new MockExtensionProcessor( array( 'wgHooks' => $pre ) ); + $processor->extractInfo( $this->dir, $info ); + $extracted = $processor->getExtractedInfo(); + $this->assertEquals( $expected, $extracted['globals']['wgHooks'] ); + } + + /** + * @covers ExtensionProcessor::extractConfig + */ + public function testExtractConfig() { + $processor = new ExtensionProcessor; + $info = array( + 'config' => array( + 'Bar' => 'somevalue', + 'Foo' => 10, + '@IGNORED' => 'yes', + ), + ) + self::$default; + $processor->extractInfo( $this->dir, $info ); + $extracted = $processor->getExtractedInfo(); + $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] ); + $this->assertEquals( 10, $extracted['globals']['wgFoo'] ); + $this->assertArrayNotHasKey( 'wg@IGNORED', $extracted['globals'] ); + } + + public static function provideExtracttExtensionMessagesFiles() { + $dir = __DIR__ . '/FooBar/'; + return array( + array( + array( 'ExtensionMessagesFiles' => array( 'FooBarAlias' => 'FooBar.alias.php' ) ), + array( 'wgExtensionMessagesFiles' => array( 'FooBarAlias' => $dir . 'FooBar.alias.php' ) ) + ), + array( + array( + 'ExtensionMessagesFiles' => array( + 'FooBarAlias' => 'FooBar.alias.php', + 'FooBarMagic' => 'FooBar.magic.i18n.php', + ), + ), + array( + 'wgExtensionMessagesFiles' => array( + 'FooBarAlias' => $dir . 'FooBar.alias.php', + 'FooBarMagic' => $dir . 'FooBar.magic.i18n.php', + ), + ), + ), + ); + } + + /** + * @covers ExtensionProcessor::extracttExtensionMessagesFiles + * @dataProvider provideExtracttExtensionMessagesFiles + */ + public function testExtracttExtensionMessagesFiles( $input, $expected ) { + $processor = new ExtensionProcessor(); + $processor->extractInfo( $this->dir, $input + self::$default ); + $out = $processor->getExtractedInfo(); + foreach ( $expected as $key => $value ) { + $this->assertEquals( $value, $out['globals'][$key] ); + } + } + + + public static function provideExtractMessagesDirs() { + $dir = __DIR__ . '/FooBar/'; + return array( + array( + array( 'MessagesDirs' => array( 'VisualEditor' => 'i18n' ) ), + array( 'wgMessagesDirs' => array( 'VisualEditor' => array( $dir . 'i18n' ) ) ) + ), + array( + array( 'MessagesDirs' => array( 'VisualEditor' => array( 'i18n', 'foobar' ) ) ), + array( 'wgMessagesDirs' => array( 'VisualEditor' => array( $dir . 'i18n', $dir . 'foobar' ) ) ) + ), + ); + } + + /** + * @covers ExtensionProcessor::extractMessagesDirs + * @dataProvider provideExtractMessagesDirs + */ + public function testExtractMessagesDirs( $input, $expected ) { + $processor = new ExtensionProcessor(); + $processor->extractInfo( $this->dir, $input + self::$default ); + $out = $processor->getExtractedInfo(); + foreach ( $expected as $key => $value ) { + $this->assertEquals( $value, $out['globals'][$key] ); + } + } + + /** + * @covers ExtensionProcessor::extractResourceLoaderModules + * @dataProvider provideExtractResourceLoaderModules + */ + public function testExtractResourceLoaderModules( $input, $expected ) { + $processor = new ExtensionProcessor(); + $processor->extractInfo( $this->dir, $input + self::$default ); + $out = $processor->getExtractedInfo(); + foreach ( $expected as $key => $value ) { + $this->assertEquals( $value, $out['globals'][$key] ); + } + } + + public static function provideExtractResourceLoaderModules() { + $dir = __DIR__ . '/FooBar/'; + return array( + // Generic module with localBasePath/remoteExtPath specified + array( + // Input + array( + 'ResourceModules' => array( + 'test.foo' => array( + 'styles' => 'foobar.js', + 'localBasePath' => '', + 'remoteExtPath' => 'FooBar', + ), + ), + ), + // Expected + array( + 'wgResourceModules' => array( + 'test.foo' => array( + 'styles' => 'foobar.js', + 'localBasePath' => $dir, + 'remoteExtPath' => 'FooBar', + ), + ), + ), + ), + // ResourceFileModulePaths specified: + array( + // Input + array( + 'ResourceFileModulePaths' => array( + 'localBasePath' => '', + 'remoteExtPath' => 'FooBar', + ), + 'ResourceModules' => array( + // No paths + 'test.foo' => array( + 'styles' => 'foo.js', + ), + // Different paths set + 'test.bar' => array( + 'styles' => 'bar.js', + 'localBasePath' => 'subdir', + 'remoteExtPath' => 'FooBar/subdir', + ), + // Custom class with no paths set + 'test.class' => array( + 'class' => 'FooBarModule', + 'extra' => 'argument', + ), + // Custom class with a localBasePath + 'test.class.with.path' => array( + 'class' => 'FooBarPathModule', + 'extra' => 'argument', + 'localBasePath' => '', + ) + ), + ), + // Expected + array( + 'wgResourceModules' => array( + 'test.foo' => array( + 'styles' => 'foo.js', + 'localBasePath' => $dir, + 'remoteExtPath' => 'FooBar', + ), + 'test.bar' => array( + 'styles' => 'bar.js', + 'localBasePath' => $dir . 'subdir', + 'remoteExtPath' => 'FooBar/subdir', + ), + 'test.class' => array( + 'class' => 'FooBarModule', + 'extra' => 'argument', + 'localBasePath' => $dir, + 'remoteExtPath' => 'FooBar', + ), + 'test.class.with.path' => array( + 'class' => 'FooBarPathModule', + 'extra' => 'argument', + 'localBasePath' => $dir, + 'remoteExtPath' => 'FooBar', + ) + ), + ), + ), + // ResourceModuleSkinStyles with file module paths + array( + // Input + array( + 'ResourceFileModulePaths' => array( + 'localBasePath' => '', + 'remoteSkinPath' => 'FooBar', + ), + 'ResourceModuleSkinStyles' => array( + 'foobar' => array( + 'test.foo' => 'foo.css', + ) + ), + ), + // Expected + array( + 'wgResourceModuleSkinStyles' => array( + 'foobar' => array( + 'test.foo' => 'foo.css', + 'localBasePath' => $dir, + 'remoteSkinPath' => 'FooBar', + ), + ), + ), + ), + // ResourceModuleSkinStyles with file module paths and an override + array( + // Input + array( + 'ResourceFileModulePaths' => array( + 'localBasePath' => '', + 'remoteSkinPath' => 'FooBar', + ), + 'ResourceModuleSkinStyles' => array( + 'foobar' => array( + 'test.foo' => 'foo.css', + 'remoteSkinPath' => 'BarFoo' + ), + ), + ), + // Expected + array( + 'wgResourceModuleSkinStyles' => array( + 'foobar' => array( + 'test.foo' => 'foo.css', + 'localBasePath' => $dir, + 'remoteSkinPath' => 'BarFoo', + ), + ), + ), + ), + ); + } + + public static function provideSetToGlobal() { + return array( + array( + array( 'wgAPIModules', 'wgAvailableRights' ), + array(), + array( + 'APIModules' => array( 'foobar' => 'ApiFooBar' ), + 'AvailableRights' => array( 'foobar', 'unfoobar' ), + ), + array( + 'wgAPIModules' => array( 'foobar' => 'ApiFooBar' ), + 'wgAvailableRights' => array( 'foobar', 'unfoobar' ), + ), + ), + array( + array( 'wgAPIModules', 'wgAvailableRights' ), + array( + 'wgAPIModules' => array( 'barbaz' => 'ApiBarBaz' ), + 'wgAvailableRights' => array( 'barbaz' ) + ), + array( + 'APIModules' => array( 'foobar' => 'ApiFooBar' ), + 'AvailableRights' => array( 'foobar', 'unfoobar' ), + ), + array( + 'wgAPIModules' => array( 'barbaz' => 'ApiBarBaz', 'foobar' => 'ApiFooBar' ), + 'wgAvailableRights' => array( 'barbaz', 'foobar', 'unfoobar' ), + ), + ), + array( + array( 'wgGroupPermissions' ), + array( + 'wgGroupPermissions' => array( 'sysop' => array( 'delete' ) ), + ), + array( + 'GroupPermissions' => array( 'sysop' => array( 'undelete' ), 'user' => array( 'edit' ) ), + ), + array( + 'wgGroupPermissions' => array( 'sysop' => array( 'delete', 'undelete' ), 'user' => array( 'edit' ) ), + ) + ) + ); + } +} + + +/** + * Allow overriding the default value of $this->globals + * so we can test merging + */ +class MockExtensionProcessor extends ExtensionProcessor { + public function __construct( $globals = array() ) { + $this->globals = $globals + $this->globals; + } +} diff --git a/tests/phpunit/includes/registration/ExtensionRegistryTest.php b/tests/phpunit/includes/registration/ExtensionRegistryTest.php new file mode 100644 index 00000000..1b24628c --- /dev/null +++ b/tests/phpunit/includes/registration/ExtensionRegistryTest.php @@ -0,0 +1,195 @@ + $value ) { + // mw prefixed globals does not exist normally + if ( substr( $key, 0, 2 ) == 'mw' ) { + $GLOBALS[$key] = $value; + } else { + $this->setMwGlobals( $key, $value ); + } + } + } + + $info = array( + 'globals' => $globals, + 'callbacks' => array(), + 'defines' => array(), + 'credits' => array(), + 'attributes' => array(), + ); + $registry = new ExtensionRegistry(); + $class = new ReflectionClass( 'ExtensionRegistry' ); + $method = $class->getMethod( 'exportExtractedData' ); + $method->setAccessible( true ); + $method->invokeArgs( $registry, array( $info ) ); + foreach ( $expected as $name => $value ) { + $this->assertArrayHasKey( $name, $GLOBALS, $desc ); + $this->assertEquals( $value, $GLOBALS[$name], $desc ); + } + + // Remove mw prefixed globals + if ( $before ) { + foreach ( $before as $key => $value ) { + if ( substr( $key, 0, 2 ) == 'mw' ) { + unset( $GLOBALS[$key] ); + } + } + } + } + + public static function provideExportExtractedDataGlobals() { + // "mwtest" prefix used instead of "$wg" to avoid potential conflicts + return array( + array( + 'Simple non-array values', + array( + 'mwtestFooBarConfig' => true, + 'mwtestFooBarConfig2' => 'string', + ), + array( + 'mwtestFooBarDefault' => 1234, + 'mwtestFooBarConfig' => false, + ), + array( + 'mwtestFooBarConfig' => true, + 'mwtestFooBarConfig2' => 'string', + 'mwtestFooBarDefault' => 1234, + ), + ), + array( + 'No global already set, simple array', + null, + array( + 'mwtestDefaultOptions' => array( + 'foobar' => true, + ) + ), + array( + 'mwtestDefaultOptions' => array( + 'foobar' => true, + ) + ), + ), + array( + 'Global already set, simple array', + array( + 'mwtestDefaultOptions' => array( + 'foobar' => true, + 'foo' => 'string' + ), + ), + array( + 'mwtestDefaultOptions' => array( + 'barbaz' => 12345, + 'foobar' => false, + ), + ), + array( + 'mwtestDefaultOptions' => array( + 'barbaz' => 12345, + 'foo' => 'string', + 'foobar' => true, + ), + ) + ), + array( + 'No global already set, $wgHooks', + array( + 'wgHooks' => array(), + ), + array( + 'wgHooks' => array( + 'FooBarEvent' => array( + 'FooBarClass::onFooBarEvent' + ), + ), + ), + array( + 'wgHooks' => array( + 'FooBarEvent' => array( + 'FooBarClass::onFooBarEvent' + ), + ), + ), + ), + array( + 'Global already set, $wgHooks', + array( + 'wgHooks' => array( + 'FooBarEvent' => array( + 'FooBarClass::onFooBarEvent' + ), + 'BazBarEvent' => array( + 'FooBarClass::onBazBarEvent', + ), + ), + ), + array( + 'wgHooks' => array( + 'FooBarEvent' => array( + 'BazBarClass::onFooBarEvent', + ), + ), + ), + array( + 'wgHooks' => array( + 'FooBarEvent' => array( + 'FooBarClass::onFooBarEvent', + 'BazBarClass::onFooBarEvent', + ), + 'BazBarEvent' => array( + 'FooBarClass::onBazBarEvent', + ), + ), + ), + ), + array( + 'Global already set, $wgGroupPermissions', + array( + 'wgGroupPermissions' => array( + 'sysop' => array( + 'something' => true, + ), + 'user' => array( + 'somethingtwo' => true, + ) + ), + ), + array( + 'wgGroupPermissions' => array( + 'customgroup' => array( + 'right' => true, + ), + 'user' => array( + 'right' => true, + 'somethingtwo' => false, + ) + ), + ), + array( + 'wgGroupPermissions' => array( + 'customgroup' => array( + 'right' => true, + ), + 'sysop' => array( + 'something' => true, + ), + 'user' => array( + 'somethingtwo' => true, + 'right' => true, + ) + ), + ), + ) + ); + } +} diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php new file mode 100644 index 00000000..122995a5 --- /dev/null +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php @@ -0,0 +1,247 @@ +register( + 'fakeskin', + 'FakeSkin', + function () { + } + ); + } + + private static function getModules() { + $base = array( + 'localBasePath' => realpath( dirname( __FILE__ ) ), + ); + + return array( + 'noTemplateModule' => array(), + + 'htmlTemplateModule' => $base + array( + 'templates' => array( + 'templates/template.html', + 'templates/template2.html', + ) + ), + + 'aliasedHtmlTemplateModule' => $base + array( + 'templates' => array( + 'foo.html' => 'templates/template.html', + 'bar.html' => 'templates/template2.html', + ) + ), + + 'templateModuleHandlebars' => $base + array( + 'templates' => array( + 'templates/template_awesome.handlebars', + ), + ), + + 'aliasFooFromBar' => $base + array( + 'templates' => array( + 'foo.foo' => 'templates/template.bar', + ), + ), + ); + } + + public static function providerTemplateDependencies() { + $modules = self::getModules(); + + return array( + array( + $modules['noTemplateModule'], + array(), + ), + array( + $modules['htmlTemplateModule'], + array( + 'mediawiki.template', + ), + ), + array( + $modules['templateModuleHandlebars'], + array( + 'mediawiki.template', + 'mediawiki.template.handlebars', + ), + ), + array( + $modules['aliasFooFromBar'], + array( + 'mediawiki.template', + 'mediawiki.template.foo', + ), + ), + ); + } + + /** + * @dataProvider providerTemplateDependencies + * @covers ResourceLoaderFileModule::__construct + * @covers ResourceLoaderFileModule::getDependencies + */ + public function testTemplateDependencies( $module, $expected ) { + $rl = new ResourceLoaderFileModule( $module ); + $this->assertEquals( $rl->getDependencies(), $expected ); + } + + /** + * @covers ResourceLoaderFileModule::getAllStyleFiles + * @covers ResourceLoaderFileModule::getAllSkinStyleFiles + * @covers ResourceLoaderFileModule::getSkinStyleFiles + */ + public function testGetAllSkinStyleFiles() { + $baseParams = array( + 'scripts' => array( + 'foo.js', + 'bar.js', + ), + 'styles' => array( + 'foo.css', + 'bar.css' => array( 'media' => 'print' ), + 'screen.less' => array( 'media' => 'screen' ), + 'screen-query.css' => array( 'media' => 'screen and (min-width: 400px)' ), + ), + 'skinStyles' => array( + 'default' => 'quux-fallback.less', + 'fakeskin' => array( + 'baz-vector.css', + 'quux-vector.less', + ), + ), + 'messages' => array( + 'hello', + 'world', + ), + ); + + $module = new ResourceLoaderFileModule( $baseParams ); + + $this->assertEquals( + array( + 'foo.css', + 'baz-vector.css', + 'quux-vector.less', + 'quux-fallback.less', + 'bar.css', + 'screen.less', + 'screen-query.css', + ), + array_map( 'basename', $module->getAllStyleFiles() ) + ); + } + + /** + * Strip @noflip annotations from CSS code. + * @param string $css + * @return string + */ + private static function stripNoflip( $css ) { + return str_replace( '/*@noflip*/ ', '', $css ); + } + + /** + * What happens when you mix @embed and @noflip? + * This really is an integration test, but oh well. + * + * @covers ResourceLoaderFileModule::getStyles + * @covers ResourceLoaderFileModule::getStyleFiles + */ + public function testMixedCssAnnotations( ) { + $basePath = __DIR__ . '/../../data/css'; + $testModule = new ResourceLoaderFileModule( array( + 'localBasePath' => $basePath, + 'styles' => array( 'test.css' ), + ) ); + $expectedModule = new ResourceLoaderFileModule( array( + 'localBasePath' => $basePath, + 'styles' => array( 'expected.css' ), + ) ); + + $contextLtr = $this->getResourceLoaderContext( 'en', 'ltr' ); + $contextRtl = $this->getResourceLoaderContext( 'he', 'rtl' ); + + // Since we want to compare the effect of @noflip+@embed against the effect of just @embed, and + // the @noflip annotations are always preserved, we need to strip them first. + $this->assertEquals( + $expectedModule->getStyles( $contextLtr ), + self::stripNoflip( $testModule->getStyles( $contextLtr ) ), + "/*@noflip*/ with /*@embed*/ gives correct results in LTR mode" + ); + $this->assertEquals( + $expectedModule->getStyles( $contextLtr ), + self::stripNoflip( $testModule->getStyles( $contextRtl ) ), + "/*@noflip*/ with /*@embed*/ gives correct results in RTL mode" + ); + } + + public static function providerGetTemplates() { + $modules = self::getModules(); + + return array( + array( + $modules['noTemplateModule'], + array(), + ), + array( + $modules['templateModuleHandlebars'], + array( + 'templates/template_awesome.handlebars' => "wow\n", + ), + ), + array( + $modules['htmlTemplateModule'], + array( + 'templates/template.html' => "hello\n", + 'templates/template2.html' => "
goodbye
\n", + ), + ), + array( + $modules['aliasedHtmlTemplateModule'], + array( + 'foo.html' => "hello\n", + 'bar.html' => "
goodbye
\n", + ), + ), + ); + } + + /** + * @dataProvider providerGetTemplates + * @covers ResourceLoaderFileModule::getTemplates + */ + public function testGetTemplates( $module, $expected ) { + $rl = new ResourceLoaderFileModule( $module ); + + $this->assertEquals( $rl->getTemplates(), $expected ); + } + + public static function providerGetModifiedTime() { + $modules = self::getModules(); + + return array( + // Check the default value when no templates present in module is 1 + array( $modules['noTemplateModule'], 1 ), + ); + } + + /** + * @dataProvider providerGetModifiedTime + * @covers ResourceLoaderFileModule::getModifiedTime + */ + public function testGetModifiedTime( $module, $expected ) { + $rl = new ResourceLoaderFileModule( $module ); + $ts = $rl->getModifiedTime( $this->getResourceLoaderContext() ); + $this->assertEquals( $ts, $expected ); + } +} diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php new file mode 100644 index 00000000..d0bc210b --- /dev/null +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php @@ -0,0 +1,162 @@ + 'add.gif', + 'remove' => array( + 'file' => 'remove.svg', + 'variants' => array( 'destructive' ), + ), + 'next' => array( + 'file' => array( + 'ltr' => 'next.svg', + 'rtl' => 'prev.svg' + ), + ), + 'help' => array( + 'file' => array( + 'ltr' => 'help-ltr.svg', + 'rtl' => 'help-rtl.svg', + 'lang' => array( + 'he' => 'help-ltr.svg', + ) + ), + ), + 'bold' => array( + 'file' => array( + 'default' => 'bold-a.svg', + 'lang' => array( + 'en' => 'bold-b.svg', + 'ar,de' => 'bold-f.svg', + ) + ), + ) + ); + + public static $commonImageVariants = array( + 'invert' => array( + 'color' => '#FFFFFF', + 'global' => true, + ), + 'primary' => array( + 'color' => '#598AD1', + ), + 'constructive' => array( + 'color' => '#00C697', + ), + 'destructive' => array( + 'color' => '#E81915', + ), + ); + + public static function providerGetModules() { + return array( + array( + array( + 'class' => 'ResourceLoaderImageModule', + 'prefix' => 'oo-ui-icon', + 'variants' => self::$commonImageVariants, + 'images' => self::$commonImageData, + ), + '.oo-ui-icon-add { + ... +} +.oo-ui-icon-add-invert { + ... +} +.oo-ui-icon-remove { + ... +} +.oo-ui-icon-remove-invert { + ... +} +.oo-ui-icon-remove-destructive { + ... +} +.oo-ui-icon-next { + ... +} +.oo-ui-icon-next-invert { + ... +} +.oo-ui-icon-help { + ... +} +.oo-ui-icon-help-invert { + ... +} +.oo-ui-icon-bold { + ... +} +.oo-ui-icon-bold-invert { + ... +}', + ), + array( + array( + 'class' => 'ResourceLoaderImageModule', + 'selectorWithoutVariant' => '.mw-ui-icon-{name}:after, .mw-ui-icon-{name}:before', + 'selectorWithVariant' => '.mw-ui-icon-{name}-{variant}:after, .mw-ui-icon-{name}-{variant}:before', + 'variants' => self::$commonImageVariants, + 'images' => self::$commonImageData, + ), + '.mw-ui-icon-add:after, .mw-ui-icon-add:before { + ... +} +.mw-ui-icon-add-invert:after, .mw-ui-icon-add-invert:before { + ... +} +.mw-ui-icon-remove:after, .mw-ui-icon-remove:before { + ... +} +.mw-ui-icon-remove-invert:after, .mw-ui-icon-remove-invert:before { + ... +} +.mw-ui-icon-remove-destructive:after, .mw-ui-icon-remove-destructive:before { + ... +} +.mw-ui-icon-next:after, .mw-ui-icon-next:before { + ... +} +.mw-ui-icon-next-invert:after, .mw-ui-icon-next-invert:before { + ... +} +.mw-ui-icon-help:after, .mw-ui-icon-help:before { + ... +} +.mw-ui-icon-help-invert:after, .mw-ui-icon-help-invert:before { + ... +} +.mw-ui-icon-bold:after, .mw-ui-icon-bold:before { + ... +} +.mw-ui-icon-bold-invert:after, .mw-ui-icon-bold-invert:before { + ... +}', + ), + ); + } + + /** + * @dataProvider providerGetModules + * @covers ResourceLoaderImageModule::getStyles + */ + public function testGetStyles( $module, $expected ) { + $module = new ResourceLoaderImageModuleTestable( $module, __DIR__ . '/../../data/resourceloader' ); + $styles = $module->getStyles( $this->getResourceLoaderContext() ); + $this->assertEquals( $expected, $styles['all'] ); + } +} + +class ResourceLoaderImageModuleTestable extends ResourceLoaderImageModule { + /** + * Replace with a stub to make test cases easier to write. + */ + protected function getCssDeclarations( $primary, $fallback ) { + return array( '...' ); + } +} diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php new file mode 100644 index 00000000..758cfe19 --- /dev/null +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php @@ -0,0 +1,122 @@ +imagesPath = __DIR__ . '/../../data/resourceloader'; + } + + protected function getTestImage( $name ) { + $options = ResourceLoaderImageModuleTest::$commonImageData[$name]; + $fileDescriptor = is_string( $options ) ? $options : $options['file']; + $allowedVariants = is_array( $options ) && isset( $options['variants'] ) ? $options['variants'] : array(); + $variants = array_fill_keys( $allowedVariants, array( 'color' => 'red' ) ); + return new ResourceLoaderImageTestable( $name, 'test', $fileDescriptor, $this->imagesPath, $variants ); + } + + public static function provideGetPath() { + return array( + array( 'add', 'en', 'add.gif' ), + array( 'add', 'he', 'add.gif' ), + array( 'remove', 'en', 'remove.svg' ), + array( 'remove', 'he', 'remove.svg' ), + array( 'next', 'en', 'next.svg' ), + array( 'next', 'he', 'prev.svg' ), + array( 'help', 'en', 'help-ltr.svg' ), + array( 'help', 'ar', 'help-rtl.svg' ), + array( 'help', 'he', 'help-ltr.svg' ), + array( 'bold', 'en', 'bold-b.svg' ), + array( 'bold', 'de', 'bold-f.svg' ), + array( 'bold', 'ar', 'bold-f.svg' ), + array( 'bold', 'fr', 'bold-a.svg' ), + array( 'bold', 'he', 'bold-a.svg' ), + ); + } + + /** + * @covers ResourceLoaderImage::getPath + * @dataProvider provideGetPath + */ + public function testGetPath( $imageName, $languageCode, $path ) { + static $dirMap = array( + 'en' => 'ltr', + 'de' => 'ltr', + 'fr' => 'ltr', + 'he' => 'rtl', + 'ar' => 'rtl', + ); + static $contexts = array(); + + $image = $this->getTestImage( $imageName ); + $context = $this->getResourceLoaderContext( $languageCode, $dirMap[$languageCode] ); + + $this->assertEquals( $image->getPath( $context ), $this->imagesPath . '/' . $path ); + } + + /** + * @covers ResourceLoaderImage::getExtension + * @covers ResourceLoaderImage::getMimeType + */ + public function testGetExtension() { + $image = $this->getTestImage( 'remove' ); + $this->assertEquals( $image->getExtension(), 'svg' ); + $this->assertEquals( $image->getExtension( 'original' ), 'svg' ); + $this->assertEquals( $image->getExtension( 'rasterized' ), 'png' ); + $image = $this->getTestImage( 'add' ); + $this->assertEquals( $image->getExtension(), 'gif' ); + $this->assertEquals( $image->getExtension( 'original' ), 'gif' ); + $this->assertEquals( $image->getExtension( 'rasterized' ), 'gif' ); + } + + /** + * @covers ResourceLoaderImage::getImageData + * @covers ResourceLoaderImage::variantize + * @covers ResourceLoaderImage::massageSvgPathdata + */ + public function testGetImageData() { + $context = $this->getResourceLoaderContext( 'en', 'ltr' ); + + $image = $this->getTestImage( 'remove' ); + $data = file_get_contents( $this->imagesPath . '/remove.svg' ); + $dataConstructive = file_get_contents( $this->imagesPath . '/remove_variantize.svg' ); + $this->assertEquals( $image->getImageData( $context, null, 'original' ), $data ); + $this->assertEquals( $image->getImageData( $context, 'destructive', 'original' ), $dataConstructive ); + // Stub, since we don't know if we even have a SVG handler, much less what exactly it'll output + $this->assertEquals( $image->getImageData( $context, null, 'rasterized' ), 'RASTERIZESTUB' ); + + $image = $this->getTestImage( 'add' ); + $data = file_get_contents( $this->imagesPath . '/add.gif' ); + $this->assertEquals( $image->getImageData( $context, null, 'original' ), $data ); + $this->assertEquals( $image->getImageData( $context, null, 'rasterized' ), $data ); + } + + /** + * @covers ResourceLoaderImage::massageSvgPathdata + */ + public function testMassageSvgPathdata() { + $image = $this->getTestImage( 'next' ); + $data = file_get_contents( $this->imagesPath . '/next.svg' ); + $dataMassaged = file_get_contents( $this->imagesPath . '/next_massage.svg' ); + $this->assertEquals( $image->massageSvgPathdata( $data ), $dataMassaged ); + } +} + +class ResourceLoaderImageTestable extends ResourceLoaderImage { + // Make some protected methods public + public function getPath( ResourceLoaderContext $context ) { + return parent::getPath( $context ); + } + public function massageSvgPathdata( $svg ) { + return parent::massageSvgPathdata( $svg ); + } + // Stub, since we don't know if we even have a SVG handler, much less what exactly it'll output + public function rasterize( $svg ) { + return 'RASTERIZESTUB'; + } +} diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php index b0edaaf7..6d1ed4e0 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php @@ -2,71 +2,12 @@ class ResourceLoaderModuleTest extends ResourceLoaderTestCase { - protected function setUp() { - parent::setUp(); - - // The return value of the closure shouldn't matter since this test should - // never call it - SkinFactory::getDefaultInstance()->register( - 'fakeskin', - 'FakeSkin', - function () { - } - ); - } - - /** - * @covers ResourceLoaderFileModule::getAllSkinStyleFiles - */ - public function testGetAllSkinStyleFiles() { - $context = self::getResourceLoaderContext(); - - $baseParams = array( - 'scripts' => array( - 'foo.js', - 'bar.js', - ), - 'styles' => array( - 'foo.css', - 'bar.css' => array( 'media' => 'print' ), - 'screen.less' => array( 'media' => 'screen' ), - 'screen-query.css' => array( 'media' => 'screen and (min-width: 400px)' ), - ), - 'skinStyles' => array( - 'default' => 'quux-fallback.less', - 'fakeskin' => array( - 'baz-vector.css', - 'quux-vector.less', - ), - ), - 'messages' => array( - 'hello', - 'world', - ), - ); - - $module = new ResourceLoaderFileModule( $baseParams ); - - $this->assertEquals( - array( - 'foo.css', - 'baz-vector.css', - 'quux-vector.less', - 'quux-fallback.less', - 'bar.css', - 'screen.less', - 'screen-query.css', - ), - array_map( 'basename', $module->getAllStyleFiles() ) - ); - } - /** * @covers ResourceLoaderModule::getDefinitionSummary * @covers ResourceLoaderFileModule::getDefinitionSummary */ public function testDefinitionSummary() { - $context = self::getResourceLoaderContext(); + $context = $this->getResourceLoaderContext(); $baseParams = array( 'scripts' => array( 'foo.js', 'bar.js' ), diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php new file mode 100644 index 00000000..7f3506cc --- /dev/null +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php @@ -0,0 +1,388 @@ + 'Empty registry', + 'modules' => array(), + 'out' => ' +mw.loader.addSource( { + "local": "/w/load.php" +} );mw.loader.register( [] );' + ) ), + array( array( + 'msg' => 'Basic registry', + 'modules' => array( + 'test.blank' => new ResourceLoaderTestModule(), + ), + 'out' => ' +mw.loader.addSource( { + "local": "/w/load.php" +} );mw.loader.register( [ + [ + "test.blank", + 1388534400 + ] +] );', + ) ), + array( array( + 'msg' => 'Group signature', + 'modules' => array( + 'test.blank' => new ResourceLoaderTestModule(), + 'test.group.foo' => new ResourceLoaderTestModule( array( 'group' => 'x-foo' ) ), + 'test.group.bar' => new ResourceLoaderTestModule( array( 'group' => 'x-bar' ) ), + ), + 'out' => ' +mw.loader.addSource( { + "local": "/w/load.php" +} );mw.loader.register( [ + [ + "test.blank", + 1388534400 + ], + [ + "test.group.foo", + 1388534400, + [], + "x-foo" + ], + [ + "test.group.bar", + 1388534400, + [], + "x-bar" + ] +] );' + ) ), + array( array( + 'msg' => 'Different target (non-test should not be registered)', + 'modules' => array( + 'test.blank' => new ResourceLoaderTestModule(), + 'test.target.foo' => new ResourceLoaderTestModule( array( 'targets' => array( 'x-foo' ) ) ), + ), + 'out' => ' +mw.loader.addSource( { + "local": "/w/load.php" +} );mw.loader.register( [ + [ + "test.blank", + 1388534400 + ] +] );' + ) ), + array( array( + 'msg' => 'Foreign source', + 'sources' => array( + 'example' => array( + 'loadScript' => 'http://example.org/w/load.php', + 'apiScript' => 'http://example.org/w/api.php', + ), + ), + 'modules' => array( + 'test.blank' => new ResourceLoaderTestModule( array( 'source' => 'example' ) ), + ), + 'out' => ' +mw.loader.addSource( { + "local": "/w/load.php", + "example": "http://example.org/w/load.php" +} );mw.loader.register( [ + [ + "test.blank", + 1388534400, + [], + null, + "example" + ] +] );' + ) ), + array( array( + 'msg' => 'Conditional dependency function', + 'modules' => array( + 'test.x.core' => new ResourceLoaderTestModule(), + 'test.x.polyfill' => new ResourceLoaderTestModule( array( + 'skipFunction' => 'return true;' + ) ), + 'test.y.polyfill' => new ResourceLoaderTestModule( array( + 'skipFunction' => + 'return !!(' . + ' window.JSON &&' . + ' JSON.parse &&' . + ' JSON.stringify' . + ');' + ) ), + 'test.z.foo' => new ResourceLoaderTestModule( array( + 'dependencies' => array( + 'test.x.core', + 'test.x.polyfill', + 'test.y.polyfill', + ), + ) ), + ), + 'out' => ' +mw.loader.addSource( { + "local": "/w/load.php" +} );mw.loader.register( [ + [ + "test.x.core", + 1388534400 + ], + [ + "test.x.polyfill", + 1388534400, + [], + null, + null, + "return true;" + ], + [ + "test.y.polyfill", + 1388534400, + [], + null, + null, + "return !!( window.JSON \u0026\u0026 JSON.parse \u0026\u0026 JSON.stringify);" + ], + [ + "test.z.foo", + 1388534400, + [ + 0, + 1, + 2 + ] + ] +] );', + ) ), + array( array( + // This may seem like an edge case, but a plain MediaWiki core install + // with a few extensions installed is likely far more complex than this + // even, not to mention an install like Wikipedia. + // TODO: Make this even more realistic. + 'msg' => 'Advanced (everything combined)', + 'sources' => array( + 'example' => array( + 'loadScript' => 'http://example.org/w/load.php', + 'apiScript' => 'http://example.org/w/api.php', + ), + ), + 'modules' => array( + 'test.blank' => new ResourceLoaderTestModule(), + 'test.x.core' => new ResourceLoaderTestModule(), + 'test.x.util' => new ResourceLoaderTestModule( array( + 'dependencies' => array( + 'test.x.core', + ), + ) ), + 'test.x.foo' => new ResourceLoaderTestModule( array( + 'dependencies' => array( + 'test.x.core', + ), + ) ), + 'test.x.bar' => new ResourceLoaderTestModule( array( + 'dependencies' => array( + 'test.x.core', + 'test.x.util', + ), + ) ), + 'test.x.quux' => new ResourceLoaderTestModule( array( + 'dependencies' => array( + 'test.x.foo', + 'test.x.bar', + 'test.x.util', + 'test.x.unknown', + ), + ) ), + 'test.group.foo.1' => new ResourceLoaderTestModule( array( + 'group' => 'x-foo', + ) ), + 'test.group.foo.2' => new ResourceLoaderTestModule( array( + 'group' => 'x-foo', + ) ), + 'test.group.bar.1' => new ResourceLoaderTestModule( array( + 'group' => 'x-bar', + ) ), + 'test.group.bar.2' => new ResourceLoaderTestModule( array( + 'group' => 'x-bar', + 'source' => 'example', + ) ), + 'test.target.foo' => new ResourceLoaderTestModule( array( + 'targets' => array( 'x-foo' ), + ) ), + 'test.target.bar' => new ResourceLoaderTestModule( array( + 'source' => 'example', + 'targets' => array( 'x-foo' ), + ) ), + ), + 'out' => ' +mw.loader.addSource( { + "local": "/w/load.php", + "example": "http://example.org/w/load.php" +} );mw.loader.register( [ + [ + "test.blank", + 1388534400 + ], + [ + "test.x.core", + 1388534400 + ], + [ + "test.x.util", + 1388534400, + [ + 1 + ] + ], + [ + "test.x.foo", + 1388534400, + [ + 1 + ] + ], + [ + "test.x.bar", + 1388534400, + [ + 2 + ] + ], + [ + "test.x.quux", + 1388534400, + [ + 3, + 4, + "test.x.unknown" + ] + ], + [ + "test.group.foo.1", + 1388534400, + [], + "x-foo" + ], + [ + "test.group.foo.2", + 1388534400, + [], + "x-foo" + ], + [ + "test.group.bar.1", + 1388534400, + [], + "x-bar" + ], + [ + "test.group.bar.2", + 1388534400, + [], + "x-bar", + "example" + ] +] );' + ) ), + ); + } + + /** + * @dataProvider provideGetModuleRegistrations + * @covers ResourceLoaderStartUpModule::compileUnresolvedDependencies + * @covers ResourceLoaderStartUpModule::getModuleRegistrations + * @covers ResourceLoader::makeLoaderSourcesScript + * @covers ResourceLoader::makeLoaderRegisterScript + */ + public function testGetModuleRegistrations( $case ) { + if ( isset( $case['sources'] ) ) { + $this->setMwGlobals( 'wgResourceLoaderSources', $case['sources'] ); + } + + $context = $this->getResourceLoaderContext(); + $rl = $context->getResourceLoader(); + + $rl->register( $case['modules'] ); + + $module = new ResourceLoaderStartUpModule(); + $this->assertEquals( + ltrim( $case['out'], "\n" ), + $module->getModuleRegistrations( $context ), + $case['msg'] + ); + } + + public static function provideRegistrations() { + return array( + array( array( + 'test.blank' => new ResourceLoaderTestModule(), + 'test.min' => new ResourceLoaderTestModule( array( + 'skipFunction' => + 'return !!(' . + ' window.JSON &&' . + ' JSON.parse &&' . + ' JSON.stringify' . + ');', + 'dependencies' => array( + 'test.blank', + ), + ) ), + ) ) + ); + } + /** + * @dataProvider provideRegistrations + */ + public function testRegistrationsMinified( $modules ) { + $this->setMwGlobals( 'wgResourceLoaderDebug', false ); + + $context = $this->getResourceLoaderContext(); + $rl = $context->getResourceLoader(); + $rl->register( $modules ); + $module = new ResourceLoaderStartUpModule(); + $this->assertEquals( +'mw.loader.addSource({"local":"/w/load.php"});' +. 'mw.loader.register([' +. '["test.blank",1388534400],' +. '["test.min",1388534400,[0],null,null,' +. '"return!!(window.JSON\u0026\u0026JSON.parse\u0026\u0026JSON.stringify);"' +. ']]);', + $module->getModuleRegistrations( $context ), + 'Minified output' + ); + } + + /** + * @dataProvider provideRegistrations + */ + public function testRegistrationsUnminified( $modules ) { + $context = $this->getResourceLoaderContext(); + $rl = $context->getResourceLoader(); + $rl->register( $modules ); + $module = new ResourceLoaderStartUpModule(); + $this->assertEquals( +'mw.loader.addSource( { + "local": "/w/load.php" +} );mw.loader.register( [ + [ + "test.blank", + 1388534400 + ], + [ + "test.min", + 1388534400, + [ + 0 + ], + null, + null, + "return !!( window.JSON \u0026\u0026 JSON.parse \u0026\u0026 JSON.stringify);" + ] +] );', + $module->getModuleRegistrations( $context ), + 'Unminified output' + ); + } + +} diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderStartupModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderStartupModuleTest.php deleted file mode 100644 index a1893873..00000000 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderStartupModuleTest.php +++ /dev/null @@ -1,388 +0,0 @@ - 'Empty registry', - 'modules' => array(), - 'out' => ' -mw.loader.addSource( { - "local": "/w/load.php" -} );mw.loader.register( [] );' - ) ), - array( array( - 'msg' => 'Basic registry', - 'modules' => array( - 'test.blank' => new ResourceLoaderTestModule(), - ), - 'out' => ' -mw.loader.addSource( { - "local": "/w/load.php" -} );mw.loader.register( [ - [ - "test.blank", - "1388534400" - ] -] );', - ) ), - array( array( - 'msg' => 'Group signature', - 'modules' => array( - 'test.blank' => new ResourceLoaderTestModule(), - 'test.group.foo' => new ResourceLoaderTestModule( array( 'group' => 'x-foo' ) ), - 'test.group.bar' => new ResourceLoaderTestModule( array( 'group' => 'x-bar' ) ), - ), - 'out' => ' -mw.loader.addSource( { - "local": "/w/load.php" -} );mw.loader.register( [ - [ - "test.blank", - "1388534400" - ], - [ - "test.group.foo", - "1388534400", - [], - "x-foo" - ], - [ - "test.group.bar", - "1388534400", - [], - "x-bar" - ] -] );' - ) ), - array( array( - 'msg' => 'Different target (non-test should not be registered)', - 'modules' => array( - 'test.blank' => new ResourceLoaderTestModule(), - 'test.target.foo' => new ResourceLoaderTestModule( array( 'targets' => array( 'x-foo' ) ) ), - ), - 'out' => ' -mw.loader.addSource( { - "local": "/w/load.php" -} );mw.loader.register( [ - [ - "test.blank", - "1388534400" - ] -] );' - ) ), - array( array( - 'msg' => 'Foreign source', - 'sources' => array( - 'example' => array( - 'loadScript' => 'http://example.org/w/load.php', - 'apiScript' => 'http://example.org/w/api.php', - ), - ), - 'modules' => array( - 'test.blank' => new ResourceLoaderTestModule( array( 'source' => 'example' ) ), - ), - 'out' => ' -mw.loader.addSource( { - "local": "/w/load.php", - "example": "http://example.org/w/load.php" -} );mw.loader.register( [ - [ - "test.blank", - "1388534400", - [], - null, - "example" - ] -] );' - ) ), - array( array( - 'msg' => 'Conditional dependency function', - 'modules' => array( - 'test.x.core' => new ResourceLoaderTestModule(), - 'test.x.polyfill' => new ResourceLoaderTestModule( array( - 'skipFunction' => 'return true;' - ) ), - 'test.y.polyfill' => new ResourceLoaderTestModule( array( - 'skipFunction' => - 'return !!(' . - ' window.JSON &&' . - ' JSON.parse &&' . - ' JSON.stringify' . - ');' - ) ), - 'test.z.foo' => new ResourceLoaderTestModule( array( - 'dependencies' => array( - 'test.x.core', - 'test.x.polyfil', - 'test.y.polyfil', - ), - ) ), - ), - 'out' => ' -mw.loader.addSource( { - "local": "/w/load.php" -} );mw.loader.register( [ - [ - "test.x.core", - "1388534400" - ], - [ - "test.x.polyfill", - "1388534400", - [], - null, - "local", - "return true;" - ], - [ - "test.y.polyfill", - "1388534400", - [], - null, - "local", - "return !!( window.JSON \u0026\u0026 JSON.parse \u0026\u0026 JSON.stringify);" - ], - [ - "test.z.foo", - "1388534400", - [ - "test.x.core", - "test.x.polyfil", - "test.y.polyfil" - ] - ] -] );', - ) ), - array( array( - // This may seem like an edge case, but a plain MediaWiki core install - // with a few extensions installed is likely far more complex than this - // even, not to mention an install like Wikipedia. - // TODO: Make this even more realistic. - 'msg' => 'Advanced (everything combined)', - 'sources' => array( - 'example' => array( - 'loadScript' => 'http://example.org/w/load.php', - 'apiScript' => 'http://example.org/w/api.php', - ), - ), - 'modules' => array( - 'test.blank' => new ResourceLoaderTestModule(), - 'test.x.core' => new ResourceLoaderTestModule(), - 'test.x.util' => new ResourceLoaderTestModule( array( - 'dependencies' => array( - 'test.x.core', - ), - ) ), - 'test.x.foo' => new ResourceLoaderTestModule( array( - 'dependencies' => array( - 'test.x.core', - ), - ) ), - 'test.x.bar' => new ResourceLoaderTestModule( array( - 'dependencies' => array( - 'test.x.core', - 'test.x.util', - ), - ) ), - 'test.x.quux' => new ResourceLoaderTestModule( array( - 'dependencies' => array( - 'test.x.foo', - 'test.x.bar', - 'test.x.util', - 'test.x.unknown', - ), - ) ), - 'test.group.foo.1' => new ResourceLoaderTestModule( array( - 'group' => 'x-foo', - ) ), - 'test.group.foo.2' => new ResourceLoaderTestModule( array( - 'group' => 'x-foo', - ) ), - 'test.group.bar.1' => new ResourceLoaderTestModule( array( - 'group' => 'x-bar', - ) ), - 'test.group.bar.2' => new ResourceLoaderTestModule( array( - 'group' => 'x-bar', - 'source' => 'example', - ) ), - 'test.target.foo' => new ResourceLoaderTestModule( array( - 'targets' => array( 'x-foo' ), - ) ), - 'test.target.bar' => new ResourceLoaderTestModule( array( - 'source' => 'example', - 'targets' => array( 'x-foo' ), - ) ), - ), - 'out' => ' -mw.loader.addSource( { - "local": "/w/load.php", - "example": "http://example.org/w/load.php" -} );mw.loader.register( [ - [ - "test.blank", - "1388534400" - ], - [ - "test.x.core", - "1388534400" - ], - [ - "test.x.util", - "1388534400", - [ - "test.x.core" - ] - ], - [ - "test.x.foo", - "1388534400", - [ - "test.x.core" - ] - ], - [ - "test.x.bar", - "1388534400", - [ - "test.x.util" - ] - ], - [ - "test.x.quux", - "1388534400", - [ - "test.x.foo", - "test.x.bar", - "test.x.unknown" - ] - ], - [ - "test.group.foo.1", - "1388534400", - [], - "x-foo" - ], - [ - "test.group.foo.2", - "1388534400", - [], - "x-foo" - ], - [ - "test.group.bar.1", - "1388534400", - [], - "x-bar" - ], - [ - "test.group.bar.2", - "1388534400", - [], - "x-bar", - "example" - ] -] );' - ) ), - ); - } - - /** - * @dataProvider provideGetModuleRegistrations - * @covers ResourceLoaderStartupModule::optimizeDependencies - * @covers ResourceLoaderStartUpModule::getModuleRegistrations - * @covers ResourceLoader::makeLoaderSourcesScript - * @covers ResourceLoader::makeLoaderRegisterScript - */ - public function testGetModuleRegistrations( $case ) { - if ( isset( $case['sources'] ) ) { - $this->setMwGlobals( 'wgResourceLoaderSources', $case['sources'] ); - } - - $context = self::getResourceLoaderContext(); - $rl = $context->getResourceLoader(); - - $rl->register( $case['modules'] ); - - $module = new ResourceLoaderStartUpModule(); - $this->assertEquals( - ltrim( $case['out'], "\n" ), - $module->getModuleRegistrations( $context ), - $case['msg'] - ); - } - - public static function provideRegistrations() { - return array( - array( array( - 'test.blank' => new ResourceLoaderTestModule(), - 'test.min' => new ResourceLoaderTestModule( array( - 'skipFunction' => - 'return !!(' . - ' window.JSON &&' . - ' JSON.parse &&' . - ' JSON.stringify' . - ');', - 'dependencies' => array( - 'test.blank', - ), - ) ), - ) ) - ); - } - /** - * @dataProvider provideRegistrations - */ - public function testRegistrationsMinified( $modules ) { - $this->setMwGlobals( 'wgResourceLoaderDebug', false ); - - $context = self::getResourceLoaderContext(); - $rl = $context->getResourceLoader(); - $rl->register( $modules ); - $module = new ResourceLoaderStartUpModule(); - $this->assertEquals( -'mw.loader.addSource({"local":"/w/load.php"});' -. 'mw.loader.register([' -. '["test.blank","1388534400"],' -. '["test.min","1388534400",["test.blank"],null,"local",' -. '"return!!(window.JSON\u0026\u0026JSON.parse\u0026\u0026JSON.stringify);"' -. ']]);', - $module->getModuleRegistrations( $context ), - 'Minified output' - ); - } - - /** - * @dataProvider provideRegistrations - */ - public function testRegistrationsUnminified( $modules ) { - $context = self::getResourceLoaderContext(); - $rl = $context->getResourceLoader(); - $rl->register( $modules ); - $module = new ResourceLoaderStartUpModule(); - $this->assertEquals( -'mw.loader.addSource( { - "local": "/w/load.php" -} );mw.loader.register( [ - [ - "test.blank", - "1388534400" - ], - [ - "test.min", - "1388534400", - [ - "test.blank" - ], - null, - "local", - "return !!( window.JSON \u0026\u0026 JSON.parse \u0026\u0026 JSON.stringify);" - ] -] );', - $module->getModuleRegistrations( $context ), - 'Unminified output' - ); - } - -} diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php index f19f6886..ca7307ec 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php @@ -2,13 +2,9 @@ class ResourceLoaderTest extends ResourceLoaderTestCase { - protected static $resourceLoaderRegisterModulesHook; - protected function setUp() { parent::setUp(); - // $wgResourceLoaderLESSFunctions, $wgResourceLoaderLESSImportPaths; $wgResourceLoaderLESSVars; - $this->setMwGlobals( array( 'wgResourceLoaderLESSFunctions' => array( 'test-sum' => function ( $frame, $less ) { @@ -30,35 +26,33 @@ class ResourceLoaderTest extends ResourceLoaderTestCase { ) ); } - /* Hook Methods */ - - /** - * ResourceLoaderRegisterModules hook - */ - public static function resourceLoaderRegisterModules( &$resourceLoader ) { - self::$resourceLoaderRegisterModulesHook = true; - - return true; - } - - /* Provider Methods */ public static function provideValidModules() { return array( array( 'TEST.validModule1', new ResourceLoaderTestModule() ), ); } - /* Test Methods */ - /** * Ensures that the ResourceLoaderRegisterModules hook is called when a new * ResourceLoader object is constructed. * @covers ResourceLoader::__construct */ public function testCreatingNewResourceLoaderCallsRegistrationHook() { - self::$resourceLoaderRegisterModulesHook = false; + $resourceLoaderRegisterModulesHook = false; + + $this->setMwGlobals( 'wgHooks', array( + 'ResourceLoaderRegisterModules' => array( + function ( &$resourceLoader ) use ( &$resourceLoaderRegisterModulesHook ) { + $resourceLoaderRegisterModulesHook = true; + } + ) + ) ); + $resourceLoader = new ResourceLoader(); - $this->assertTrue( self::$resourceLoaderRegisterModulesHook ); + $this->assertTrue( + $resourceLoaderRegisterModulesHook, + 'Hook ResourceLoaderRegisterModules called' + ); return $resourceLoader; } @@ -80,7 +74,7 @@ class ResourceLoaderTest extends ResourceLoaderTestCase { * @covers ResourceLoaderFileModule::compileLessFile */ public function testLessFileCompilation() { - $context = self::getResourceLoaderContext(); + $context = $this->getResourceLoaderContext(); $basePath = __DIR__ . '/../../data/less/module'; $module = new ResourceLoaderFileModule( array( 'localBasePath' => $basePath, @@ -96,42 +90,10 @@ class ResourceLoaderTest extends ResourceLoaderTestCase { * @param string $css * @return string */ - private function stripNoflip( $css ) { + private static function stripNoflip( $css ) { return str_replace( '/*@noflip*/ ', '', $css ); } - /** - * What happens when you mix @embed and @noflip? - * This really is an integration test, but oh well. - */ - public function testMixedCssAnnotations( ) { - $basePath = __DIR__ . '/../../data/css'; - $testModule = new ResourceLoaderFileModule( array( - 'localBasePath' => $basePath, - 'styles' => array( 'test.css' ), - ) ); - $expectedModule = new ResourceLoaderFileModule( array( - 'localBasePath' => $basePath, - 'styles' => array( 'expected.css' ), - ) ); - - $contextLtr = self::getResourceLoaderContext( 'en' ); - $contextRtl = self::getResourceLoaderContext( 'he' ); - - // Since we want to compare the effect of @noflip+@embed against the effect of just @embed, and - // the @noflip annotations are always preserved, we need to strip them first. - $this->assertEquals( - $expectedModule->getStyles( $contextLtr ), - $this->stripNoflip( $testModule->getStyles( $contextLtr ) ), - "/*@noflip*/ with /*@embed*/ gives correct results in LTR mode" - ); - $this->assertEquals( - $expectedModule->getStyles( $contextLtr ), - $this->stripNoflip( $testModule->getStyles( $contextRtl ) ), - "/*@noflip*/ with /*@embed*/ gives correct results in RTL mode" - ); - } - /** * @dataProvider providePackedModules * @covers ResourceLoader::makePackedModulesString @@ -193,6 +155,7 @@ class ResourceLoaderTest extends ResourceLoaderTestCase { /** * @dataProvider provideAddSource * @covers ResourceLoader::addSource + * @covers ResourceLoader::getSources */ public function testAddSource( $name, $info, $expected ) { $rl = new ResourceLoader; @@ -223,6 +186,106 @@ class ResourceLoaderTest extends ResourceLoaderTestCase { ); } + public static function provideLoaderImplement() { + return array( + array( array( + 'title' => 'Implement scripts, styles and messages', + + 'name' => 'test.example', + 'scripts' => 'mw.example();', + 'styles' => array( 'css' => array( '.mw-example {}' ) ), + 'messages' => array( 'example' => '' ), + 'templates' => array(), + + 'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery ) { +mw.example(); +}, { + "css": [ + ".mw-example {}" + ] +}, { + "example": "" +} );', + ) ), + array( array( + 'title' => 'Implement scripts', + + 'name' => 'test.example', + 'scripts' => 'mw.example();', + 'styles' => array(), + 'messages' => new XmlJsCode( '{}' ), + 'templates' => array(), + 'title' => 'scripts, styles and messags', + + 'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery ) { +mw.example(); +} );', + ) ), + array( array( + 'title' => 'Implement styles', + + 'name' => 'test.example', + 'scripts' => array(), + 'styles' => array( 'css' => array( '.mw-example {}' ) ), + 'messages' => new XmlJsCode( '{}' ), + 'templates' => array(), + + 'expected' => 'mw.loader.implement( "test.example", [], { + "css": [ + ".mw-example {}" + ] +} );', + ) ), + array( array( + 'title' => 'Implement scripts and messages', + + 'name' => 'test.example', + 'scripts' => 'mw.example();', + 'styles' => array(), + 'messages' => array( 'example' => '' ), + 'templates' => array(), + + 'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery ) { +mw.example(); +}, {}, { + "example": "" +} );', + ) ), + array( array( + 'title' => 'Implement scripts and templates', + + 'name' => 'test.example', + 'scripts' => 'mw.example();', + 'styles' => array(), + 'messages' => new XmlJsCode( '{}' ), + 'templates' => array( 'example.html' => '' ), + + 'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery ) { +mw.example(); +}, {}, {}, { + "example.html": "" +} );', + ) ), + ); + } + + /** + * @dataProvider provideLoaderImplement + * @covers ResourceLoader::makeLoaderImplementScript + */ + public function testMakeLoaderImplementScript( $case ) { + $this->assertEquals( + $case['expected'], + ResourceLoader::makeLoaderImplementScript( + $case['name'], + $case['scripts'], + $case['styles'], + $case['messages'], + $case['templates'] + ) + ); + } + /** * @covers ResourceLoader::getLoadScript */ @@ -242,8 +305,14 @@ class ResourceLoaderTest extends ResourceLoaderTestCase { $this->assertTrue( true ); } } -} -/* Hooks */ -global $wgHooks; -$wgHooks['ResourceLoaderRegisterModules'][] = 'ResourceLoaderTest::resourceLoaderRegisterModules'; + /** + * @covers ResourceLoader::isModuleRegistered + */ + public function testIsModuleRegistered() { + $rl = new ResourceLoader(); + $rl->register( 'test.module', new ResourceLoaderTestModule() ); + $this->assertTrue( $rl->isModuleRegistered( 'test.module' ) ); + $this->assertFalse( $rl->isModuleRegistered( 'test.modulenotregistered' ) ); + } +} diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php index 9dc18050..93a3ebba 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php @@ -2,12 +2,94 @@ class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase { + /** + * @covers ResourceLoaderWikiModule::__construct + * @dataProvider provideConstructor + */ + public function testConstructor( $params ) { + $module = new ResourceLoaderWikiModule( $params ); + $this->assertInstanceOf( 'ResourceLoaderWikiModule', $module ); + } + + public static function provideConstructor() { + return array( + // Nothing + array( null ), + array( array() ), + // Unrecognized settings + array( array( 'foo' => 'baz' ) ), + // Real settings + array( array( 'scripts' => array( 'MediaWiki:Common.js' ) ) ), + ); + } + + /** + * @dataProvider provideGetPages + * @covers ResourceLoaderWikiModule::getPages + */ + public function testGetPages( $params, Config $config, $expected ) { + $module = new ResourceLoaderWikiModule( $params ); + $module->setConfig( $config ); + + // Use getDefinitionSummary because getPages is protected + $summary = $module->getDefinitionSummary( ResourceLoaderContext::newDummyContext() ); + $this->assertEquals( + $expected, + $summary['pages'] + ); + } + + public static function provideGetPages() { + $settings = array( + 'UseSiteJs' => true, + 'UseSiteCss' => true, + ); + + $params = array( + 'styles' => array( 'MediaWiki:Common.css' ), + 'scripts' => array( 'MediaWiki:Common.js' ), + ); + + return array( + array( array(), new HashConfig( $settings ), array() ), + array( $params, new HashConfig( $settings ), array( + 'MediaWiki:Common.js' => array( 'type' => 'script' ), + 'MediaWiki:Common.css' => array( 'type' => 'style' ) + ) ), + array( $params, new HashConfig( array( 'UseSiteCss' => false ) + $settings ), array( + 'MediaWiki:Common.js' => array( 'type' => 'script' ), + ) ), + array( $params, new HashConfig( array( 'UseSiteJs' => false ) + $settings ), array( + 'MediaWiki:Common.css' => array( 'type' => 'style' ), + ) ), + array( $params, new HashConfig( array( 'UseSiteJs' => false, 'UseSiteCss' => false ) ), array() ), + ); + } + + /** + * @covers ResourceLoaderWikiModule::getGroup + * @dataProvider provideGetGroup + */ + public function testGetGroup( $params, $expected ) { + $module = new ResourceLoaderWikiModule( $params ); + $this->assertEquals( $expected, $module->getGroup() ); + } + + public static function provideGetGroup() { + return array( + // No group specified + array( array(), null ), + // A random group + array( array( 'group' => 'foobar' ), 'foobar' ), + ); + } + /** * @covers ResourceLoaderWikiModule::isKnownEmpty * @dataProvider provideIsKnownEmpty */ public function testIsKnownEmpty( $titleInfo, $group, $expected ) { - $module = $this->getMockBuilder( 'ResourceLoaderWikiModuleTestModule' ) + $module = $this->getMockBuilder( 'ResourceLoaderWikiModule' ) ->setMethods( array( 'getTitleInfo', 'getGroup' ) ) ->getMock(); $module->expects( $this->any() ) diff --git a/tests/phpunit/includes/resourceloader/templates/template.html b/tests/phpunit/includes/resourceloader/templates/template.html new file mode 100644 index 00000000..1f6a7d22 --- /dev/null +++ b/tests/phpunit/includes/resourceloader/templates/template.html @@ -0,0 +1 @@ +hello diff --git a/tests/phpunit/includes/resourceloader/templates/template2.html b/tests/phpunit/includes/resourceloader/templates/template2.html new file mode 100644 index 00000000..a322f67d --- /dev/null +++ b/tests/phpunit/includes/resourceloader/templates/template2.html @@ -0,0 +1 @@ +
goodbye
diff --git a/tests/phpunit/includes/resourceloader/templates/template_awesome.handlebars b/tests/phpunit/includes/resourceloader/templates/template_awesome.handlebars new file mode 100644 index 00000000..5f5c07d5 --- /dev/null +++ b/tests/phpunit/includes/resourceloader/templates/template_awesome.handlebars @@ -0,0 +1 @@ +wow diff --git a/tests/phpunit/includes/search/SearchEngineTest.php b/tests/phpunit/includes/search/SearchEngineTest.php index 3da13615..d0cbfa0a 100644 --- a/tests/phpunit/includes/search/SearchEngineTest.php +++ b/tests/phpunit/includes/search/SearchEngineTest.php @@ -14,8 +14,6 @@ class SearchEngineTest extends MediaWikiLangTestCase { */ protected $search; - protected $pageList; - /** * Checks for database type & version. * Will skip current test if DB does not support search. @@ -37,10 +35,6 @@ class SearchEngineTest extends MediaWikiLangTestCase { 'wgSearchType' => $searchType ) ); - if ( !isset( self::$pageList ) ) { - $this->addPages(); - } - $this->search = new $searchType( $this->db ); } @@ -50,33 +44,32 @@ class SearchEngineTest extends MediaWikiLangTestCase { parent::tearDown(); } - protected function addPages() { + public function addDBData() { if ( !$this->isWikitextNS( NS_MAIN ) ) { // @todo cover the case of non-wikitext content in the main namespace return; } - $this->insertPage( "Not_Main_Page", "This is not a main page", 0 ); + $this->insertPage( 'Not_Main_Page', 'This is not a main page' ); $this->insertPage( 'Talk:Not_Main_Page', - 'This is not a talk page to the main page, see [[smithee]]', - 1 + 'This is not a talk page to the main page, see [[smithee]]' ); - $this->insertPage( 'Smithee', 'A smithee is one who smiths. See also [[Alan Smithee]]', 0 ); - $this->insertPage( 'Talk:Smithee', 'This article sucks.', 1 ); - $this->insertPage( 'Unrelated_page', 'Nothing in this page is about the S word.', 0 ); - $this->insertPage( 'Another_page', 'This page also is unrelated.', 0 ); - $this->insertPage( 'Help:Help', 'Help me!', 4 ); - $this->insertPage( 'Thppt', 'Blah blah', 0 ); - $this->insertPage( 'Alan_Smithee', 'yum', 0 ); - $this->insertPage( 'Pages', 'are\'food', 0 ); - $this->insertPage( 'HalfOneUp', 'AZ', 0 ); - $this->insertPage( 'FullOneUp', 'AZ', 0 ); - $this->insertPage( 'HalfTwoLow', 'az', 0 ); - $this->insertPage( 'FullTwoLow', 'az', 0 ); - $this->insertPage( 'HalfNumbers', '1234567890', 0 ); - $this->insertPage( 'FullNumbers', '1234567890', 0 ); - $this->insertPage( 'DomainName', 'example.com', 0 ); + $this->insertPage( 'Smithee', 'A smithee is one who smiths. See also [[Alan Smithee]]' ); + $this->insertPage( 'Talk:Smithee', 'This article sucks.' ); + $this->insertPage( 'Unrelated_page', 'Nothing in this page is about the S word.' ); + $this->insertPage( 'Another_page', 'This page also is unrelated.' ); + $this->insertPage( 'Help:Help', 'Help me!' ); + $this->insertPage( 'Thppt', 'Blah blah' ); + $this->insertPage( 'Alan_Smithee', 'yum' ); + $this->insertPage( 'Pages', 'are\'food' ); + $this->insertPage( 'HalfOneUp', 'AZ' ); + $this->insertPage( 'FullOneUp', 'AZ' ); + $this->insertPage( 'HalfTwoLow', 'az' ); + $this->insertPage( 'FullTwoLow', 'az' ); + $this->insertPage( 'HalfNumbers', '1234567890' ); + $this->insertPage( 'FullNumbers', '1234567890' ); + $this->insertPage( 'DomainName', 'example.com' ); } protected function fetchIds( $results ) { @@ -101,30 +94,6 @@ class SearchEngineTest extends MediaWikiLangTestCase { return $matches; } - /** - * Insert a new page - * - * @param string $pageName Page name - * @param string $text Page's content - * @param int $ns Unused - */ - protected function insertPage( $pageName, $text, $ns ) { - $title = Title::newFromText( $pageName, $ns ); - - $user = User::newFromName( 'WikiSysop' ); - $comment = 'Search Test'; - - // avoid memory leak...? - LinkCache::singleton()->clear(); - - $page = WikiPage::factory( $title ); - $page->doEditContent( ContentHandler::makeContent( $text, $title ), $comment, 0, false, $user ); - - $this->pageList[] = array( $title, $page->getId() ); - - return true; - } - public function testFullWidth() { $this->assertEquals( array( 'FullOneUp', 'FullTwoLow', 'HalfOneUp', 'HalfTwoLow' ), diff --git a/tests/phpunit/includes/search/SearchUpdateTest.php b/tests/phpunit/includes/search/SearchUpdateTest.php deleted file mode 100644 index c6275371..00000000 --- a/tests/phpunit/includes/search/SearchUpdateTest.php +++ /dev/null @@ -1,81 +0,0 @@ -setMwGlobals( 'wgSearchType', 'MockSearch' ); - } - - public function updateText( $text ) { - return trim( SearchUpdate::updateText( $text ) ); - } - - /** - * @covers SearchUpdate::updateText - */ - public function testUpdateText() { - $this->assertEquals( - 'test', - $this->updateText( '
TeSt
' ), - 'HTML stripped, text lowercased' - ); - - $this->assertEquals( - 'foo bar boz quux', - $this->updateText( << -
foo
bar - bozquux - -EOT - ), 'Stripping HTML tables' ); - - $this->assertEquals( - 'a b', - $this->updateText( 'a > b' ), - 'Handle unclosed tags' - ); - - $text = str_pad( "foo assertNotEquals( - '', - $this->updateText( $text ), - 'Bug 18609' - ); - } - - /** - * @covers SearchUpdate::updateText - * @todo give this test a real name explaining what is being tested here - */ - public function testBug32712() { - $text = "text „http://example.com“ text"; - $result = $this->updateText( $text ); - $processed = preg_replace( '/Q/u', 'Q', $result ); - $this->assertTrue( - $processed != '', - 'Link surrounded by unicode quotes should not fail UTF-8 validation' - ); - } -} diff --git a/tests/phpunit/includes/site/CachingSiteStoreTest.php b/tests/phpunit/includes/site/CachingSiteStoreTest.php new file mode 100644 index 00000000..d0a79803 --- /dev/null +++ b/tests/phpunit/includes/site/CachingSiteStoreTest.php @@ -0,0 +1,161 @@ + + */ +class CachingSiteStoreTest extends MediaWikiTestCase { + + /** + * @covers CachingSiteStore::getSites + */ + public function testGetSites() { + $testSites = TestSites::getSites(); + + $store = new CachingSiteStore( + $this->getHashSiteStore( $testSites ), + wfGetMainCache() + ); + + $sites = $store->getSites(); + + $this->assertInstanceOf( 'SiteList', $sites ); + + /** + * @var Site $site + */ + foreach ( $sites as $site ) { + $this->assertInstanceOf( 'Site', $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(), wfGetMainCache() ); + + $sites = array(); + + $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', $site ); + $this->assertEquals( 'en', $site->getLanguageCode() ); + + $site = $store->getSite( 'sdfhxujgkfpth' ); + $this->assertInstanceOf( 'Site', $site ); + $this->assertEquals( 'nl', $site->getLanguageCode() ); + } + + /** + * @covers CachingSiteStore::reset + */ + public function testReset() { + $dbSiteStore = $this->getMockBuilder( 'SiteStore' ) + ->disableOriginalConstructor() + ->getMock(); + + // php 5.3 compatibility! + $self = $this; + + $dbSiteStore->expects( $this->any() ) + ->method( 'getSite' ) + ->will( $this->returnValue( $self->getTestSite() ) ); + + $dbSiteStore->expects( $this->any() ) + ->method( 'getSites' ) + ->will( $this->returnCallback( function() use( $self ) { + $siteList = new SiteList(); + $siteList->setSite( $self->getTestSite() ); + + return $siteList; + } ) ); + + $store = new CachingSiteStore( $dbSiteStore, wfGetMainCache() ); + + // 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(), wfGetMainCache() ); + $this->assertTrue( $store->clear() ); + + $site = $store->getSite( 'enwiki' ); + $this->assertNull( $site ); + + $sites = $store->getSites(); + $this->assertEquals( 0, $sites->count() ); + } + + private function getHashSiteStore( array $sites ) { + $siteStore = new HashSiteStore(); + $siteStore->saveSites( $sites ); + + return $siteStore; + } + +} diff --git a/tests/phpunit/includes/site/DBSiteStoreTest.php b/tests/phpunit/includes/site/DBSiteStoreTest.php new file mode 100644 index 00000000..673ba54d --- /dev/null +++ b/tests/phpunit/includes/site/DBSiteStoreTest.php @@ -0,0 +1,133 @@ + + */ +class DBSiteStoreTest extends MediaWikiTestCase { + + /** + * @covers DBSiteStore::getSites + */ + public function testGetSites() { + $expectedSites = TestSites::getSites(); + TestSites::insertIntoDb(); + + $store = new DBSiteStore(); + + $sites = $store->getSites(); + + $this->assertInstanceOf( 'SiteList', $sites ); + + /** + * @var Site $site + */ + foreach ( $sites as $site ) { + $this->assertInstanceOf( 'Site', $site ); + } + + foreach ( $expectedSites as $site ) { + if ( $site->getGlobalId() !== null ) { + $this->assertTrue( $sites->hasSite( $site->getGlobalId() ) ); + } + } + } + + /** + * @covers DBSiteStore::saveSites + */ + public function testSaveSites() { + $store = new DBSiteStore(); + + $sites = array(); + + $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', $site ); + $this->assertEquals( 'en', $site->getLanguageCode() ); + $this->assertTrue( is_integer( $site->getInternalId() ) ); + $this->assertTrue( $site->getInternalId() >= 0 ); + + $site = $store->getSite( 'sdfhxujgkfpth' ); + $this->assertInstanceOf( 'Site', $site ); + $this->assertEquals( 'nl', $site->getLanguageCode() ); + $this->assertTrue( is_integer( $site->getInternalId() ) ); + $this->assertTrue( $site->getInternalId() >= 0 ); + } + + /** + * @covers DBSiteStore::reset + */ + public function testReset() { + $store1 = new DBSiteStore(); + $store2 = new DBSiteStore(); + + // initialize internal cache + $this->assertGreaterThan( 0, $store1->getSites()->count() ); + $this->assertGreaterThan( 0, $store2->getSites()->count() ); + + // Clear actual data. Will purge the external cache and reset the internal + // cache in $store1, but not the internal cache in store2. + $this->assertTrue( $store1->clear() ); + + // sanity check: $store2 should have a stale cache now + $this->assertNotNull( $store2->getSite( 'enwiki' ) ); + + // purge cache + $store2->reset(); + + // ...now the internal cache of $store2 should be updated and thus empty. + $site = $store2->getSite( 'enwiki' ); + $this->assertNull( $site ); + } + + /** + * @covers DBSiteStore::clear + */ + public function testClear() { + $store = new DBSiteStore(); + $this->assertTrue( $store->clear() ); + + $site = $store->getSite( 'enwiki' ); + $this->assertNull( $site ); + + $sites = $store->getSites(); + $this->assertEquals( 0, $sites->count() ); + } +} diff --git a/tests/phpunit/includes/site/FileBasedSiteLookupTest.php b/tests/phpunit/includes/site/FileBasedSiteLookupTest.php new file mode 100644 index 00000000..90ebe5c4 --- /dev/null +++ b/tests/phpunit/includes/site/FileBasedSiteLookupTest.php @@ -0,0 +1,101 @@ + + */ +class FileBasedSiteLookupTest extends PHPUnit_Framework_TestCase { + + protected function setUp() { + $this->cacheFile = $this->getCacheFile(); + } + + protected function tearDown() { + unlink( $this->cacheFile ); + } + + public function testGetSites() { + $sites = $this->getSites(); + $cacheBuilder = $this->newSitesCacheFileBuilder( $sites ); + $cacheBuilder->build(); + + $cache = new FileBasedSiteLookup( $this->cacheFile ); + $this->assertEquals( $sites, $cache->getSites() ); + } + + public function testGetSite() { + $sites = $this->getSites(); + $cacheBuilder = $this->newSitesCacheFileBuilder( $sites ); + $cacheBuilder->build(); + + $cache = new FileBasedSiteLookup( $this->cacheFile ); + + $this->assertEquals( $sites->getSite( 'enwiktionary' ), $cache->getSite( 'enwiktionary' ) ); + } + + private function newSitesCacheFileBuilder( SiteList $sites ) { + return new SitesCacheFileBuilder( + $this->getSiteLookup( $sites ), + $this->cacheFile + ); + } + + private function getSiteLookup( SiteList $sites ) { + $siteLookup = $this->getMockBuilder( 'SiteLookup' ) + ->disableOriginalConstructor() + ->getMock(); + + $siteLookup->expects( $this->any() ) + ->method( 'getSites' ) + ->will( $this->returnValue( $sites ) ); + + return $siteLookup; + } + + private function getSites() { + $sites = array(); + + $site = new Site(); + $site->setGlobalId( 'foobar' ); + $sites[] = $site; + + $site = new MediaWikiSite(); + $site->setGlobalId( 'enwiktionary' ); + $site->setGroup( 'wiktionary' ); + $site->setLanguageCode( 'en' ); + $site->addNavigationId( 'enwiktionary' ); + $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 ); + } + + private function getCacheFile() { + return tempnam( sys_get_temp_dir(), 'mw-test-sitelist' ); + } + +} diff --git a/tests/phpunit/includes/site/HashSiteStoreTest.php b/tests/phpunit/includes/site/HashSiteStoreTest.php new file mode 100644 index 00000000..49a96338 --- /dev/null +++ b/tests/phpunit/includes/site/HashSiteStoreTest.php @@ -0,0 +1,105 @@ + + */ +class HashSiteStoreTest extends MediaWikiTestCase { + + /** + * @covers HashSiteStore::getSites + */ + public function testGetSites() { + $expectedSites = array(); + + 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 = array(); + + $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/MediaWikiSiteTest.php b/tests/phpunit/includes/site/MediaWikiSiteTest.php index c3fd1557..ef2ccca2 100644 --- a/tests/phpunit/includes/site/MediaWikiSiteTest.php +++ b/tests/phpunit/includes/site/MediaWikiSiteTest.php @@ -26,7 +26,6 @@ * * @group Site * - * @licence GNU GPL v2+ * @author Jeroen De Dauw < jeroendedauw@gmail.com > */ class MediaWikiSiteTest extends SiteTest { diff --git a/tests/phpunit/includes/site/SiteExporterTest.php b/tests/phpunit/includes/site/SiteExporterTest.php new file mode 100644 index 00000000..19dd0aa1 --- /dev/null +++ b/tests/phpunit/includes/site/SiteExporterTest.php @@ -0,0 +1,147 @@ +setExpectedException( 'InvalidArgumentException' ); + + 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( array( $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->getMock( 'SiteStore' ); + + $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 array( + 'empty' => array( + new SiteList() + ), + + 'some' => array( + new SiteList( array( $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 new file mode 100644 index 00000000..cb0316ab --- /dev/null +++ b/tests/phpunit/includes/site/SiteImporterTest.php @@ -0,0 +1,200 @@ +getMock( 'SiteStore' ); + + $self = $this; + $store->expects( $this->once() ) + ->method( 'saveSites' ) + ->will( $this->returnCallback( function ( $sites ) use ( $expectedSites, $self ) { + $self->assertSitesEqual( $expectedSites, $sites ); + } ) ); + + $store->expects( $this->any() ) + ->method( 'getSites' ) + ->will( $this->returnValue( new SiteList() ) ); + + $errorHandler = $this->getMock( 'Psr\Log\LoggerInterface' ); + $errorHandler->expects( $this->exactly( $errorCount ) ) + ->method( 'error' ); + + $importer = new SiteImporter( $store ); + $importer->setExceptionCallback( array( $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 array( + 'empty' => array( + '', + array(), + ), + 'no sites' => array( + 'FooBla', + array(), + ), + 'minimal' => array( + '' . + 'Foo' . + '', + array( $foo ), + ), + 'full' => array( + '' . + '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/' . + '' . + '', + array( $foo, $acme, $dewiki ), + ), + 'skip' => array( + '' . + 'Foo' . + 'Foo' . + '' . + 'acme.com' . + 'acme' . + 'boop!' . + 'Test' . + 'http://acme.com/' . + '' . + '', + array( $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' ); + + $store = $this->getMock( 'SiteStore' ); + $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( array( $foo, $acme, $dewiki ), 0 ); + + $file = __DIR__ . '/SiteImporterTest.xml'; + $importer->importFromFile( $file ); + } + + /** + * @param Site[] $sites + * + * @return array[] + */ + private function getSerializedSiteList( $sites ) { + $serialized = array(); + + 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 new file mode 100644 index 00000000..720b1faf --- /dev/null +++ b/tests/phpunit/includes/site/SiteImporterTest.xml @@ -0,0 +1,19 @@ + + 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/site/SiteListTest.php b/tests/phpunit/includes/site/SiteListTest.php index 534ed9c9..d6c58cf6 100644 --- a/tests/phpunit/includes/site/SiteListTest.php +++ b/tests/phpunit/includes/site/SiteListTest.php @@ -26,7 +26,6 @@ * * @group Site * - * @licence GNU GPL v2+ * @author Jeroen De Dauw < jeroendedauw@gmail.com > */ class SiteListTest extends MediaWikiTestCase { diff --git a/tests/phpunit/includes/site/SiteSQLStoreTest.php b/tests/phpunit/includes/site/SiteSQLStoreTest.php index 6002c1a1..69088006 100644 --- a/tests/phpunit/includes/site/SiteSQLStoreTest.php +++ b/tests/phpunit/includes/site/SiteSQLStoreTest.php @@ -1,8 +1,6 @@ + * @author Katie Filbert < aude.wiki@gmail.com > */ class SiteSQLStoreTest extends MediaWikiTestCase { /** - * @covers SiteSQLStore::getSites - */ - public function testGetSites() { - $expectedSites = TestSites::getSites(); - TestSites::insertIntoDb(); - - $store = SiteSQLStore::newInstance(); - - $sites = $store->getSites(); - - $this->assertInstanceOf( 'SiteList', $sites ); - - /** - * @var Site $site - */ - foreach ( $sites as $site ) { - $this->assertInstanceOf( 'Site', $site ); - } - - foreach ( $expectedSites as $site ) { - if ( $site->getGlobalId() !== null ) { - $this->assertTrue( $sites->hasSite( $site->getGlobalId() ) ); - } - } - } - - /** - * @covers SiteSQLStore::saveSites - */ - public function testSaveSites() { - $store = SiteSQLStore::newInstance(); - - $sites = array(); - - $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', $site ); - $this->assertEquals( 'en', $site->getLanguageCode() ); - $this->assertTrue( is_integer( $site->getInternalId() ) ); - $this->assertTrue( $site->getInternalId() >= 0 ); - - $site = $store->getSite( 'sdfhxujgkfpth' ); - $this->assertInstanceOf( 'Site', $site ); - $this->assertEquals( 'nl', $site->getLanguageCode() ); - $this->assertTrue( is_integer( $site->getInternalId() ) ); - $this->assertTrue( $site->getInternalId() >= 0 ); - } - - /** - * @covers SiteSQLStore::reset + * @covers SiteSQLStore::newInstance */ - public function testReset() { - $store1 = SiteSQLStore::newInstance(); - $store2 = SiteSQLStore::newInstance(); - - // initialize internal cache - $this->assertGreaterThan( 0, $store1->getSites()->count() ); - $this->assertGreaterThan( 0, $store2->getSites()->count() ); - - // Clear actual data. Will purge the external cache and reset the internal - // cache in $store1, but not the internal cache in store2. - $this->assertTrue( $store1->clear() ); - - // sanity check: $store2 should have a stale cache now - $this->assertNotNull( $store2->getSite( 'enwiki' ) ); - - // purge cache - $store2->reset(); - - // ...now the internal cache of $store2 should be updated and thus empty. - $site = $store2->getSite( 'enwiki' ); - $this->assertNull( $site ); + public function testNewInstance() { + $siteStore = SiteSQLStore::newInstance(); + $this->assertInstanceOf( 'SiteSQLStore', $siteStore ); } - /** - * @covers SiteSQLStore::clear - */ - public function testClear() { - $store = SiteSQLStore::newInstance(); - $this->assertTrue( $store->clear() ); - - $site = $store->getSite( 'enwiki' ); - $this->assertNull( $site ); - - $sites = $store->getSites(); - $this->assertEquals( 0, $sites->count() ); - } } diff --git a/tests/phpunit/includes/site/SiteTest.php b/tests/phpunit/includes/site/SiteTest.php index 29c1ff33..63d90d2e 100644 --- a/tests/phpunit/includes/site/SiteTest.php +++ b/tests/phpunit/includes/site/SiteTest.php @@ -26,7 +26,6 @@ * * @group Site * - * @licence GNU GPL v2+ * @author Jeroen De Dauw < jeroendedauw@gmail.com > */ class SiteTest extends MediaWikiTestCase { diff --git a/tests/phpunit/includes/site/SitesCacheFileBuilderTest.php b/tests/phpunit/includes/site/SitesCacheFileBuilderTest.php new file mode 100644 index 00000000..087341a0 --- /dev/null +++ b/tests/phpunit/includes/site/SitesCacheFileBuilderTest.php @@ -0,0 +1,135 @@ + + */ +class SitesCacheFileBuilderTest extends PHPUnit_Framework_TestCase { + + protected function setUp() { + $this->cacheFile = $this->getCacheFile(); + } + + protected function tearDown() { + unlink( $this->cacheFile ); + } + + public function testBuild() { + $cacheBuilder = $this->newSitesCacheFileBuilder( $this->getSites() ); + $cacheBuilder->build(); + + $contents = file_get_contents( $this->cacheFile ); + $this->assertEquals( json_encode( $this->getExpectedData() ), $contents ); + } + + private function getExpectedData() { + return array( + 'sites' => array( + 'foobar' => array( + 'globalid' => 'foobar', + 'type' => 'unknown', + 'group' => 'none', + 'source' => 'local', + 'language' => null, + 'localids' => array(), + 'config' => array(), + 'data' => array(), + 'forward' => false, + 'internalid' => null, + 'identifiers' => array() + ), + 'enwiktionary' => array( + 'globalid' => 'enwiktionary', + 'type' => 'mediawiki', + 'group' => 'wiktionary', + 'source' => 'local', + 'language' => 'en', + 'localids' => array( + 'equivalent' => array( 'enwiktionary' ) + ), + 'config' => array(), + 'data' => array( + 'paths' => array( + 'page_path' => 'https://en.wiktionary.org/wiki/$1', + 'file_path' => 'https://en.wiktionary.org/w/$1' + ) + ), + 'forward' => false, + 'internalid' => null, + 'identifiers' => array( + array( + 'type' => 'equivalent', + 'key' => 'enwiktionary' + ) + ) + ) + ) + ); + } + + private function newSitesCacheFileBuilder( SiteList $sites ) { + return new SitesCacheFileBuilder( + $this->getSiteLookup( $sites ), + $this->cacheFile + ); + } + + private function getSiteLookup( SiteList $sites ) { + $siteLookup = $this->getMockBuilder( 'SiteLookup' ) + ->disableOriginalConstructor() + ->getMock(); + + $siteLookup->expects( $this->any() ) + ->method( 'getSites' ) + ->will( $this->returnValue( $sites ) ); + + return $siteLookup; + } + + private function getSites() { + $sites = array(); + + $site = new Site(); + $site->setGlobalId( 'foobar' ); + $sites[] = $site; + + $site = new MediaWikiSite(); + $site->setGlobalId( 'enwiktionary' ); + $site->setGroup( 'wiktionary' ); + $site->setLanguageCode( 'en' ); + $site->addNavigationId( 'enwiktionary' ); + $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 ); + } + + private function getCacheFile() { + return tempnam( sys_get_temp_dir(), 'mw-test-sitelist' ); + } + +} diff --git a/tests/phpunit/includes/site/TestSites.php b/tests/phpunit/includes/site/TestSites.php index af314ba2..4c402484 100644 --- a/tests/phpunit/includes/site/TestSites.php +++ b/tests/phpunit/includes/site/TestSites.php @@ -26,7 +26,6 @@ * * @group Site * - * @licence GNU GPL v2+ * @author Jeroen De Dauw < jeroendedauw@gmail.com > */ class TestSites { @@ -108,7 +107,7 @@ class TestSites { * @since 0.1 */ public static function insertIntoDb() { - $sitesTable = SiteSQLStore::newInstance(); + $sitesTable = new DBSiteStore(); $sitesTable->clear(); $sitesTable->saveSites( TestSites::getSites() ); } diff --git a/tests/phpunit/includes/skins/SkinTemplateTest.php b/tests/phpunit/includes/skins/SkinTemplateTest.php index baa995d4..8084a66f 100644 --- a/tests/phpunit/includes/skins/SkinTemplateTest.php +++ b/tests/phpunit/includes/skins/SkinTemplateTest.php @@ -5,7 +5,6 @@ * * @group Output * - * @licence GNU GPL v2+ * @author Bene* < benestar.wikimedia@gmail.com > */ diff --git a/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php b/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php index 779fa558..fd6911f6 100644 --- a/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php +++ b/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php @@ -28,21 +28,52 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { SpecialPageFactory::resetList(); } + public function testResetList() { + SpecialPageFactory::resetList(); + $this->assertContains( 'Specialpages', SpecialPageFactory::getNames() ); + } + + public function testHookNotCalledTwice() { + $count = 0; + $this->mergeMwGlobalArrayValue( 'wgHooks', array( + 'SpecialPage_initList' => array( + function () use ( &$count ) { + $count++; + } + ) ) ); + SpecialPageFactory::resetList(); + SpecialPageFactory::getNames(); + SpecialPageFactory::getNames(); + $this->assertEquals( 1, $count ); + } + public function newSpecialAllPages() { return new SpecialAllPages(); } public function specialPageProvider() { + $specialPageTestHelper = new SpecialPageTestHelper(); + return array( 'class name' => array( 'SpecialAllPages', false ), - 'closure' => array( function() { + 'closure' => array( function () { return new SpecialAllPages(); }, false ), - 'function' => array( array( $this, 'newSpecialAllPages' ), false ), + 'function' => array( array( $this, 'newSpecialAllPages' ), false ), + 'callback string' => array( 'SpecialPageTestHelper::newSpecialAllPages', false ), + 'callback with object' => array( + array( $specialPageTestHelper, 'newSpecialAllPages' ), + false + ), + 'callback array' => array( + array( 'SpecialPageTestHelper', 'newSpecialAllPages' ), + false + ) ); } /** + * @covers SpecialPageFactory::getPage * @dataProvider specialPageProvider */ public function testGetPage( $spec, $shouldReuseInstance ) { @@ -56,6 +87,9 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { $this->assertEquals( $shouldReuseInstance, $page2 === $page, "Should re-use instance:" ); } + /** + * @covers SpecialPageFactory::getNames + */ public function testGetNames() { $this->mergeMwGlobalArrayValue( 'wgSpecialPages', array( 'testdummy' => 'SpecialAllPages' ) ); SpecialPageFactory::resetList(); @@ -65,6 +99,9 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { $this->assertContains( 'testdummy', $names ); } + /** + * @covers SpecialPageFactory::resolveAlias + */ public function testResolveAlias() { $this->setMwGlobals( 'wgContLang', Language::factory( 'de' ) ); SpecialPageFactory::resetList(); @@ -74,6 +111,9 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { $this->assertEquals( 'Foo', $param ); } + /** + * @covers SpecialPageFactory::getLocalNameFor + */ public function testGetLocalNameFor() { $this->setMwGlobals( 'wgContLang', Language::factory( 'de' ) ); SpecialPageFactory::resetList(); @@ -82,6 +122,9 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { $this->assertEquals( 'Spezialseiten/Foo', $name ); } + /** + * @covers SpecialPageFactory::getTitleForAlias + */ public function testGetTitleForAlias() { $this->setMwGlobals( 'wgContLang', Language::factory( 'de' ) ); SpecialPageFactory::resetList(); @@ -222,4 +265,19 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { ); } + public function testGetAliasListRecursion() { + $called = false; + $this->mergeMwGlobalArrayValue( 'wgHooks', array( + 'SpecialPage_initList' => array( + function () use ( &$called ) { + SpecialPageFactory::getLocalNameFor( 'Specialpages' ); + $called = true; + } + ), + ) ); + SpecialPageFactory::resetList(); + SpecialPageFactory::getLocalNameFor( 'Specialpages' ); + $this->assertTrue( $called, 'Recursive call succeeded' ); + } + } diff --git a/tests/phpunit/includes/specialpage/SpecialPageTest.php b/tests/phpunit/includes/specialpage/SpecialPageTest.php new file mode 100644 index 00000000..5a0aef97 --- /dev/null +++ b/tests/phpunit/includes/specialpage/SpecialPageTest.php @@ -0,0 +1,104 @@ + + */ +class SpecialPageTest extends MediaWikiTestCase { + + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( array( + 'wgScript' => '/index.php', + 'wgContLang' => Language::factory( 'en' ) + ) ); + } + + /** + * @dataProvider getTitleForProvider + */ + public function testGetTitleFor( $expectedName, $name ) { + $title = SpecialPage::getTitleFor( $name ); + $expected = Title::makeTitle( NS_SPECIAL, $expectedName ); + $this->assertEquals( $expected, $title ); + } + + public function getTitleForProvider() { + return array( + array( 'UserLogin', 'Userlogin' ) + ); + } + + /** + * @expectedException PHPUnit_Framework_Error_Notice + */ + public function testInvalidGetTitleFor() { + $title = SpecialPage::getTitleFor( 'cat' ); + $expected = Title::makeTitle( NS_SPECIAL, 'Cat' ); + $this->assertEquals( $expected, $title ); + } + + /** + * @expectedException PHPUnit_Framework_Error_Notice + * @dataProvider getTitleForWithWarningProvider + */ + public function testGetTitleForWithWarning( $expected, $name ) { + $title = SpecialPage::getTitleFor( $name ); + $this->assertEquals( $expected, $title ); + } + + public function getTitleForWithWarningProvider() { + return array( + array( Title::makeTitle( NS_SPECIAL, 'UserLogin' ), 'UserLogin' ) + ); + } + + /** + * @dataProvider requireLoginAnonProvider + */ + public function testRequireLoginAnon( $expected, $reason, $title ) { + $specialPage = new SpecialPage( 'Watchlist', 'viewmywatchlist' ); + + $user = User::newFromId( 0 ); + $specialPage->getContext()->setUser( $user ); + $specialPage->getContext()->setLanguage( Language::factory( 'en' ) ); + + $this->setExpectedException( 'UserNotLoggedIn', $expected ); + + // $specialPage->requireLogin( [ $reason [, $title ] ] ) + call_user_func_array( + array( $specialPage, 'requireLogin' ), + array_filter( array( $reason, $title ) ) + ); + } + + public function requireLoginAnonProvider() { + $lang = 'en'; + + $expected1 = wfMessage( 'exception-nologin-text' )->inLanguage( $lang )->text(); + $expected2 = wfMessage( 'about' )->inLanguage( $lang )->text(); + + return array( + array( $expected1, null, null ), + array( $expected2, 'about', null ), + array( $expected2, 'about', 'about' ), + ); + } + + public function testRequireLoginNotAnon() { + $specialPage = new SpecialPage( 'Watchlist', 'viewmywatchlist' ); + + $user = User::newFromName( "UTSysop" ); + $specialPage->getContext()->setUser( $user ); + + $specialPage->requireLogin(); + + // no exception thrown, logged in use can access special page + $this->assertTrue( true ); + } + +} diff --git a/tests/phpunit/includes/specialpage/SpecialPageTestHelper.php b/tests/phpunit/includes/specialpage/SpecialPageTestHelper.php new file mode 100644 index 00000000..37e29dcb --- /dev/null +++ b/tests/phpunit/includes/specialpage/SpecialPageTestHelper.php @@ -0,0 +1,24 @@ +assertSame( $isValid, SpecialBookSources::isValidISBN( $isbn ) ); + } +} diff --git a/tests/phpunit/includes/specials/SpecialMIMESearchTest.php b/tests/phpunit/includes/specials/SpecialMIMESearchTest.php index 14d19685..fe1c9e83 100644 --- a/tests/phpunit/includes/specials/SpecialMIMESearchTest.php +++ b/tests/phpunit/includes/specials/SpecialMIMESearchTest.php @@ -5,11 +5,11 @@ class SpecialMIMESearchTest extends MediaWikiTestCase { - /** @var MIMESearchPage */ + /** @var MIMEsearchPage */ private $page; function setUp() { - $this->page = new MIMESearchPage; + $this->page = new MIMEsearchPage; $context = new RequestContext(); $context->setTitle( Title::makeTitle( NS_SPECIAL, 'MIMESearch' ) ); $context->setRequest( new FauxRequest() ); diff --git a/tests/phpunit/includes/title/ForeignTitleTest.php b/tests/phpunit/includes/title/ForeignTitleTest.php new file mode 100644 index 00000000..599d2a33 --- /dev/null +++ b/tests/phpunit/includes/title/ForeignTitleTest.php @@ -0,0 +1,103 @@ +assertEquals( true, $title->isNamespaceIdKnown() ); + $this->assertEquals( $expectedId, $title->getNamespaceId() ); + $this->assertEquals( $expectedName, $title->getNamespaceName() ); + $this->assertEquals( $expectedText, $title->getText() ); + } + + public function testUnknownNamespaceCheck( ) { + $title = new ForeignTitle( null, 'this', 'that' ); + + $this->assertEquals( false, $title->isNamespaceIdKnown() ); + $this->assertEquals( 'this', $title->getNamespaceName() ); + $this->assertEquals( 'that', $title->getText() ); + } + + public function testUnknownNamespaceError( ) { + $this->setExpectedException( 'MWException' ); + $title = new ForeignTitle( null, 'this', 'that' ); + $title->getNamespaceId(); + } + + public function fullTextProvider() { + return array( + array( + new ForeignTitle( 20, 'Contributor', 'JohnDoe' ), + 'Contributor:JohnDoe' + ), + array( + new ForeignTitle( '1', 'Discussion', 'Capital' ), + 'Discussion:Capital' + ), + array( + new ForeignTitle( 0, '', 'MainNamespace' ), + 'MainNamespace' + ), + array( + new ForeignTitle( 4, 'Some ns', 'Article title with spaces' ), + 'Some_ns:Article_title_with_spaces' + ), + ); + } + + /** + * @dataProvider fullTextProvider + */ + public function testFullText( ForeignTitle $title, $fullText ) { + $this->assertEquals( $fullText, $title->getFullText() ); + } +} diff --git a/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php b/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php index 4171c10e..cd0d0b1c 100644 --- a/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php +++ b/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php @@ -16,7 +16,6 @@ * http://www.gnu.org/copyleft/gpl.html * * @file - * @license GPL 2+ * @author Daniel Kinzler */ diff --git a/tests/phpunit/includes/title/MediaWikiTitleCodecTest.php b/tests/phpunit/includes/title/MediaWikiTitleCodecTest.php index f95b3050..78d304c1 100644 --- a/tests/phpunit/includes/title/MediaWikiTitleCodecTest.php +++ b/tests/phpunit/includes/title/MediaWikiTitleCodecTest.php @@ -16,7 +16,6 @@ * http://www.gnu.org/copyleft/gpl.html * * @file - * @license GPL 2+ * @author Daniel Kinzler */ @@ -39,6 +38,7 @@ class MediaWikiTitleCodecTest extends MediaWikiTestCase { 'wgLang' => Language::factory( 'en' ), 'wgAllowUserJs' => false, 'wgDefaultLanguageVariant' => false, + 'wgMetaNamespace' => 'Project', 'wgLocalInterwikis' => array( 'localtestiw' ), 'wgCapitalLinks' => true, @@ -82,6 +82,8 @@ class MediaWikiTitleCodecTest extends MediaWikiTestCase { protected function makeCodec( $lang ) { $gender = $this->getGenderCache(); $lang = Language::factory( $lang ); + // language object can came from cache, which does not respect test settings + $lang->resetNamespaces(); return new MediaWikiTitleCodec( $lang, $gender ); } @@ -367,13 +369,6 @@ class MediaWikiTitleCodecTest extends MediaWikiTestCase { /** * @dataProvider provideGetNamespaceName - * - * @param int $namespace - * @param string $text - * @param string $lang - * @param string $expected - * - * @internal param \TitleValue $title */ public function testGetNamespaceName( $namespace, $text, $lang, $expected ) { $codec = $this->makeCodec( $lang ); diff --git a/tests/phpunit/includes/title/NaiveForeignTitleFactoryTest.php b/tests/phpunit/includes/title/NaiveForeignTitleFactoryTest.php new file mode 100644 index 00000000..504e8712 --- /dev/null +++ b/tests/phpunit/includes/title/NaiveForeignTitleFactoryTest.php @@ -0,0 +1,91 @@ +createForeignTitle( $title, $ns ); + + $this->assertEquals( $testTitle->isNamespaceIdKnown(), + $foreignTitle->isNamespaceIdKnown() ); + + if ( + $testTitle->isNamespaceIdKnown() && + $foreignTitle->isNamespaceIdKnown() + ) { + $this->assertEquals( $testTitle->getNamespaceId(), + $foreignTitle->getNamespaceId() ); + } + + $this->assertEquals( $testTitle->getNamespaceName(), + $foreignTitle->getNamespaceName() ); + $this->assertEquals( $testTitle->getText(), $foreignTitle->getText() ); + + $this->assertEquals( str_replace( ' ', '_', $title ), + $foreignTitle->getFullText() ); + } +} diff --git a/tests/phpunit/includes/title/NaiveImportTitleFactoryTest.php b/tests/phpunit/includes/title/NaiveImportTitleFactoryTest.php new file mode 100644 index 00000000..98b414e0 --- /dev/null +++ b/tests/phpunit/includes/title/NaiveImportTitleFactoryTest.php @@ -0,0 +1,89 @@ +setMwGlobals( array( + 'wgLanguageCode' => 'en', + 'wgContLang' => Language::factory( 'en' ), + 'wgExtraNamespaces' => array( 100 => 'Portal' ), + ) ); + } + + public function basicProvider() { + return array( + array( + new ForeignTitle( 0, '', 'MainNamespaceArticle' ), + Title::newFromText( 'MainNamespaceArticle' ) + ), + array( + new ForeignTitle( null, '', 'MainNamespaceArticle' ), + Title::newFromText( 'MainNamespaceArticle' ) + ), + array( + new ForeignTitle( 1, 'Discussion', 'Nice_talk' ), + Title::newFromText( 'Talk:Nice_talk' ) + ), + array( + new ForeignTitle( 0, '', 'Bogus:Nice_talk' ), + Title::newFromText( 'Bogus:Nice_talk' ) + ), + array( + new ForeignTitle( 100, 'Bogus', 'Nice_talk' ), + Title::newFromText( 'Bogus:Nice_talk' ) // not Portal:Nice_talk + ), + array( + new ForeignTitle( 1, 'Bogus', 'Nice_talk' ), + Title::newFromText( 'Talk:Nice_talk' ) // not Bogus:Nice_talk + ), + array( + new ForeignTitle( 100, 'Portal', 'Nice_talk' ), + Title::newFromText( 'Portal:Nice_talk' ) + ), + array( + new ForeignTitle( 724, 'Portal', 'Nice_talk' ), + Title::newFromText( 'Portal:Nice_talk' ) + ), + array( + new ForeignTitle( 2, 'Portal', 'Nice_talk' ), + Title::newFromText( 'User:Nice_talk' ) + ), + ); + } + + /** + * @dataProvider basicProvider + */ + public function testBasic( ForeignTitle $foreignTitle, Title $title ) { + $factory = new NaiveImportTitleFactory(); + $testTitle = $factory->createTitleFromForeignTitle( $foreignTitle ); + + $this->assertTrue( $title->equals( $testTitle ) ); + } +} diff --git a/tests/phpunit/includes/title/NamespaceAwareForeignTitleFactoryTest.php b/tests/phpunit/includes/title/NamespaceAwareForeignTitleFactoryTest.php new file mode 100644 index 00000000..9cb195cc --- /dev/null +++ b/tests/phpunit/includes/title/NamespaceAwareForeignTitleFactoryTest.php @@ -0,0 +1,89 @@ + '', 1 => 'Talk', 100 => 'Portal', 9000 => 'Bogus' + ); + + $factory = new NamespaceAwareForeignTitleFactory( $foreignNamespaces ); + $testTitle = $factory->createForeignTitle( $title, $ns ); + + $this->assertEquals( $testTitle->isNamespaceIdKnown(), + $foreignTitle->isNamespaceIdKnown() ); + + if ( + $testTitle->isNamespaceIdKnown() && + $foreignTitle->isNamespaceIdKnown() + ) { + $this->assertEquals( $testTitle->getNamespaceId(), + $foreignTitle->getNamespaceId() ); + } + + $this->assertEquals( $testTitle->getNamespaceName(), + $foreignTitle->getNamespaceName() ); + $this->assertEquals( $testTitle->getText(), $foreignTitle->getText() ); + } +} diff --git a/tests/phpunit/includes/title/NamespaceImportTitleFactoryTest.php b/tests/phpunit/includes/title/NamespaceImportTitleFactoryTest.php new file mode 100644 index 00000000..d6fe6848 --- /dev/null +++ b/tests/phpunit/includes/title/NamespaceImportTitleFactoryTest.php @@ -0,0 +1,77 @@ +setMwGlobals( array( + 'wgLanguageCode' => 'en', + 'wgContLang' => Language::factory( 'en' ), + ) ); + } + + public function basicProvider() { + return array( + array( + new ForeignTitle( 0, '', 'MainNamespaceArticle' ), + 0, + Title::newFromText( 'MainNamespaceArticle' ) + ), + array( + new ForeignTitle( 0, '', 'MainNamespaceArticle' ), + 2, + Title::newFromText( 'User:MainNamespaceArticle' ) + ), + array( + new ForeignTitle( 1, 'Discussion', 'Nice_talk' ), + 0, + Title::newFromText( 'Nice_talk' ) + ), + array( + new ForeignTitle( 0, '', 'Bogus:Nice_talk' ), + 0, + Title::newFromText( 'Bogus:Nice_talk' ) + ), + array( + new ForeignTitle( 0, '', 'Bogus:Nice_talk' ), + 2, + Title::newFromText( 'User:Bogus:Nice_talk' ) + ), + ); + } + + /** + * @dataProvider basicProvider + */ + public function testBasic( ForeignTitle $foreignTitle, $ns, Title $title ) { + $factory = new NamespaceImportTitleFactory( $ns ); + $testTitle = $factory->createTitleFromForeignTitle( $foreignTitle ); + + $this->assertTrue( $title->equals( $testTitle ) ); + } +} diff --git a/tests/phpunit/includes/title/SubpageImportTitleFactoryTest.php b/tests/phpunit/includes/title/SubpageImportTitleFactoryTest.php new file mode 100644 index 00000000..d5c17f3e --- /dev/null +++ b/tests/phpunit/includes/title/SubpageImportTitleFactoryTest.php @@ -0,0 +1,86 @@ +setMwGlobals( array( + 'wgLanguageCode' => 'en', + 'wgContLang' => Language::factory( 'en' ), + 'wgNamespacesWithSubpages' => array( 0 => false, 2 => true ), + ) ); + } + + public function basicProvider() { + return array( + array( + new ForeignTitle( 0, '', 'MainNamespaceArticle' ), + Title::newFromText( 'User:Graham' ), + Title::newFromText( 'User:Graham/MainNamespaceArticle' ) + ), + array( + new ForeignTitle( 1, 'Discussion', 'Nice_talk' ), + Title::newFromText( 'User:Graham' ), + Title::newFromText( 'User:Graham/Discussion:Nice_talk' ) + ), + array( + new ForeignTitle( 0, '', 'Bogus:Nice_talk' ), + Title::newFromText( 'User:Graham' ), + Title::newFromText( 'User:Graham/Bogus:Nice_talk' ) + ), + ); + } + + /** + * @dataProvider basicProvider + */ + public function testBasic( ForeignTitle $foreignTitle, Title $rootPage, + Title $title ) { + + $factory = new SubpageImportTitleFactory( $rootPage ); + $testTitle = $factory->createTitleFromForeignTitle( $foreignTitle ); + + $this->assertTrue( $testTitle->equals( $title ) ); + } + + public function failureProvider() { + return array( + array( + Title::newFromText( 'Graham' ), + ), + ); + } + + /** + * @dataProvider failureProvider + */ + public function testFailures( Title $rootPage ) { + $this->setExpectedException( 'MWException' ); + new SubpageImportTitleFactory( $rootPage ); + } +} diff --git a/tests/phpunit/includes/title/TitleValueTest.php b/tests/phpunit/includes/title/TitleValueTest.php index 3ba008d6..184198d2 100644 --- a/tests/phpunit/includes/title/TitleValueTest.php +++ b/tests/phpunit/includes/title/TitleValueTest.php @@ -16,7 +16,6 @@ * http://www.gnu.org/copyleft/gpl.html * * @file - * @license GPL 2+ * @author Daniel Kinzler */ diff --git a/tests/phpunit/includes/upload/UploadBaseTest.php b/tests/phpunit/includes/upload/UploadBaseTest.php index 3d3b0068..9441b77f 100644 --- a/tests/phpunit/includes/upload/UploadBaseTest.php +++ b/tests/phpunit/includes/upload/UploadBaseTest.php @@ -9,21 +9,17 @@ class UploadBaseTest extends MediaWikiTestCase { protected $upload; protected function setUp() { - global $wgHooks; parent::setUp(); $this->upload = new UploadTestHandler; - $this->hooks = $wgHooks; - $wgHooks['InterwikiLoadPrefix'][] = function ( $prefix, &$data ) { - return false; - }; - } - - protected function tearDown() { - global $wgHooks; - $wgHooks = $this->hooks; - parent::tearDown(); + $this->setMwGlobals( 'wgHooks', array( + 'InterwikiLoadPrefix' => array( + function ( $prefix, &$data ) { + return false; + } + ), + ) ); } /** @@ -97,7 +93,7 @@ class UploadBaseTest extends MediaWikiTestCase { // Helper used to create an empty file of size $size. private function createFileOfSize( $size ) { - $filename = tempnam( wfTempDir(), "mwuploadtest" ); + $filename = $this->getNewTempFile(); $fh = fopen( $filename, 'w' ); ftruncate( $fh, $size ); @@ -112,22 +108,21 @@ class UploadBaseTest extends MediaWikiTestCase { * This method should be abstracted so we can test different settings. */ public function testMaxUploadSize() { - global $wgMaxUploadSize; - $savedGlobal = $wgMaxUploadSize; // save global - global $wgFileExtensions; - $wgFileExtensions[] = 'txt'; - - $wgMaxUploadSize = 100; + $this->setMwGlobals( array( + 'wgMaxUploadSize' => 100, + 'wgFileExtensions' => array( + 'txt', + ), + ) ); - $filename = $this->createFileOfSize( $wgMaxUploadSize ); + $filename = $this->createFileOfSize( 100 ); $this->upload->initializePathInfo( basename( $filename ) . '.txt', $filename, 100 ); $result = $this->upload->verifyUpload(); - unlink( $filename ); $this->assertEquals( - array( 'status' => UploadBase::OK ), $result ); - - $wgMaxUploadSize = $savedGlobal; // restore global + array( 'status' => UploadBase::OK ), + $result + ); } diff --git a/tests/phpunit/includes/upload/UploadFromUrlTest.php b/tests/phpunit/includes/upload/UploadFromUrlTest.php index ec56b63e..b7496629 100644 --- a/tests/phpunit/includes/upload/UploadFromUrlTest.php +++ b/tests/phpunit/includes/upload/UploadFromUrlTest.php @@ -35,7 +35,10 @@ class UploadFromUrlTest extends ApiTestCase { wfSetupSession( $sessionId ); - return array( $module->getResultData(), $req ); + return array( + $module->getResult()->getResultData( null, array( 'Strip' => 'all' ) ), + $req + ); } /** diff --git a/tests/phpunit/includes/utils/CdbTest.php b/tests/phpunit/includes/utils/CdbTest.php deleted file mode 100644 index 487ee1fc..00000000 --- a/tests/phpunit/includes/utils/CdbTest.php +++ /dev/null @@ -1,90 +0,0 @@ -markTestSkipped( 'Native CDB support is not available' ); - } - } - - /** - * @group medium - */ - public function testCdb() { - $dir = wfTempDir(); - if ( !is_writable( $dir ) ) { - $this->markTestSkipped( "Temp dir isn't writable" ); - } - - $phpcdbfile = $this->getNewTempFile(); - $dbacdbfile = $this->getNewTempFile(); - - $w1 = new CdbWriterPHP( $phpcdbfile ); - $w2 = new CdbWriterDBA( $dbacdbfile ); - - $data = array(); - for ( $i = 0; $i < 1000; $i++ ) { - $key = $this->randomString(); - $value = $this->randomString(); - $w1->set( $key, $value ); - $w2->set( $key, $value ); - - if ( !isset( $data[$key] ) ) { - $data[$key] = $value; - } - } - - $w1->close(); - $w2->close(); - - $this->assertEquals( - md5_file( $phpcdbfile ), - md5_file( $dbacdbfile ), - 'same hash' - ); - - $r1 = new CdbReaderPHP( $phpcdbfile ); - $r2 = new CdbReaderDBA( $dbacdbfile ); - - foreach ( $data as $key => $value ) { - if ( $key === '' ) { - // Known bug - continue; - } - $v1 = $r1->get( $key ); - $v2 = $r2->get( $key ); - - $v1 = $v1 === false ? '(not found)' : $v1; - $v2 = $v2 === false ? '(not found)' : $v2; - - # cdbAssert( 'Mismatch', $key, $v1, $v2 ); - $this->cdbAssert( "PHP error", $key, $v1, $value ); - $this->cdbAssert( "DBA error", $key, $v2, $value ); - } - } - - private function randomString() { - $len = mt_rand( 0, 10 ); - $s = ''; - for ( $j = 0; $j < $len; $j++ ) { - $s .= chr( mt_rand( 0, 255 ) ); - } - - return $s; - } - - private function cdbAssert( $msg, $key, $v1, $v2 ) { - $this->assertEquals( - $v2, - $v1, - $msg . ', k=' . bin2hex( $key ) - ); - } -} diff --git a/tests/phpunit/includes/utils/IPTest.php b/tests/phpunit/includes/utils/IPTest.php index ebe347fd..09c1587d 100644 --- a/tests/phpunit/includes/utils/IPTest.php +++ b/tests/phpunit/includes/utils/IPTest.php @@ -9,7 +9,7 @@ * dataprovider. */ -class IPTest extends MediaWikiTestCase { +class IPTest extends PHPUnit_Framework_TestCase { /** * not sure it should be tested with boolean false. hashar 20100924 * @covers IP::isIPAddress diff --git a/tests/phpunit/includes/utils/MWCryptHKDFTest.php b/tests/phpunit/includes/utils/MWCryptHKDFTest.php index 7e37534a..73e4c1a9 100644 --- a/tests/phpunit/includes/utils/MWCryptHKDFTest.php +++ b/tests/phpunit/includes/utils/MWCryptHKDFTest.php @@ -6,6 +6,12 @@ class MWCryptHKDFTest extends MediaWikiTestCase { + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( 'wgSecretKey', '5bf1945342e67799cb50704a7fa19ac6' ); + } + /** * Test basic usage works */ diff --git a/tests/phpunit/includes/utils/MWFunctionTest.php b/tests/phpunit/includes/utils/MWFunctionTest.php new file mode 100644 index 00000000..f4d17999 --- /dev/null +++ b/tests/phpunit/includes/utils/MWFunctionTest.php @@ -0,0 +1,34 @@ +hideDeprecated( 'MWFunction::newObj' ); + $this->assertEquals( + MWFunction::newObj( 'MWBlankClass', $args )->args, + $newObject->args + ); + } +} + +class MWBlankClass { + + public $args = array(); + + function __construct( $arg1, $arg2, $arg3, $arg4 ) { + $this->args = array( $arg1, $arg2, $arg3, $arg4 ); + } +} + +class ExampleObject { +} diff --git a/tests/phpunit/includes/utils/StringUtilsTest.php b/tests/phpunit/includes/utils/StringUtilsTest.php deleted file mode 100644 index 0fdb8e15..00000000 --- a/tests/phpunit/includes/utils/StringUtilsTest.php +++ /dev/null @@ -1,149 +0,0 @@ -markTestSkipped( 'Test requires the mbstring PHP extension' ); - } - $this->assertEquals( $expected, - StringUtils::isUtf8( $string ), - 'Testing string "' . $this->escaped( $string ) . '" with mb_check_encoding' - ); - } - - /** - * This tests StringUtils::isUtf8 making sure we use the pure PHP - * implementation used as a fallback when mb_check_encoding() is - * not available. - * - * @covers StringUtils::isUtf8 - * @dataProvider provideStringsForIsUtf8Check - */ - public function testIsUtf8WithPhpFallbackImplementation( $expected, $string ) { - $this->assertEquals( $expected, - StringUtils::isUtf8( $string, /** disable mbstring: */true ), - 'Testing string "' . $this->escaped( $string ) . '" with pure PHP implementation' - ); - } - - /** - * Print high range characters as a hexadecimal - * @param string $string - * @return string - */ - function escaped( $string ) { - $escaped = ''; - $length = strlen( $string ); - for ( $i = 0; $i < $length; $i++ ) { - $char = $string[$i]; - $val = ord( $char ); - if ( $val > 127 ) { - $escaped .= '\x' . dechex( $val ); - } else { - $escaped .= $char; - } - } - - return $escaped; - } - - /** - * See also "UTF-8 decoder capability and stress test" by - * Markus Kuhn: - * http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt - */ - public static function provideStringsForIsUtf8Check() { - // Expected return values for StringUtils::isUtf8() - $PASS = true; - $FAIL = false; - - return array( - 'some ASCII' => array( $PASS, 'Some ASCII' ), - 'euro sign' => array( $PASS, "Euro sign €" ), - - 'first possible sequence 1 byte' => array( $PASS, "\x00" ), - 'first possible sequence 2 bytes' => array( $PASS, "\xc2\x80" ), - 'first possible sequence 3 bytes' => array( $PASS, "\xe0\xa0\x80" ), - 'first possible sequence 4 bytes' => array( $PASS, "\xf0\x90\x80\x80" ), - 'first possible sequence 5 bytes' => array( $FAIL, "\xf8\x88\x80\x80\x80" ), - 'first possible sequence 6 bytes' => array( $FAIL, "\xfc\x84\x80\x80\x80\x80" ), - - 'last possible sequence 1 byte' => array( $PASS, "\x7f" ), - 'last possible sequence 2 bytes' => array( $PASS, "\xdf\xbf" ), - 'last possible sequence 3 bytes' => array( $PASS, "\xef\xbf\xbf" ), - 'last possible sequence 4 bytes (U+1FFFFF)' => array( $FAIL, "\xf7\xbf\xbf\xbf" ), - 'last possible sequence 5 bytes' => array( $FAIL, "\xfb\xbf\xbf\xbf\xbf" ), - 'last possible sequence 6 bytes' => array( $FAIL, "\xfd\xbf\xbf\xbf\xbf\xbf" ), - - 'boundary 1' => array( $PASS, "\xed\x9f\xbf" ), - 'boundary 2' => array( $PASS, "\xee\x80\x80" ), - 'boundary 3' => array( $PASS, "\xef\xbf\xbd" ), - 'boundary 4' => array( $PASS, "\xf2\x80\x80\x80" ), - 'boundary 5 (U+FFFFF)' => array( $PASS, "\xf3\xbf\xbf\xbf" ), - 'boundary 6 (U+100000)' => array( $PASS, "\xf4\x80\x80\x80" ), - 'boundary 7 (U+10FFFF)' => array( $PASS, "\xf4\x8f\xbf\xbf" ), - 'boundary 8 (U+110000)' => array( $FAIL, "\xf4\x90\x80\x80" ), - - 'malformed 1' => array( $FAIL, "\x80" ), - 'malformed 2' => array( $FAIL, "\xbf" ), - 'malformed 3' => array( $FAIL, "\x80\xbf" ), - 'malformed 4' => array( $FAIL, "\x80\xbf\x80" ), - 'malformed 5' => array( $FAIL, "\x80\xbf\x80\xbf" ), - 'malformed 6' => array( $FAIL, "\x80\xbf\x80\xbf\x80" ), - 'malformed 7' => array( $FAIL, "\x80\xbf\x80\xbf\x80\xbf" ), - 'malformed 8' => array( $FAIL, "\x80\xbf\x80\xbf\x80\xbf\x80" ), - - 'last byte missing 1' => array( $FAIL, "\xc0" ), - 'last byte missing 2' => array( $FAIL, "\xe0\x80" ), - 'last byte missing 3' => array( $FAIL, "\xf0\x80\x80" ), - 'last byte missing 4' => array( $FAIL, "\xf8\x80\x80\x80" ), - 'last byte missing 5' => array( $FAIL, "\xfc\x80\x80\x80\x80" ), - 'last byte missing 6' => array( $FAIL, "\xdf" ), - 'last byte missing 7' => array( $FAIL, "\xef\xbf" ), - 'last byte missing 8' => array( $FAIL, "\xf7\xbf\xbf" ), - 'last byte missing 9' => array( $FAIL, "\xfb\xbf\xbf\xbf" ), - 'last byte missing 10' => array( $FAIL, "\xfd\xbf\xbf\xbf\xbf" ), - - 'extra continuation byte 1' => array( $FAIL, "e\xaf" ), - 'extra continuation byte 2' => array( $FAIL, "\xc3\x89\xaf" ), - 'extra continuation byte 3' => array( $FAIL, "\xef\xbc\xa5\xaf" ), - 'extra continuation byte 4' => array( $FAIL, "\xf0\x9d\x99\xb4\xaf" ), - - 'impossible bytes 1' => array( $FAIL, "\xfe" ), - 'impossible bytes 2' => array( $FAIL, "\xff" ), - 'impossible bytes 3' => array( $FAIL, "\xfe\xfe\xff\xff" ), - - 'overlong sequences 1' => array( $FAIL, "\xc0\xaf" ), - 'overlong sequences 2' => array( $FAIL, "\xc1\xaf" ), - 'overlong sequences 3' => array( $FAIL, "\xe0\x80\xaf" ), - 'overlong sequences 4' => array( $FAIL, "\xf0\x80\x80\xaf" ), - 'overlong sequences 5' => array( $FAIL, "\xf8\x80\x80\x80\xaf" ), - 'overlong sequences 6' => array( $FAIL, "\xfc\x80\x80\x80\x80\xaf" ), - - 'maximum overlong sequences 1' => array( $FAIL, "\xc1\xbf" ), - 'maximum overlong sequences 2' => array( $FAIL, "\xe0\x9f\xbf" ), - 'maximum overlong sequences 3' => array( $FAIL, "\xf0\x8f\xbf\xbf" ), - 'maximum overlong sequences 4' => array( $FAIL, "\xf8\x87\xbf\xbf" ), - 'maximum overlong sequences 5' => array( $FAIL, "\xfc\x83\xbf\xbf\xbf\xbf" ), - - 'surrogates 1 (U+D799)' => array( $PASS, "\xed\x9f\xbf" ), - 'surrogates 2 (U+E000)' => array( $PASS, "\xee\x80\x80" ), - 'surrogates 3 (U+D800)' => array( $FAIL, "\xed\xa0\x80" ), - 'surrogates 4 (U+DBFF)' => array( $FAIL, "\xed\xaf\xbf" ), - 'surrogates 5 (U+DC00)' => array( $FAIL, "\xed\xb0\x80" ), - 'surrogates 6 (U+DFFF)' => array( $FAIL, "\xed\xbf\xbf" ), - 'surrogates 7 (U+D800 U+DC00)' => array( $FAIL, "\xed\xa0\x80\xed\xb0\x80" ), - - 'noncharacters 1' => array( $PASS, "\xef\xbf\xbe" ), - 'noncharacters 2' => array( $PASS, "\xef\xbf\xbf" ), - ); - } -} diff --git a/tests/phpunit/includes/utils/UIDGeneratorTest.php b/tests/phpunit/includes/utils/UIDGeneratorTest.php index 50fa3849..0e11ccad 100644 --- a/tests/phpunit/includes/utils/UIDGeneratorTest.php +++ b/tests/phpunit/includes/utils/UIDGeneratorTest.php @@ -35,14 +35,14 @@ class UIDGeneratorTest extends MediaWikiTestCase { $lastId_bin = wfBaseConvert( $lastId, 10, 2 ); $this->assertGreaterThanOrEqual( - substr( $id_bin, 0, $tbits ), substr( $lastId_bin, 0, $tbits ), + substr( $id_bin, 0, $tbits ), "New ID timestamp ($id_bin) >= prior one ($lastId_bin)." ); if ( $hostbits ) { $this->assertEquals( - substr( $id_bin, 0, -$hostbits ), - substr( $lastId_bin, 0, -$hostbits ), + substr( $id_bin, -$hostbits ), + substr( $lastId_bin, -$hostbits ), "Host ID of ($id_bin) is same as prior one ($lastId_bin)." ); } diff --git a/tests/phpunit/includes/utils/ZipDirectoryReaderTest.php b/tests/phpunit/includes/utils/ZipDirectoryReaderTest.php index 34ffb535..05d07d45 100644 --- a/tests/phpunit/includes/utils/ZipDirectoryReaderTest.php +++ b/tests/phpunit/includes/utils/ZipDirectoryReaderTest.php @@ -4,7 +4,7 @@ * @covers ZipDirectoryReader * NOTE: this test is more like an integration test than a unit test */ -class ZipDirectoryReaderTest extends MediaWikiTestCase { +class ZipDirectoryReaderTest extends PHPUnit_Framework_TestCase { protected $zipDir; protected $entries; -- cgit v1.2.2